Skip to content

Test G's income stand-in on NZ's toy graph - #1483

Merged
MaxGhenis merged 3 commits into
nzhub/g8r2c-g-wiringfrom
nzhub/g8r2c-g-toy
Nov 3, 2026
Merged

MaxGhenis merged 3 commits into
nzhub/g8r2c-g-wiringfrom
nzhub/g8r2c-g-toy

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

G8-r2c (#1183), brief 212: design 80 section 6 G's toy-graph tests for G's income stand-in. Draft, stacked on G (#1478, nzhub/g8r2c-g-wiring at 64ef20af). The toy session's commits change only the toy fixture and the toy test file, and the hub's commit 85d55af adds one test to test_nz_transport_declarations.py. The PR changes no kernel, no packaged resource and no golden. Paths: T/ is packages/microcosm-build/src/microcosm/build/transport/, N/ is packages/microcosm-build/src/microcosm/build/nz/, and tests sit in packages/microcosm-build/tests/engine_free/shared/. File:line is at 4ae5810e, the toy session's last commit; 85d55af changes no line in the toy files.

Change

test_support/microcosm_build/transport_nz_toy.py gives the toy its own counterpart of each piece G packages. Every name starts with toy_, except the stand-in node's id, which mirrors NZ's.

  • Measure (toy_standin_declaration, :635): the toy's version of N/income_measurements.json measures.central. It sums wages, signed farm income, interest, rental (the one add-back) and the toy's three annual transfers, with floor 0 and scale 100 (R2C-10). It writes toy_income_proxy_cents (Int64), toy_income_proxy_nzd and toy_income_proxy_universe_status. The resource toy_income_measurements carries it with its canonical SHA-256.
  • Nodes (_income_standin, :722): nz.earnings.cents (money.minor_units@1, E's wage-cents node) and nz.income.standin (measure.combine@1, measurement_id: toy_income_standin_v1) on nz.central. The variant branches clone both.
  • Coarse table (toy_standin_table, :649): toy_income_standin, with 3 bands, both companions and no nil band. Like NZ's table, its first cut (15,000 cents) sits above zero.
  • Summed rows (toy_standin_references, :690): 6 rows in A's multi-member form (R2C-17), with sum for counts and identity for amounts, each pinned to its members in TOY_STANDIN_MEMBERS (:611). Family is income. Their measures are toy_people, a constant like NZ's people, and toy_income_proxy_nzd. Nine invented native member facts (TOY_STANDIN_FACTS, :618) sit near the toy's pre-fit weighted totals.
  • Ageing profile: reference_profiles declares the dimension toy_aging_index with one scale_money_and_intervals transform over the table and the 6 rows, under required_measurement_binding. _pin_calibration_facts (:879) writes the toy calibration facts and the shared factor fixture's facts as one pinned feed. It then resolves the factor (1.1) against that feed and passes the pin to every targets.compile@1 node, so the compiler replays the factor as NZ's compiler does.
  • Prepare and problem: prepare reads the stand-in's cents and universe columns and writes toy_people and the 3 interval indicators. The problem slices those columns, the unit column (R2C-25, :846) and household.source_household_id.
  • Gates: toy_target_profile_coverage (required_families: ["income", "toy"], release-blocking) and toy_measurement_prefit. The prefit gate uses R2C-3's ratio bounds, the packaged required_evidence list (TOY_PREFIT_EVIDENCE, :909) and support thresholds of 3 source households and Kish ESS 2, sized for forty households (:632). Its criticality is diagnostic (:873).

Invariants and tests

All tests are in test_nz_transport_toy_graph.py.

Design 80 section 6 G Invariant Test Mutant → red
(1) Each of the 6 coarse rows appears once in the problem's matrix, with nonzero support, family income and its declared measure. A's proof names nz.income.standin as nz.calibrate's producer test_each_toy_coarse_row_enters_the_problem_matrix_once_with_support (:786) problem_slices_no_nzd: targets.problem@1 refuses the 3 amount rows, so the toy fixture errors and every toy test that uses it goes red, this one included
(2) target_profile_coverage passes, with income required test_target_profile_coverage_passes_on_the_toy_with_income_required (:825) drop_standin_node: activation refuses the 6 rows, so the fixture errors
(3) measurement_prefit runs. Its failures are exactly one evidence: the run lacks required evidence '<alias>'. per packaged alias, in order, with no roster, band, support or diagnostics failure. The toy's list equals N/gates.json's test_measurement_prefit_fails_the_toy_only_on_its_unwired_evidence (:839) alias_as_problem_artifact (alias native_band_report wired to nz.targets.problem's problem artifact): 5 lines instead of 6. support_threshold_10 (minimum source households 10, above b2's 9): a support failure appears. alias_wired_to_problem (artifact name native_band_report): composition refuses "reads undeclared artifact"
(4) Each coarse count's problem value is the sum of its declared members' count facts in the pinned feed. Each amount's value is 11/10 times the sum of their amount facts (rel 1e-12). Each row's selector equals the declared members test_each_toy_coarse_reference_equals_the_sum_of_its_member_references (:880) members_swapped (b0 and b1 trade n03 and n04 in both rows of both bands)
Property For any toy incomes (Hypothesis, 150 examples, through combine_measure and interval_indicators on the effective table): x > the aged first cut lands in exactly one band, the one an independent cut count names; 0 < x ≤ the aged first cut lands in none; x ≤ 0 is excluded and lands in none test_property_a_positive_standin_above_the_first_cut_lands_in_one_band (:949) table_first_cut_0 (the table's first cut set to 0)
A's proof Without the stand-in, activation names each of the 6 rows and measurement:toy_income_standin_v1 test_without_the_toy_standin_its_six_rows_refuse_activation_by_name (:1010) builds the drop_standin_node mutant itself

Mutants. I ran each mutant in place on the working tree, ran the 7 affected tests, and restored the files with git checkout after each one. git status was clean after every series. one_member_changed (one member changed in b0's count row only) made composition refuse ("References 'toy_income_standin_amount__b0' and 'toy_income_standin_amount__b1' share a summed member"), so the fixture errored. members_swapped is the variant that composes, and the differential catches it. No mutant survived.

Existing toy tests whose counts changed

  • test_four_regional_age_cells_prepare_and_calibrate_once (:487): n_targets goes from 4 to 4 + 2 * len(TOY_STANDIN_MEMBERS) (10), because the problem now holds the 4 demography cells plus a count row and an amount row for each of the 3 coarse bands.
  • test_each_activated_calibration_row_compiles_once_and_exempt_rows_stay_out (:240): the expected set now comes from the effective view, transport_resource(spec, "target_references"), not the raw resource. The 6 rows are requires_prerequisites in the raw resource, and the profile and A's proof activate them before compilation, so the compiled set holds 4 + 6 names.

Tests at 4ae5810e

NZ_CHRONICLE_ARTIFACT was set to the brief's feed, and both digests match the pin (03f5ea0f…, 1ea2d1ee…). Runs used -n 4.

  • test_nz_transport_toy_graph.py: 31 passed, 0 skipped.
  • test_nz_transport_declarations.py: 60 passed, 0 skipped.
  • python3 tools/ci_test_plan.py verify: verification=ok.
  • ruff check and ruff format --check pass on both files.

Hub commit 85d55af

The NZ hub added one test to test_nz_transport_declarations.py after the toy hand-off, because of the proof-scope observation in Notes. test_every_calibration_node_has_its_own_standin_as_the_only_writer composes NZ's packaged graph. For each of its nine calibration nodes (nz.calibrate, and nz.v.<V>.calibrate for V1, V2, V3, V4, VEX, VHF, VPT and VSI), it asserts R2C-15 on compiled ancestry: exactly one ancestor declares ird_taxable_income_ty2027_v1, it is that branch's stand-in, and no other ancestor writes any of the proof's columns as an output or an EXPAND cell. A cached helper, _nz_composed, composes the graph once per process for this test and test_nz_packaged_graph_compiles_with_only_pins_and_money_producers.

Mutant (in N/scenarios.json, restored after each run) Result
vsi_standin_removed: drop nz.v.VSI.income.standin The new test fails its declarer assertion, because the test helper substitutes nz.v.VSI.synthetic_measurement. The existing compile test passes.
vsi_standin_undeclared: drop that stand-in's measurement_id Composition refuses: the column is "owned by both 'nz.v.VSI.income.standin' and 'nz.v.VSI.synthetic_measurement'".
vex_undeclared_rewrite: add a second, undeclared writer of a proof column to VEX Composition refuses: "owned by both 'nz.v.VEX.income.standin' and 'nz.v.VEX.income.rewrite'".

No NZ scenario declares an EXPAND node, so no mutant reaches the EXPAND-cell clause. Before the commit, a hub script applied the same rule to G's head 64ef20af and found all nine nodes passing. The hub ran the two tests the commit touches (2 passed in 61.5 s, peak 1.45 GB), ruff check and ruff format --check on the file, and tools/ci_test_plan.py verify (verification=ok).

Notes

  • Prefit criticality. The toy's measurement_prefit entry is diagnostic, not release_blocking like NZ's, because R2C-18 makes it fail every run. A blocking entry would stop the toy export that the other toy tests exercise. The test checks that the refusal reaches the gate report.
  • One ageing profile. The toy feed holds one factor's anchor facts, so toy_aging_index declares only central.
  • The property's wording. The brief says every positive stand-in falls in exactly one band. With a first cut above zero and no nil band (as NZ's table has), a positive value at or below the first cut falls in none. The property states that case.
  • Proof scope (observation, no ruling asked). measurement_proofs (T/compose.py:610) and _check_measurement_proofs (:716, called at :4372 with the skeleton graph) walk only the transport skeleton. So the proof names a producer for nz.calibrate only, and not for the variant calibrations on scenario branches. The toy test asserts what the code does. The hub's commit 85d55af checks the rule on every calibration node of NZ's composed graph (see above).

https://claude.ai/code/session_01LwQq6FpF7dJcTwxa1pX54W


Generated by Claude Code

Give the synthetic NZ toy its own counterpart of each piece G packages:
a measure.combine@1 stand-in node with measurement_id, its declaration
(floor 0, scale 100, rental as the one add-back), a cents node for the
mapped wage, a three-band coarse table with no nil band, six summed rows
(sum counts, identity amounts) over invented native member facts, a
scale_money_and_intervals ageing profile resolved against a pinned toy
feed, the prepare and problem inputs (the problem slices the unit
column, R2C-25), target_profile_coverage and a diagnostic
measurement_prefit entry with the packaged required_evidence list.

Invariants, each checked by a new toy test: the six rows enter the
problem matrix once with nonzero support; target_profile_coverage passes
with income required; measurement_prefit's only failures are one
evidence line per unwired alias (R2C-18); each coarse value equals its
declared members' sum (times the ageing factor for amounts). A
Hypothesis property checks the band membership of any toy incomes.

Two existing toy tests count the new rows: n_targets moves from 4 to 10,
and the compiled-row test reads the effective (profile-resolved) view.
The full toy file passes (30 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FpF7dJcTwxa1pX54W
The drop-the-node mutant made the toy fixture itself refuse, so no toy
test asserted the reason. This test removes nz.income.standin from a
copy of the toy spec and checks that activation names each of the six
coarse rows and measurement:toy_income_standin_v1, as A's proof
requires (R2C-1). The toy file passes in full (31 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FpF7dJcTwxa1pX54W
compose's _check_measurement_proofs runs on the transport skeleton,
before the scenario extensions append their branches, so it checks
R2C-15 for nz.calibrate only and never sees the eight variant
calibrations (nz.v.<V>.calibrate). The new test composes NZ's packaged
graph and, for each of the nine calibration nodes, asserts that exactly
one compiled ancestor declares ird_taxable_income_ty2027_v1, that it is
that branch's stand-in, and that no other ancestor writes any of the
proof's columns as an output or an EXPAND cell. A cached helper composes
the graph once per process for this test and the existing compile test.

Mutants, each restored after its run:
- Removing nz.v.VSI.income.standin from scenarios.json fails the new
  test's declarer assertion (the test helper substitutes
  nz.v.VSI.synthetic_measurement), while the existing compile test
  passes.
- Dropping that stand-in's measurement_id, or adding an undeclared
  second writer of a proof column to VEX, makes composition refuse the
  graph with two owners of one column, before the new test's assertions
  run.
No NZ scenario declares an EXPAND node, so no mutant reaches the
EXPAND-cell clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants