Skip to content

G8-r2c E-G: NZ's wage map, coarse income rows, stand-in measure, variants and pre-fit gate - #1478

Merged
MaxGhenis merged 36 commits into
mainfrom
nzhub/g8r2c-g-wiring
Nov 4, 2026
Merged

MaxGhenis merged 36 commits into
mainfrom
nzhub/g8r2c-g-wiring

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

G8-r2c hunks E, F and G (#1183), with G's toy-graph tests and two fix rounds, in one merge commit (R2C-21a): E maps NZ's P1 wages onto IRD's scaled wage bands (#1475), F replaces NZ's 474 native income rows with 24 coarse stand-in rows (#1476), and G wires NZ's stand-in income measure, its variants, the NZS S5 stage and the pre-fit gate. Paths: B/ is packages/microcosm-build/src/microcosm/build/, N/ is B/nz/, T/ is B/transport/, and tests sit in packages/microcosm-build/tests/engine_free/shared/. File:line in G's sections refers to G's head, 64ef20af.

What this merge brings (R2C-21a)

One merge commit of f43e8fbb into main brings the 36 commits in main..f43e8fbb: hunks E, F and G, the toy-graph tests and two hub fix rounds. Each part's PR body holds its change list, invariants and tests; the sections below this one describe G.

Part PR Commits (each part's own line) What it does
E #1475 fee9f17b, 572ee347, 385d5967 and two merges of the integration base Maps NZ's P1 wages onto IRD's scaled wage bands with transport.quantile_map@2
F #1476 c91a7808, 8167c424, a52efb77, c9f5b0c2 and four merges Replaces NZ's 474 native income rows with 24 coarse stand-in rows
G this PR 714e10d0, 75eee5f6, 50b033a7, 8c66267e, a3fe1952, 423ca43a, 64ef20af and three merges Wires NZ's stand-in income measure, its three variants, the NZS S5 stage and the pre-fit gate
Toy tests #1483 9ae0914c, 4ae5810e, 85d55af5 Adds design §6 G's four tests on NZ's synthetic toy graph; the hub's 85d55af5 checks that each of NZ's nine calibration nodes has its own stand-in as the only writer of the measured columns
Fix round 1 #1486 06760993, 0506c9d6, 044440a7, d1d72353 (merges main 4a36edcf), 20507ad8 Answers the E-G review's fixes owed (#1183 comment 6599546057) under rulings R2C-26 and R2C-27
Fix round 2 #1486 f43e8fbb Keeps NZ's interval outputs out of the small-frame column tests; CI found the 17 failures on probe #1489

Gates at f43e8fbb.

Two sessions built this PR. Brief 194's session built it up to a3fe1952. Brief 209's session (this update) added R2C-25's line, the tests listed under "Added at the hand-off", the mutant runs, the hash table and this body.

Landing (R2C-21a, #1183 comment 6604753490). E, F and G land together. At 21:10Z 11/03 the hub retargeted this PR to main and fast-forwarded its branch to the fix rounds' top f43e8fbb (#1486). One merge commit brings E's and F's commits, the toy tests (#1483) and both fix rounds, so #1475 and #1476 never land alone. GitHub marked #1483 merged into this branch when the branch moved.

Change

  • N/income_measurements.json (new; written by tools/build_nz_transport_graph.py:487, income_measurements_document):
    • measures.central (:8): C's measure.combine@1 declaration for ird_taxable_income_ty2027_v1 on person, floor 0 and scale 100 (R2C-10), outputs ird_taxable_income_proxy_cents, _nzd and _universe_status, with rental income as the one add-back. Components follow design §3's table: wages, signed non-farm and farm self-employment, interest, dividends, rental, and the annual gross JS, SPS, SLP and NZS transfers.
    • measures.vsi (:67): the same declaration without the four transfer addends.
    • Each declaration carries its canonical SHA-256, which the node selects beside it.
    • omission_register (:111): the 13 items design §3 excludes. Every size reads unknown, because design 80's percentages are the proposal's claims and no repo resource or cited public source gives one.
    • review_triggers (:181): VSI's three R2C-3 escalation triggers. diagnostics.wage_earner_count (:200): R2C-8's diagnostic.
  • nz.income.standin (N/transport_graph.json:1845; builder tools/build_nz_transport_graph.py:603, standin_node): measure.combine@1 on nz.central, after nz.benefits.annual_transfers and before nz.targets.prepare, with measurement_id: ird_taxable_income_ty2027_v1 in its params (:1901, R2C-13). Every calibration variant repeats it on its own branch in N/scenarios.json; VSI's selects measures.vsi.
  • Prepare and problem inputs. nz.targets.prepare reads the income and universe columns. nz.targets.problem reads those, the unit column ird_taxable_income_proxy_nzd (a3fe1952) and household.source_household_id (tools/build_nz_transport_graph.py:815-819). N/calibration_bindings.json:4418 onward declares the 12 band indicators (tools/generate_nz_target_references.py:408).
  • S5 stage nz.precal.nz_super (N/precalibration_stages.json:84, R2C-2): outputs entitled_to_new_zealand_superannuation (classification) and nz_super_weekly_amount_before_tax (calibration measure), and the derived nz_super_weekly_amount_before_tax_cents. nz.gross_up.super_cents gains engine_ref and period selections of nz_super (N/benefit_gross_up.json:336-345; tools/build_nz_benefit_gross_up.py).
  • Variants in N/scenarios.json: VSI (:27520, knob income_measure: vsi), VEX (:32117, annual_transfer_exposure: 0.75) and VPT (knob earnings_tail_anchor: vpt, E's $300,000 anchor). VEX's annual-transfers declaration (:33563) carries the exact decimal text "0.75", and the knob's domain text (:68) calls 0.75 a declared judgment, not a measured exposure (R2C-7). That declaration keeps the convention label full_year_stock_equivalent at 0.75, as the E-G review notes. The knob domains sit at :64-69. tools/build_nz_entitlement_graph.py:628-629 carries the two new knobs into the variant branches, and N/reporting_classifiers.json adds the three variants to the carried scenarios and the structural-zero list.
  • Gates (N/gates.json):
    • measurement_prefit (:101), release-blocking, terminal: R2C-3's frozen numbers in the parameter names D's gate reads (roster_total_bounds [0.75, 1.25], band_bounds [0.5, 2.0], maximum_discrepant_bands 2, minimum_source_households 30, minimum_kish_ess 30), table ird_taxable_income_standin_ty2024, and R2C-18's six required_evidence aliases.
    • VSI, VEX and VPT join required_scenarios in reported_cell_support (:352) and reported_cell_support_target_a (:443).
    • The income family lists stay unchanged.
  • N/country_package.json:116 registers income_measurements.json. The NZ golden moves with these resources.
  • R2C-20. nz_super_shares_residence_with_person_at_least_adult_age keeps its default, and neither closure entry nor NZ's concept mapping changes. H1 and H2 carry the binding.

Invariants and tests

All in test_nz_transport_declarations.py unless named. "Red under" names the mutant I ran this session (details under "Mutants").

Item Invariant Test Mutant → red
1 Both declarations validate under C, carry their own canonical SHA-256, floor 0 and scale 100, rental add-back, and the three outputs; VSI omits exactly the four transfers test_the_measure_declarations_are_c_shaped_with_r2c10_floor_and_scale (:1254) vsi_keeps_nzs and central_floor_1 (each with its SHA-256 recomputed)
1 VSI = market sum + max(0, −rental); central − VSI = Σ the four transfers; status is included exactly when x > 0 (Hypothesis, 150 examples, through combine_measure) test_property_vsi_is_the_central_measure_less_its_four_transfers (:1617, added) vsi_keeps_nzs
1 The omission register gives no unsourced size test_the_omission_register_gives_no_unsourced_size (:1293) register_size (ACC set to "1.0653%")
2 Every calibration branch has one stand-in between the annual transfers and prepare; its params select its own measure; the problem slices the proxy cents, the NZD unit, the universe, source_household_id and every income row's measure; VEX's exposure is the text "0.75" test_every_calibration_branch_has_one_standin_between_transfers_and_prepare (:1305) a3fe1952 revert; vex_exposure_1 (exposure set back to 1)
2 A's proof names nz.income.standin as the only producer for nz.calibrate test_a_proves_nz_income_standin_the_measurements_only_producer (:1390) no_measurement_id
2 A refuses a second declared producer; without the stand-in, the 24 rows refuse activation by name test_a_refuses_a_second_declared_producer_upstream_of_calibration (:1401), test_without_the_standin_the_24_rows_refuse_activation_by_name (:1413) each test builds its own mutant
3 nz.precal.nz_super lists its two rules outputs and its derived cents output, and no stage lists the stand-in test_the_nz_super_stage_lists_its_rules_outputs_for_r2c2 (:1426) no_super_stage
3 The S5 guard refuses a reader of a non-public nz_super column, both a rules output (:1564, added, parametrized) and the derived output (:1446) both tests s5_guard_ignores_unlisted (the guard's "undeclared pre-calibration stage output" check deleted in T/compose.py, worktree only): all three red
5 VSI, VEX and VPT each change only their own knob; the domains and the declared-judgment disclosure hold test_vsi_vex_and_vpt_change_only_their_own_knob (:1370) —
6 measurement_prefit carries R2C-3's numbers and the six aliases test_measurement_prefit_carries_r2c3s_frozen_numbers_and_six_aliases (:1461) kish_29
6 No alias reaches nz.gates.terminal (R2C-18) test_r2c18_wires_no_placeholder_evidence_into_the_terminal_battery (:1505) placeholder_alias
6 Both cell-support gates require VSI, VEX and VPT; the income family lists hold test_cell_support_gates_require_the_three_new_variants_and_income_lists_hold (:1518), test_transport_weight_quality.py:499 target_a_drops_vpt: both red
6, 7 The coarse table has 12 bands with both companions and no nil band test_the_prefit_table_has_12_bands_each_with_both_companions (:1491) coarse_nil_present
7 Every prepared table on C's columns has no nil band; the declared-only national and age tables keep IRD's test_every_prepared_table_on_the_standins_columns_has_no_nil_band (:1578, added) coarse_nil_present
8 VSI's triggers and the wage-earner count stay diagnostic and stay out of gates.json test_vsi_review_triggers_and_the_wage_earner_count_stay_diagnostic (:1543) no_wage_earner_diag

Mutants. I ran each mutant in a separate worktree with its own environment, at G 423ca43a with the added tests copied in, and selected the named tests. Each mutant turned its test red, so none survived. An earlier batch ran two series concurrently in that worktree by mistake, so I discarded it and reran every mutant in one clean series. super_cents_no_engine_ref (dropping engine_ref and period from nz.gross_up.super_cents) ran only in the discarded batch, where it turned test_the_s5_guard_refuses_an_undeclared_nz_super_derived_output red. I didn't rerun it.

a3fe1952: the problem slices the amount rows' measure

  • The refusal it prevents. The 12 coarse amount rows measure ird_taxable_income_proxy_nzd. _compiled_problem (T/target_kernels.py:541-551) refuses any target whose measure its node doesn't slice ("every reference must name columns the node slices"). At 8c66267e, no targets.problem node sliced that column, so a real run would have refused all 12 amount rows.
  • Files it changes: tools/build_nz_transport_graph.py:816-819, N/transport_graph.json (the central problem's person inputs), N/scenarios.json (each variant's problem), the golden nz_country_spec.json, and test_nz_transport_declarations.py (test_every_calibration_branch_has_one_standin_between_transfers_and_prepare, the _PROXY[1] line and the income-row measure check).
  • Its mutant run. In a separate worktree I reverted tools/build_nz_transport_graph.py, N/transport_graph.json and N/scenarios.json to 8c66267e and kept the test. test_every_calibration_branch_has_one_standin_between_transfers_and_prepare went red; with the files restored, it passes.

Shared-test lines at G (for Belgium's hub)

Ruling Line Edit
R2C-19 item 1 / R2C-23 test_spec_engine_country_bundles.py:252 earnings_bands.json moved to its sorted place
R2C-23 test_spec_engine_country_bundles.py:264 income_measurements.json added
R2C-24 line 1 test_transport_weight_quality.py:499 43 * 10 → 43 * 13
R2C-24 line 2, amended by R2C-25 test_transport_evidence_kernels.py:2706 "derived": 307 → "derived": 322 (423ca43a)
R2C-22 (from F) test_reference_profiles.py:1163-1187, test_transport_composed_graph.py:2424-2441 as #1476 lists

R2C-21. git diff <E head> <G head> -- test_transport_composed_graph.py touches only R2C-22's two hunks (@@ -2424,7 +2424,8 and @@ -2433,2 +2434,2). _GAPS and test_packaged_spec_refuses_with_every_activation_gap_named match E's text, and the test passes at G.

The 15 derived rows the stand-in adds (the roster reads 322 = E's 307 + 15; every one still misses exactly household.rent and person.liquid_financial_assets): person.ird_taxable_income_proxy_cents, person.ird_taxable_income_proxy_nzd, person.ird_taxable_income_proxy_universe_status, and person.ird_taxable_income_standin_band_b01_indicator through …_b12_indicator. The NZD unit column joins as derived because the problem now slices it (a3fe1952).

pending_outputs after G

N/transport_graph.json:4980 lists two entries:

  1. "G8-x income reconciliation evidence: the native-band report, omission-register and identity-receipt artifacts and the VSI, VEX and VPT run evidence that measurement_prefit requires (R2C-18), and the NZS sharing binding (R2C-5)".
  2. "Census rent and liquid-assets quantile producers" (unchanged from main).

Hashes

I hashed all 42 engine-free transport kernels (implementation_hash()[:16], no simulate.*) in their own trees, from the NZ toy fixture's registry: once at int 3e5f5edb and once at G 423ca43a. The two tables match exactly, so no kernel hash moves. G changes only declarations and tests, and its later commits touch tests only. The values also match #1476's table column by column.

Kernel int 3e5f5edb G 423ca43a
calibrate.ordered_adam@1 0d4577effa147ebe 0d4577effa147ebe
calibration.prepare@1 180823901b9a0d99 180823901b9a0d99
concepts.encode@1 7815fae8ed573af6 7815fae8ed573af6
concepts.encode_groups@1 062f88411ad605ab 062f88411ad605ab
diagnostics.calibration@1 30654dfc7038f560 30654dfc7038f560
export.prepare@1 af31fa6e1690b90c af31fa6e1690b90c
export.readback@1 96be393c760ca768 96be393c760ca768
gates.battery@1 ac050bab09f48dc2 ac050bab09f48dc2
geography.assign_atomic@1 d60f7ec388903678 d60f7ec388903678
geography.derive@1 d60f7ec388903678 d60f7ec388903678
geography.gate@1 d60f7ec388903678 d60f7ec388903678
geography.person_location@1 3efcbe3741d04497 3efcbe3741d04497
geography.support_from_facts@1 ad0a8e73d20405be ad0a8e73d20405be
geography.support_import@1 d60f7ec388903678 d60f7ec388903678
measure.combine@1 d609832882ffc113 d609832882ffc113
money.annual_transfers@1 c8a067cd750ed2fe c8a067cd750ed2fe
money.minor_units@1 c8a067cd750ed2fe c8a067cd750ed2fe
money.normalize@1 f10fbeeaf87467b6 f10fbeeaf87467b6
reporting.carry_income_decile@1 80335f0183c27410 80335f0183c27410
reporting.income_decile@1 80335f0183c27410 80335f0183c27410
reporting.receipt_group@1 80335f0183c27410 80335f0183c27410
takeup.assign@1 b2a3a50cc376108b b2a3a50cc376108b
takeup.bands@1 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92
takeup.cell_diagnostics@1 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92
takeup.compare@1 1605d7e8519f4b0f 1605d7e8519f4b0f
takeup.gap@1 54f79560d896fab4 54f79560d896fab4
targets.compile@1 dc91d1479f8f84b1 dc91d1479f8f84b1
targets.problem@1 fe401c736551e351 fe401c736551e351
transport.boundary@1 fc9d10670f6a092f fc9d10670f6a092f
transport.create@1 865c21926cee0ac0 865c21926cee0ac0
transport.create@2 865c21926cee0ac0 865c21926cee0ac0
transport.currency@1 9c3457ddb908aa96 9c3457ddb908aa96
transport.donor_ranges@1 62def362139a52e4 62def362139a52e4
transport.donor_receipt@1 62def362139a52e4 62def362139a52e4
transport.input_closure@1 23134112061495b7 23134112061495b7
transport.package@1 e86be552287a6eb3 e86be552287a6eb3
transport.quantile_map@1 b25bb6538b19dd64 b25bb6538b19dd64
transport.quantile_map@2 76c81605aa416dc9 76c81605aa416dc9
transport.scenario_override@1 062f88411ad605ab 062f88411ad605ab
transport.support_bounds@1 2303c60a7ec6135c 2303c60a7ec6135c
transport.unit_attributes@1 f50ec74a1bfe35bb f50ec74a1bfe35bb
transport.weights_audit@1 1f3d475013456127 1f3d475013456127

Golden

I regenerated tests/golden/nz_country_spec.json at G with its own summary (test_country_spec.py's _loaded_spec_summary("nz") through canonical_json_bytes), and it comes out byte-identical to the committed file. Against F, the golden moves only through G's resources:

  • the fingerprint;
  • measurement_prefit in gate_ids;
  • income_measurements.json among the resources and their hashes;
  • the new hashes of benefit_gross_up.json, calibration_bindings.json, country_package.json, gates.json, precalibration_stages.json, reporting_classifiers.json, scenarios.json and transport_graph.json.

Tests

The closed list ran at G 423ca43a, with no --maxfail and -n 4, with NZ_CHRONICLE_ARTIFACT set to the artifact I built from Chronicle 8f4cfd57 under Python 3.13. Both digests match the pin (facts_sha256 03f5ea0f…, manifest_sha256 1ea2d1ee…). The result is 4383 passed, 18 skipped, 0 failed. G's final head 64ef20af changes only test_nz_transport_declarations.py, and that file passes there with 60 passed (56 + the 4 added tests). The other 57 files are byte-identical between the two heads. Counts read passed / skipped.

File G 423ca43a
frame/test_transport_donor.py 94 / 2s
frame/test_transport_quantile.py 79
test_country_spec.py 143
test_nz_as_area_crosswalk.py 67 / 3s
test_nz_as_bridge.py 34
test_nz_axiom_input_closure.py 19 / 2s
test_nz_benefit_gross_up.py 16
test_nz_earnings_bands.py 4
test_nz_family_reconciliation.py 16
test_nz_joint_geography.py 22 / 11s
test_nz_money_normalization.py 56
test_nz_msd_benefit_rates.py 6
test_nz_msd_pre_activation_reconciliation.py 5
test_nz_reference_aging.py 22
test_nz_reference_intervals.py 16
test_nz_spec_package.py 1203
test_nz_target_references.py 60
test_nz_transport_declarations.py 56
test_nz_transport_toy_graph.py 25
test_reference_interval_support.py 12
test_reference_intervals.py 107
test_reference_profiles.py 129
test_reference_summed_bands.py 23
test_spec_engine_country_bundles.py 22
test_spec_only_country_packages.py 37
test_transport_annual_transfers.py 75
test_transport_binding_identity.py 32
test_transport_bridge_differential.py 37
test_transport_codecs_geography.py 22
test_transport_column_kernels.py 31
test_transport_composed_graph.py 469
test_transport_declared_units.py 18
test_transport_dependent_children.py 24
test_transport_entitlement_graph.py 67
test_transport_evidence_kernels.py 151
test_transport_gate_bindings.py 131
test_transport_gate_kernels.py 31
test_transport_graph.py 15
test_transport_grossup_selectors.py 26
test_transport_measure_kernels.py 37
test_transport_measurement_prefit.py 47
test_transport_measurement_proof.py 33
test_transport_money_kernels.py 40
test_transport_population_kernels.py 32
test_transport_quantile_map_v2.py 38
test_transport_receipt_v2.py 117
test_transport_receipt_v2_kernel.py 14
test_transport_reference_selection.py 60
test_transport_registry.py 59
test_transport_reporting_kernels.py 31
test_transport_run_entry_factors.py 75
test_transport_run_entry_forms.py 229
test_transport_source_household_lineage.py 8
test_transport_takeup_kernels.py 38
test_transport_target_kernels.py 68
test_transport_terminal_kernels.py 18
test_transport_weight_quality.py 30
uk/test_transport_gate_bindings.py 7
Total 4383 / 18s / 0 failed

Skips (18), each for a source this container lacks: test_nz_joint_geography.py 11 (NZ_AS_AREA_SOURCES and NZ_JOINT_GEOGRAPHY_SOURCES unset); test_nz_as_area_crosswalk.py 3 (pinned ArcGIS bytes); test_nz_axiom_input_closure.py 2 (Axiom dense engine and a rulespec-nz checkout); frame/test_transport_donor.py 2 (pinned real donor not cached). Every feed audit ran.

Also at G: ci_test_plan.py verify prints verification=ok. --check exits 0 for build_nz_transport_graph.py, build_nz_entitlement_graph.py, build_nz_benefit_gross_up.py, generate_nz_target_references.py (with the artifact), generate_nz_msd_pre_activation_reconciliation.py and python -m tools.generate_nz_reference_aging. ruff check and format are clean on the changed test file. I didn't rerun E's or F's closed lists, for lack of time; #1475 and #1476 report them.

Left for the hub

  • Toy-graph tests (design §6 G; item 6's toy assertion). Brief 194's session didn't touch test_support/microcosm_build/transport_nz_toy.py, and I didn't either, for lack of time. So no toy test yet shows the 24 rows reaching the matrix, target_profile_coverage passing on income, measurement_prefit running with only unwired-alias failures, or the differential (the toy problem's income row equal to the summed coarse reference). Those tests need a toy stand-in node, a toy coarse table and toy summed references. All of that sits in scope, but it's real work. F's test_coarse_amounts_equal_the_aged_sum_of_their_native_rows gives the compile-side differential on the packaged feed only.
  • Item 6, VSI's triggers. They sit in income_measurements.json review_triggers, not in gates.json. A gates.json entry must name a gate in ALLOWED_GATE_FUNCTIONS (B/country_spec.py:115), and no gate kind computes these triggers, so a diagnostic gate entry would need a shared change. The wage-earner count (item 8) sits beside them under diagnostics. G8-x reports both.
  • Item 7, mass at and below zero. C's receipt records below_zero and at_zero (T/measure_kernels.py:264-265). No G artifact surfaces those figures yet, and G8-x's native-band report owes them. The no-nil-band rule now has a test (:1578).
  • R2C-20. The NZS sharing default stays. H1 and H2 carry the binding.
  • R2C-18. All six aliases stay unwired. On a real run, measurement_prefit refuses as evidence: the run lacks required evidence … until G8-x.

https://claude.ai/code/session_01LwQq6J45cyZzJWT9gGBLYB

🤖 Generated with Claude Code

claude added 18 commits November 2, 2026 22:31
The generator adds the TY2026 wage interval table
ird_wage_salary_national_ty2026 (rows 7-241, right-closed, no nil band,
negative wages refused, open tail over $1,000,000) from the pinned feed's
labels, and activates the 235 P1 precal rows, which drops their
exemptions. The transport builder writes earnings_bands.json: for each
aging_index profile, every edge is cut_cents x P1 factor / 100, computed
in Fraction and rounded once to binary64, with the factor id, its
receipt SHA-256 and the wage table's SHA-256. nz.earnings.map
(transport.quantile_map@2, grouped_pareto_mle@1 on rows 231-241) rewrites
person.employment_income beside nz.money.normalize, and nz.earnings.cents
writes employment_income_cents half-even, both before nz.encode.person.
Each calibration variant repeats them with its own profile (V1 low, V2
high). R2C-6 adds person.employment_income to the export contract and
both slices.

Three wage labels repeat the previous upper endpoint ($20,000, $50,000,
$80,000: rows 27, 57, 87); the generator pins them and the right-closed
cuts give each endpoint to the lower band.

Invariants: every edge equals cut x factor exactly for every profile
(example test, plus a builder --check that refuses a hand-edited edge,
a stale factor receipt, a stale table and a changed tail); the edges
stay contiguous and increasing for any positive factor (Hypothesis); the
scaled-edge map equals the unscaled map x f within 2^-49 relative
(measured at most 4.3e-16); the toy's @2 support row is quantile_mapped
with 0 records outside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
test_nz_spec_package pins the active row count per reference set. G8-r2c
E activates the 235 wage rows, so precal's active count moves from 80 to
315. The calibration and hold-out counts stay as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The generator adds the coarse table ird_taxable_income_standin_ty2024
(person, right-closed, no nil band, floor_zero, first lower cut $40,000,
cuts at $50k to $1m and an open tail: 12 bands) with count and amount
companions on every band and per-band source-household support
(support_household_entity: household), which measurement_prefit needs.
It writes 24 rows in A's summed form: 12 count rows with
value_operation sum and no operands, 12 identity amount rows measuring
ird_taxable_income_proxy_nzd (R2C-10, R2C-17), each pinning its band's
native labels and expected_member_count. The rows keep the prerequisites
reference_profile:aging_index and measurement:ird_taxable_income_ty2027_v1,
stay requires_prerequisites and are not exempt. The calibration
document allows sum.

R2C-4 moves the 474 native rows to the non-executable
income_diagnostic_references.json (key diagnostic_references) and drops
their exemptions. The national table loses its companions and becomes
declared and validated only. The aging_index transform now scales the 24
rows and the coarse table.

Invariants: each coarse band is the sum of whole native rows 50-244
(generator check, a feed-gated label audit and a differential that
compiles each aged coarse row against its native rows' aged sum); the
central first cut is exactly 4,468,436 cents; no native row sits in an
executable document; the rows stay parked and unexempt without a
producer; the scaled coarse table tiles the positive range for any
positive factor (Hypothesis, through scale_interval_table and
classify_records). The NZ composition tests add a compile-only
measurement producer, as they already add synthetic money producers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The hub's R2C-19 lifts the freeze for these tests on E. Each edit follows
from what E changes in NZ's own resources:

- test_spec_engine_country_bundles: NZ's legacy resources include
  earnings_bands.json.
- test_transport_composed_graph: _GAPS no longer names the
  person.employment_income quantile-map gap, which nz.earnings.map closes.
- test_transport_evidence_kernels: the roster gains one quantile_mapped
  row (R2C-6), and derived rows miss only rent and liquid assets; the
  per-row assertion over every derived row stays.
- test_transport_quantile_map_v2: _spec_with_map first removes the
  packaged nz.central producer of employment_income, so the kernel under
  test stands alone; that spec without a map still reports a central
  wage gap; the packaged spec reports none. Both accepted kernels and the
  transport.currency@1 refusal stay.

Mutant run: with _money_producer_gaps accepting any kernel,
test_composition_accepts_v2_as_the_quantile_mapped_money_producer goes
red (the currency@1 refusal finds no gap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The hub's R2C-19 item 1 lifts the freeze on
test_nz_generation_zero_views_come_from_the_country_spec_seam for F: NZ's
legacy resources now include income_diagnostic_references.json, the
non-executable home of the 474 native IRD rows (R2C-4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
test_nz_transport_declarations memoizes resolved reference views by the
content of the documents the resolver reads. A's measurement proof now
reads the transport graph too, so a view resolved for a graph without a
measurement producer was reused for a graph with one, and the composition
tests saw "no proof". The cache key now includes the graph.

Checked: the full file passes in one process after the change; before
it, the five composition tests failed only when an earlier test in the
same process had cached a producer-less view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
R2C-4 moves the 474 native rows out of the executable documents, so
the aging_index profiles scale the coarse stand-in table, not the
national one. The hub's R2C-22 lifts the freeze for:

- test_reference_profiles
  test_nz_default_factor_receipt_is_compact_and_covers_the_exact_roster:
  the receipt names ird_taxable_income_standin_ty2024 (12 bands) and 24
  companions, whose source carries A's expected_member_count.
- test_transport_composed_graph
  test_declared_scenarios_scale_national_cuts_with_their_own_factor: the
  per-scenario cut check moves to the coarse table (local name coarse),
  and the national and age tables must stay unscaled.

Mutants: a resolver that also scales the national table turns both
tests red (the second at tables[unscaled] == by_id[unscaled]); giving
the low profile the central factor id turns the second test red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
- income_measurements.json (written by the transport builder) holds C's
  measure.combine@1 declaration for ird_taxable_income_ty2027_v1 from
  design 80's component table: person, floor 0, scale 100 (R2C-10),
  rental income as the add-back, outputs ird_taxable_income_proxy_cents,
  _nzd and _universe_status. A VSI sibling drops the four transfer
  addends. The document also holds the omission register (every size
  "unknown": the design's percentages have no repo source), VSI's review
  triggers and the R2C-8 wage-earner diagnostic, none of them gates.
- nz.income.standin (measure.combine@1, measurement_id
  ird_taxable_income_ty2027_v1) runs after nz.benefits.annual_transfers
  and before nz.targets.prepare on every calibration branch. A's proof
  names it as the measurement's only producer for nz.calibrate, which
  lifts the 24 coarse rows' measurement prerequisite.
- calibration_bindings.json binds the coarse table's 12 band indicators
  by interval; prepare reads the measure and universe columns, and the
  problem reads them plus household.source_household_id, which
  targets.problem@1's per-band support needs (target_kernels.py:994-1002,
  reference_intervals.py:1137-1160).
- R2C-2: the S5 stage nz.precal.nz_super lists NZS eligibility and the
  before-tax weekly amount, with the cents as a derived output;
  nz.gross_up.super_cents names the nz_super engine_ref and period.
- Scenarios VSI (income_measure vsi), VEX (exposure.central "0.75", a
  declared judgment, R2C-7) and VPT (earnings_tail_anchor vpt) with
  their knob domains; both cell-support gates require them, and the
  structural-zero superannuitant cell lists them.
- gates.json adds measurement_prefit with R2C-3's frozen numbers and
  R2C-18's six evidence aliases, none wired, so the gate refuses for
  missing evidence until G8-x.

R2C-20: the NZS sharing input keeps its default; the binding needs a new
transform kind, which the hub carries as H1 and H2.

Invariants: A's proof finds exactly nz.income.standin and refuses a
second declaring producer; without the stand-in the 24 rows refuse
activation by name; VSI omits exactly the four transfer columns; each
declaration's digest is its canonical text's; the prefit entry equals
R2C3_PREFIT_THRESHOLDS; the S5 guard refuses a reader of a non-public
nz_super column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The hub's R2C-23 extends R2C-19 item 1 to G:
test_nz_generation_zero_views_come_from_the_country_spec_seam lists
income_measurements.json, the new home of C's declaration. As R2C-23
asks, E's earnings_bands.json moves to its sorted place between
currency_bridge.json and entitlement_graph.json; F's and G's lines sit
in theirs. The test passes (22 of 22).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
G's closed list showed NZ resources and tests that pin the scenario set
or the pre-G state:

- reporting_classifiers.json's baseline_cell_contract lists VSI, VEX and
  VPT, so every scenario has a baseline cell contract.
- VEX's knob is the number 0.75 (central 1), as NZ's scenario contract
  wants for a non-list domain; the builder writes it into the
  annual-transfers declaration as the exact decimal text "0.75".
- NZ tests: the scenario list and structural-zero cells include the
  three variants; the active-or-exempt check accepts the coarse rows as
  active once A's proof holds; the family-reconciliation check prepares
  without G's person interval outputs; the MSD rate check strips
  super_cents' S5 provenance selections, which name the stage and not
  the rounding, because the shared helper prepares only two bindings.

Checked: the 59 tests that failed or touch these lines pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The hub's R2C-24 lifts the freeze for two lines on G:

- test_transport_weight_quality
  test_adopted_cell_targets_are_flagged_and_never_block: 43 * 10 becomes
  43 * 13, because both cell-support gates now require 13 scenarios
  (main's 10 plus VSI, VEX and VPT).
- test_transport_evidence_kernels test_the_nz_roster_classes: derived
  307 becomes 321. G adds 14 derived rows: the stand-in's measure and
  universe columns and the 12 coarse band indicators. The per-row
  assertion stands; all 321 rows miss exactly rent and liquid assets.

Both tests pass at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
The 12 coarse amount rows measure ird_taxable_income_proxy_nzd, and
targets.problem@1 refuses any target whose measure its node doesn't
slice (T/target_kernels.py _compiled_problem: "every reference must name
columns the node slices"). G wired the measure and universe columns and
the source households but not this one, so a real run would have
refused the 12 amount rows. Every calibration branch's problem now reads
it.

The test now checks that the problem slices every income row's measure.
With the builder and resources reverted it goes red; with them it
passes.

This adds one derived row to the support roster (322), so
test_the_nz_roster_classes' R2C-24 count of 321 goes red; the hub
rules on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fwj4yUEPsD3wfNstU
a3fe195 adds person.ird_taxable_income_proxy_nzd to the problem
inputs of every calibration branch, so the unit column joins the
support roster as a derived row. R2C-25 amends R2C-24's second line
in test_the_nz_roster_classes from 321 to 322. The per-row assertion
stays as it was: every derived row still misses exactly household.rent
and person.liquid_financial_assets. I ran the test at this head and it
passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6J45cyZzJWT9gGBLYB
claude and others added 7 commits November 3, 2026 08:15
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>
The E-G review's fix 3. Only the Chronicle feed, which CI doesn't load,
checked R2C-16's membership, so a coarse row that lost a member label
passed every feed-free test. The new test reads the committed
income_diagnostic_references.json: each coarse row's labels equal, in
order, the labels of the native rows its metadata.native_rows names, its
selector otherwise equals theirs, and each measure's rows tile 50 to 244
once.

Without the feed, the review's b05 member drop turns this test red and no
other in test_nz_target_references.py, test_nz_reference_intervals.py or
test_nz_spec_package.py (1 failed, 1237 passed, 42 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The E-G review's fix 4. Only the feed checked the wage table's cents
against their labels, so a $1 change to one upper_cent passed every
feed-free test, even after the builder rewrote earnings_bands.json to
match. The new test reads each band's committed label as the right-closed
band (a - 1, b] dollars, so the cut below it is 100(a - 1) cents and its
upper_cent is 100b. A label whose lower endpoint repeats the previous
band's upper one starts at that cut, and the test pins those rows to 27,
57 and 87.

Without the feed, the review's row_107 mutant turns this test red and no
other in test_nz_transport_declarations.py or test_nz_earnings_bands.py
(1 failed, 63 passed, 2 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Belgium's hub asked for this rename when it cleared E to G's shared test
lines: since F, the test checks the coarse stand-in table, not the
national one. Only the name changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stack last met main at D's head (91bf4c0). Since then main has landed
eleven PRs whose content the stack lacks: #1432, #1442, #1410, #1428,
#1051, #1444, #1471, #1479, #1472, #1477 and #1463. #1472 brings the
holdout test whose pinned NZ stage set the next commit extends (R2C-26).
git merge-tree resolves the merge without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G's nz.precal.nz_super (R2C-2) adds the nz_super binding to NZ's
precalibration stages. At main + G, this test failed on its pinned set
alone, and the stage guard itself accepts the stage:

  test_packaged_precalibration_stages_pass_the_stage_guard[nz]

Hub ruling R2C-26 lifts the freeze for this one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G's 714e10d adds twelve stand-in income band outputs to NZ's
calibration_bindings.json, each an "interval" entry. The tests in
test_calibration_columns.py call prepare_calibration_columns on that
file without the effective interval document, so the check at
packages/microcosm-build/src/microcosm/build/calibration_columns.py:106-107
refuses them with "Interval outputs need the effective interval
document." Brief 214 counted 17 such failures at 891cb9f. The prepare
kernel passes the document
(packages/microcosm-build/src/microcosm/build/transport/target_kernels.py:679);
these tests call the function directly.

_bindings() now drops the interval entries, and a new test pins the
refusal: NZ's packaged bindings declare interval outputs, and
prepare_calibration_columns refuses them without the document. The
file's interval tests still cover interval outputs on their own
fixture. With the fix the file gives 106 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis changed the base branch from nzhub/g8r2c-f-coarse-refs to main November 3, 2026 21:10
@MaxGhenis MaxGhenis changed the title G8-r2c G: wire NZ's stand-in income measure, its variants and the pre-fit gate G8-r2c E-G: NZ's wage map, coarse income rows, stand-in measure, variants and pre-fit gate Nov 3, 2026
@MaxGhenis MaxGhenis closed this Nov 3, 2026
@MaxGhenis MaxGhenis reopened this Nov 3, 2026
@MaxGhenis MaxGhenis closed this Nov 3, 2026
@MaxGhenis MaxGhenis reopened this Nov 3, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review November 4, 2026 00:13
@MaxGhenis
MaxGhenis merged commit f8f543a into main Nov 4, 2026
32 of 40 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged by the NZ hub (session local_e407a527) at f43e8fb with --merge. Gates: gh pr checks exit 0; MERGEABLE; not draft; no CHANGES_REQUESTED review. Reviewed head: f43e8fb. Reviews: 213 v4 APPROVE_WITH_NITS at f43e8fb (#1183 comment 6615417145; its five test nits are #1501); Belgium cleared every shared line, fix rounds 1 and 2 included. CI run 41729696579 tested f43e8fb merged into main 79a533c (dcf8841): ten of ten passed.

MaxGhenis added a commit that referenced this pull request Nov 4, 2026
G landed on main as f8f543a, and this PR now targets main. After the retarget, GitHub's merge ref kept G's old branch head f43e8fb as its first parent, so this merge starts CI on main as it now stands. The merge writes the tree git merge-tree gives for main and b64eb6c, 57481e8, where the measurement-proof, NZ toy-graph, calibration-column, evidence-kernel and composed-graph test files all passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis deleted the nzhub/g8r2c-g-wiring branch November 4, 2026 00:28
MaxGhenis added a commit that referenced this pull request Nov 4, 2026
G landed on main as f8f543a after this PR's last CI run, which tested 8a4e7a4 merged into main 79a533c. GitHub's merge ref still has 79a533c as its first parent, so this merge starts CI on main as it now stands. G and this PR change no file in common, and git merge-tree writes tree 5c0a6b9 with no conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 4, 2026
main moved from this PR's base, 4a36edc, through G (#1478, f8f543a) to 875506e. The merge has no conflict: git merge-tree writes tree 70ca7aa.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 4, 2026
…1488)

* Prove extension calibration nodes' measurement producers in compose

The extension pass of compose_transport_graph compiled the extended graph
and reran the ancestry guard, but never ran _check_measurement_proofs. So a
calibration-tier scenario's calibrate.* node could calibrate against a
money-scaled measurement that no proof covered: measurement_proofs reads
only the declared graph's rows and never names those nodes.

The extension pass now runs the check after the ancestry guard, as the
skeleton pass does. For a calibration node that an extension adds, the
compiled graph proves the producer under R2C-1's rule: exactly one compiled
ancestor declares the measurement, and that ancestor outputs every proof
column. R2C-15's only-writer rule applies as before. A skeleton calibration
node keeps the declared comparison, which the pass now also runs against
the extended graph.

Tests: unit cases on the compiled toy; a differential property that the
declared proof and the compiled extension proof agree, refusal for refusal,
on chains of declarers and writers; and NZ's synthetic graph with four V3
mutants (M1, M2, M4 and the NZ hub's G7), each of which composes without
this change.

* Move the NZ-toy measurement-proof tests into NZ's toy-graph file

The five tests that run the extension proof on NZ's synthetic graph name the
toy's V3 nodes, and the NZ hub owns the toy and its node ids. They move, with
their helpers, from test_transport_measurement_proof.py into
test_nz_transport_toy_graph.py and use that file's module `toy` fixture, so a
change to the toy edits only NZ's files. The shared file keeps the unit tests
on the compiled toy and the differential property.

The move adds G7 in the NZ hub's placement. V3's calibration keeps its base on
V3, while preparation, the compiled targets and the problem move into the
review version that rewrites both measured columns. The problem reads the
rewritten columns, so only the calibration's artifact edge from the problem
puts the rewrite among its compiled ancestors. The earlier G7 opens the
calibration from the review version, where the rewrite joins its base, and
doesn't need that edge.

* Refuse a malformed measurement_id in the compiled proof check

Review r2 found that the compiled check skipped a node whose
measurement_id was neither None nor a nonempty string, while the declared
check refuses it. With the string producer still in place, an extra
upstream node declaring measurement_id 7 composed, though the same param on
a declared row parks every measurement. r2 showed it on NZ's toy graph,
where V3's earnings.cents with id 7 composed.

_check_measurement_proofs now refuses such a node with the declared
check's reason before it proves any producer, and reads an explicit None as
no declaration, as measurement_proofs does.

The new test gives a node upstream of calibration a measurement_id of 7, ""
or None, and runs the declared check and the compiled check on a
calibration node that an extension adds and on a skeleton one. Both checks
refuse 7 and "" with the same reason, and both ignore None. Without the
fix, the compiled check accepts the four 7 and "" cases, and those cases
fail.

The commit also fixes three docstrings that r2 flagged: the test module's
account of the compiled tests, which patch in a proof; _chain's, whose
nodes chain by step columns; and compose's module docstring, which now
names gate_inputs, the other explicit way an extension may add a skeleton
predecessor.

* Say the compiled proof check scans malformed ids only under a proof

_check_measurement_proofs returns before its malformed-id scan when the declaration proves no measurement, and its docstring now says so. The independent review of b64eb6c noted that the sentence read as unconditional.

Two merges of main bring G (#1478) and then H2 (#1485) and #1501. The second
keeps main's test_nz_transport_toy_graph.py whole and appends this PR's
section.

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