From 9ae0914c0529c21df1deb92d7b4984a3afba5f88 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 08:00:56 +0000 Subject: [PATCH 1/3] Test G's income stand-in on NZ's toy graph 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 Claude-Session: https://claude.ai/code/session_01LwQq6FpF7dJcTwxa1pX54W --- .../shared/test_nz_transport_toy_graph.py | 270 +++++++++++++- .../microcosm_build/transport_nz_toy.py | 343 ++++++++++++++++++ 2 files changed, 611 insertions(+), 2 deletions(-) diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py index 640bca1ff5..5bfe68a94b 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py @@ -19,6 +19,7 @@ from microcosm.build.transport.cli import through from microcosm.build.transport.compose import ( compose_transport_graph, + transport_resource, validate_transport_activation, ) from microcosm.build.transport.reporting_kernels import ( @@ -51,6 +52,7 @@ from test_support.microcosm_build.transport_nz_toy import ( DEMOGRAPHY, SCENARIOS, + TOY_STANDIN_MEMBERS, _cell_gates, _classifiers, make_nz_toy_fixture, @@ -237,9 +239,13 @@ def test_variants_normalize_once_with_their_knobs_and_hold_fixed_uses_identity(t def test_each_activated_calibration_row_compiles_once_and_exempt_rows_stay_out(toy): fixture, run = toy + # The effective view, after the ageing profile and the measurement proof + # activate the six parked stand-in rows. expected = { row["name"] - for row in fixture.spec["resources"]["target_references"]["target_references"] + for row in transport_resource(fixture.spec, "target_references")[ + "target_references" + ] if row["metadata"]["activation_status"] == "active" } surface = decode_target_surface(_artifact(run, "nz.targets.compile", "surface")) @@ -477,7 +483,10 @@ def test_four_regional_age_cells_prepare_and_calibrate_once(toy): frame = run[0].populations["nz.central"] names = [name + "_indicator" for name, _, _, _ in DEMOGRAPHY] assert frame.table("person")[names].sum(axis=1).eq(1).all() - assert run[0].nodes["nz.targets.problem"].receipt["n_targets"] == 4 + # The four cells plus the stand-in's count and amount row per coarse band. + assert run[0].nodes["nz.targets.problem"].receipt["n_targets"] == 4 + 2 * len( + TOY_STANDIN_MEMBERS + ) rows = fixture.spec["resources"]["target_references"]["target_references"] assert { row["measure"] @@ -739,3 +748,260 @@ def test_the_wage_map_v2_fits_the_toy_alpha_on_scaled_edges(toy): assert fit["alpha_decimal"] == "2" assert fit["top_band_lower"] == "500" == str(int(400 * TOY_EARNINGS_FACTOR)) assert [anchor["count"] for anchor in fit["anchors"]] == [3.0, 1.0] + + +# --------------------------------------------------------------------------- +# G8-r2c G on the toy: the income stand-in, its coarse rows and its gates +# --------------------------------------------------------------------------- + + +def _toy_problem(run): + from microcosm.calibrate.artifacts import decode_problem + + return decode_problem(_artifact(run, "nz.targets.problem", "problem")) + + +def _gate_row(run, gate_id): + report = json.loads(_artifact(run, "nz.gates.terminal", "gate_report")) + (row,) = [row for row in report["report"]["outcomes"] if row["id"] == gate_id] + return row + + +def _standin_bands(fixture): + from test_support.microcosm_build.transport_nz_toy import TOY_STANDIN_TABLE + + (table,) = [ + table + for table in transport_resource(fixture.spec, "toy_reference_intervals")[ + "tables" + ] + if table["id"] == TOY_STANDIN_TABLE + ] + return table, [ + (band, members) + for band, members in zip(table["bands"], TOY_STANDIN_MEMBERS, strict=True) + ] + + +def test_each_toy_coarse_row_enters_the_problem_matrix_once_with_support(toy): + """Design 80 section 6 G (1): the coarse income rows reach the toy matrix.""" + from test_support.microcosm_build.transport_nz_toy import ( + TOY_STANDIN_NODE, + TOY_STANDIN_OUTPUTS, + ) + + fixture, run = toy + _, bands = _standin_bands(fixture) + problem = _toy_problem(run) + # Matrix row names carry the period; target_metadata keeps the reference. + names = [row["reference"] for row in problem.target_metadata] + matrix = problem.problem.matrix.tocsr() + metadata = {row["reference"]: row for row in problem.target_metadata} + expected = { + band[role]: measure + for band, _ in bands + for role, measure in ( + ("count_reference", "toy_people"), + ("amount_reference", TOY_STANDIN_OUTPUTS["unit"]), + ) + } + assert len(expected) == 2 * len(TOY_STANDIN_MEMBERS) == 6 + for name, measure in expected.items(): + assert names.count(name) == 1, name + index = names.index(name) + assert matrix[[index], :].count_nonzero() > 0, name + assert metadata[name]["family"] == "income" + assert metadata[name]["measure"] == measure + # A's proof names the toy stand-in as nz.calibrate's only producer. It + # walks the transport skeleton (compose.py measurement_proofs), so the + # variant branches' calibrations sit outside it. + resolution = json.loads( + fixture.graph().node("nz.targets.compile").params["reference_resolution"] + ) + (proof,) = resolution["measurement_proofs"].values() + assert proof["producers"] == {"nz.calibrate": TOY_STANDIN_NODE} + + +def test_target_profile_coverage_passes_on_the_toy_with_income_required(toy): + """Design 80 section 6 G (2).""" + fixture, run = toy + (entry,) = [ + row + for row in fixture.spec["resources"]["gates"]["gates"] + if row["id"] == "toy_target_profile_coverage" + ] + assert entry["gate"] == "target_profile_coverage" + assert "income" in entry["parameters"]["required_families"] + row = _gate_row(run, "toy_target_profile_coverage") + assert row["status"] == "passed", row["failures"] + + +def test_measurement_prefit_fails_the_toy_only_on_its_unwired_evidence(toy): + """Design 80 section 6 G (3): R2C-18 leaves every alias unwired until G8-x. + + The gate runs on the toy's problem, and its only failures are one evidence + line per alias in the packaged gate's required_evidence; no roster, band, + support or diagnostics failure appears. + """ + from importlib.resources import files + + from test_support.microcosm_build.transport_nz_toy import ( + TOY_PREFIT_EVIDENCE, + TOY_STANDIN_TABLE, + ) + + fixture, run = toy + gates = json.loads((files("microcosm.build") / "nz" / "gates.json").read_text()) + (packaged,) = [ + gate for gate in gates["gates"] if gate["gate"] == "measurement_prefit" + ] + assert list(TOY_PREFIT_EVIDENCE) == packaged["parameters"]["required_evidence"] + row = _gate_row(run, "toy_measurement_prefit") + assert row["gate"] == "measurement_prefit" + assert row["status"] == "failed" + assert row["failures"] == [ + f"evidence: the run lacks required evidence {alias!r}." + for alias in TOY_PREFIT_EVIDENCE + ] + details = row["details"] + assert details["tables"] == [TOY_STANDIN_TABLE] + assert details["discrepant_bands"] == [] + assert [band["band"] for band in details["bands"]] == [ + f"{TOY_STANDIN_TABLE}/b{index}" for index in range(len(TOY_STANDIN_MEMBERS)) + ] + for band in details["bands"]: + for role in ("count", "amount"): + assert band[role]["inside_band_bounds"] is True, band + # A diagnostic entry: the refusal reaches the report, not the export. + report = json.loads(_artifact(run, "nz.gates.terminal", "gate_report")) + assert "toy_measurement_prefit" not in report["enforcement"]["blocking"] + + +def test_each_toy_coarse_reference_equals_the_sum_of_its_member_references(toy): + """Design 80 section 6 G (4), the differential. + + Each coarse count row's problem value is the sum of its declared members' + count facts in the pinned toy feed, and each amount row's is the selected + ageing factor times the sum of their amount facts. The members come from + the declared toy partition, not from the rows under test. + """ + from microcosm.build.ledger_artifact import load_ledger_consumer_artifact + from test_support.microcosm_build.transport_nz_toy import TOY_STANDIN_FACTS + + fixture, run = toy + resources = fixture.spec["resources"] + artifact = load_ledger_consumer_artifact( + fixture.sources["nz_calibration_facts"], + feed_pin=resources["toy_calibration_feed_pin"], + ) + native = {} + for fact in artifact.facts: + if fact.get("layout", {}).get("record_set_id") == "toy.standin.bands": + key = ( + fact["observed_measure"]["source_measure_id"], + fact["dimensions"]["band"], + ) + assert key not in native, key + native[key] = Fraction(str(fact["value"])) + assert native == { + (kind, label): Fraction(str(value)) + for label, pair in TOY_STANDIN_FACTS.items() + for kind, value in zip(("count", "money"), pair, strict=True) + } + (entry,) = resources["toy_reference_aging"]["factors"] + factor = Fraction( + entry["operational_factor"]["numerator"], + entry["operational_factor"]["denominator"], + ) + assert factor == Fraction(11, 10) + raw = { + row["name"]: row for row in resources["target_references"]["target_references"] + } + problem = _toy_problem(run) + targets = dict( + zip( + [row["reference"] for row in problem.target_metadata], + problem.problem.target_vector, + strict=True, + ) + ) + _, bands = _standin_bands(fixture) + for band, members in bands: + for role, kind, scale in ( + ("count_reference", "count", Fraction(1)), + ("amount_reference", "money", factor), + ): + name = band[role] + selector = raw[name]["ledger_selector"]["dimension_values"]["band"] + assert selector == list(members), name + expected = scale * sum(native[kind, label] for label in members) + assert targets[name] == pytest.approx(float(expected), rel=1e-12), name + + +@pytest.fixture(scope="module") +def toy_effective_standin_table(toy): + from microcosm.build.reference_intervals import interval_table + + table, _ = _standin_bands(toy[0]) + return interval_table(table) + + +def test_property_a_positive_standin_above_the_first_cut_lands_in_one_band( + toy, toy_effective_standin_table +): + """For any toy incomes: x > 0 above the aged first cut is in exactly one + coarse band (the independent cut count says which), 0 < x <= first cut is + in none, and x <= 0 is ``excluded`` and in none. The table, as NZ's, has + no nil band and a first cut above zero. + """ + from hypothesis import given, settings + from hypothesis import strategies as st + + from microcosm.build.reference_intervals import interval_indicators + from microcosm.build.transport.measure_kernels import combine_measure + from test_support.microcosm_build.transport_nz_toy import ( + TOY_STANDIN_COMPONENTS, + TOY_STANDIN_CUTS, + TOY_STANDIN_FIRST_CUT, + toy_standin_declaration, + ) + + declaration = toy_standin_declaration() + table = toy_effective_standin_table + factor = Fraction(11, 10) + cuts = [factor * cut for cut in (TOY_STANDIN_FIRST_CUT, *TOY_STANDIN_CUTS)] + signed = st.integers(-150_000, 150_000) + + @settings(max_examples=150, deadline=None) + @given( + st.lists( + st.tuples(*(signed for _ in TOY_STANDIN_COMPONENTS)), + min_size=1, + max_size=12, + ) + ) + def check(records): + columns = { + column: pd.Series([row[i] for row in records], dtype="int64") + for i, column in enumerate(TOY_STANDIN_COMPONENTS) + } + measures, statuses, _ = combine_measure( + declaration, columns, np.ones(len(records)) + ) + indicators = interval_indicators(table, measures, statuses) + hits = np.vstack(list(indicators.values())).astype(int) + names = list(indicators) + for index, (x, status) in enumerate(zip(measures, statuses, strict=True)): + if x <= 0: + assert status == "excluded" + assert hits[:, index].sum() == 0 + elif x <= cuts[0]: + assert status == "included" + assert hits[:, index].sum() == 0 + else: + assert status == "included" + assert hits[:, index].sum() == 1 + band = sum(x > cut for cut in cuts) - 1 + assert names[int(np.argmax(hits[:, index]))] == table.indicators[band] + + check() diff --git a/test_support/microcosm_build/transport_nz_toy.py b/test_support/microcosm_build/transport_nz_toy.py index 9868a36347..90f44a87d8 100644 --- a/test_support/microcosm_build/transport_nz_toy.py +++ b/test_support/microcosm_build/transport_nz_toy.py @@ -577,6 +577,345 @@ def _earnings_map_v2(resources, rows, facts): ) +# --------------------------------------------------------------------------- +# The toy income stand-in (G8-r2c G at toy scale) +# --------------------------------------------------------------------------- + +#: The toy's counterpart of NZ's ird_taxable_income_ty2027_v1. +TOY_MEASUREMENT = "toy_income_standin_v1" +TOY_STANDIN_NODE = "nz.income.standin" +TOY_STANDIN_TABLE = "toy_income_standin" +TOY_STANDIN_OUTPUTS = { + "measure": "toy_income_proxy_cents", + "unit": "toy_income_proxy_nzd", + "universe_status": "toy_income_proxy_universe_status", +} +#: Signed components, as N/income_measurements.json lists NZ's: wages, signed +#: farm income, interest, rental (the one add-back) and the three annual +#: transfers the toy's annual-transfers node writes. +TOY_STANDIN_COMPONENTS = ( + "employment_income_cents", + "farm_self_employment_income_cents", + "interest_income_cents", + "rental_income_cents", + "toy_main_transfer_cents", + "toy_other_transfer_cents", + "toy_pension_transfer_cents", +) +#: The coarse table's source-period cuts in cents; the last band is open. +#: There is no nil band, and the first cut sits above zero, as NZ's does. +TOY_STANDIN_FIRST_CUT = 15_000 +TOY_STANDIN_CUTS = (40_000, 60_000) +#: Native member labels of each coarse band, in band order. A coarse row +#: sums its members' facts, as F's 24 rows sum IRD's native rows. +TOY_STANDIN_MEMBERS = ( + ("n01", "n02", "n03"), + ("n04", "n05"), + ("n06", "n07", "n08", "n09"), +) +#: Invented native facts: (count, amount in currency units) per member, +#: chosen near the toy's pre-fit weighted totals so R2C-3's ratio bounds hold. +TOY_STANDIN_FACTS = { + "n01": (17.0, 5_000.0), + "n02": (19.0, 5_000.0), + "n03": (18.0, 5_100.0), + "n04": (20.0, 9_300.0), + "n05": (19.0, 9_200.0), + "n06": (6.0, 9_000.0), + "n07": (6.0, 8_000.0), + "n08": (5.0, 7_000.0), + "n09": (5.0, 6_700.0), +} +#: The toy's own ageing dimension; its factors come from the shared fixture. +TOY_AGING_DIMENSION = "toy_aging_index" +#: Support thresholds suited to forty households; R2C-3's ratio bounds stand. +TOY_PREFIT_SUPPORT = {"minimum_source_households": 3, "minimum_kish_ess": 2} + + +def toy_standin_declaration(): + """The toy counterpart of income_measurements.json's ``measures.central``.""" + return { + "entity": "person", + "components": [ + {"column": column, "sign": 1} for column in TOY_STANDIN_COMPONENTS + ], + "add_backs": ["rental_income_cents"], + "floor": 0, + "scale": 100, + "outputs": dict(TOY_STANDIN_OUTPUTS), + } + + +def toy_standin_table(): + """The toy counterpart of ird_taxable_income_standin_ty2024.""" + from test_support.microcosm_build.reference_intervals import ( + interval_table_fixture, + ) + + table = interval_table_fixture( + table_id=TOY_STANDIN_TABLE, + cuts=TOY_STANDIN_CUTS, + nil=False, + first=TOY_STANDIN_FIRST_CUT, + companions=True, + indicator_prefix="toy_income_standin_band", + ) + table.update( + income_column=TOY_STANDIN_OUTPUTS["measure"], + universe_column=TOY_STANDIN_OUTPUTS["universe_status"], + ) + table["diagnostics"]["support_household_entity"] = "household" + for band in table["bands"]: + band["indicator"] += "_indicator" + band.update( + count_reference=f"toy_income_standin_count__{band['id']}", + amount_reference=f"toy_income_standin_amount__{band['id']}", + ) + return table + + +def toy_standin_member_facts(): + """One count and one amount fact per native member, NZ-geography.""" + from test_support.microcosm_build.reference_intervals import band_fact + + rows = [] + for label, (count, amount) in TOY_STANDIN_FACTS.items(): + for kind, value in (("count", count), ("money", amount)): + fact = band_fact(kind, label, value, record_set="toy.standin.bands") + fact["geography"].update(id="NZ", name="Synthetic destination") + rows.append(fact) + return rows + + +def toy_standin_references(table): + """F's multi-member rows at toy scale: ``sum`` counts, ``identity`` amounts.""" + from test_support.microcosm_build.reference_intervals import summed_row + + rows = [] + for band, members in zip(table["bands"], TOY_STANDIN_MEMBERS, strict=True): + for kind, name in ( + ("count", band["count_reference"]), + ("money", band["amount_reference"]), + ): + row = summed_row(kind, name, members, indicator=band["indicator"]) + row.update( + family="income", + measure="toy_people" + if kind == "count" + else TOY_STANDIN_OUTPUTS["unit"], + ) + row["metadata"].update( + required_reference_profile=TOY_AGING_DIMENSION, + required_measurement_binding=TOY_MEASUREMENT, + reference_prerequisites=json.dumps( + [ + f"reference_profile:{TOY_AGING_DIMENSION}", + f"measurement:{TOY_MEASUREMENT}", + ] + ), + interval_table_id=table["id"], + ) + rows.append(row) + return rows + + +def _income_standin(resources, rows, facts, problem): + """Wire the toy stand-in as G wires nz.income.standin, prepare and problem.""" + from test_support.microcosm_build.reference_intervals import prefit_gate_entry + + declaration = toy_standin_declaration() + resources["toy_income_measurements"] = { + "schema_version": 1, + "measures": { + "central": { + "declaration": declaration, + "declaration_sha256": sha256(canonical_json(declaration)).hexdigest(), + } + }, + } + # E's nz.earnings.cents: the mapped float wage becomes integer cents. + wages = { + "entity": "person", + "columns": [ + { + "source_column": "employment_income", + "output_column": "employment_income_cents", + "quantum": "0.01", + "rounding": "half_even", + } + ], + } + rows.append( + _node( + "nz.earnings.cents", + "money.minor_units@1", + population="nz.central", + inputs=[_slice("person", ["employment_income"])], + outputs=[_owned("person", "employment_income_cents", "int64")], + params={"declaration": canonical_json(wages).decode("utf-8")}, + ) + ) + rows.append( + _node( + TOY_STANDIN_NODE, + "measure.combine@1", + population="nz.central", + inputs=[_slice("person", list(TOY_STANDIN_COMPONENTS))], + outputs=[ + _owned("person", TOY_STANDIN_OUTPUTS["measure"], "Int64"), + _owned("person", TOY_STANDIN_OUTPUTS["unit"], "float64"), + _owned("person", TOY_STANDIN_OUTPUTS["universe_status"], "string"), + ], + params={ + "measure": _select( + "toy_income_measurements", + "json", + "measures", + "central", + "declaration", + ), + "measure_sha256": _select( + "toy_income_measurements", + "value", + "measures", + "central", + "declaration_sha256", + ), + "measurement_id": TOY_MEASUREMENT, + }, + ) + ) + table = toy_standin_table() + resources["toy_reference_intervals"] = {"schema_version": 1, "tables": [table]} + references = toy_standin_references(table) + document = resources["target_references"] + document["target_references"] += references + document["hierarchy"].setdefault("target_labels", {}).update( + { + row["metadata"]["contract_target_id"]: f"Synthetic {row['name']}" + for row in references + } + ) + facts += toy_standin_member_facts() + # The compiler replays each selected factor against the facts it loads, + # under the feed pin (_pin_calibration_facts writes both). + for row in rows: + if row["kernel"] == "targets.compile@1": + row["params"]["chronicle_feed_pin"] = _select("toy_calibration_feed_pin") + transformation = { + "operation": "scale_money_and_intervals", + "reference_names": [band["amount_reference"] for band in table["bands"]], + "count_reference_names": [band["count_reference"] for band in table["bands"]], + "interval_table_ids": [table["id"]], + "required_measurement_binding": TOY_MEASUREMENT, + } + resources["reference_profiles"] = { + "schema_version": 1, + "reference_resource": "target_references", + "factor_context_resource": "toy_reference_aging", + "interval_resource": "toy_reference_intervals", + "dimensions": { + TOY_AGING_DIMENSION: { + "default": "central", + # One profile: the toy feed carries one factor's anchor facts. + "profiles": { + "central": { + "transformations": [ + {**transformation, "factor_id": "toy_central"} + ] + } + }, + } + }, + } + bindings = resources["calibration_bindings"] + measured = [TOY_STANDIN_OUTPUTS["measure"], TOY_STANDIN_OUTPUTS["universe_status"]] + added = [{"entity": "person", "name": "toy_people", "constant": 1}] + [ + { + "entity": "person", + "name": band["indicator"], + "interval": {"table": table["id"], "band": band["id"]}, + } + for band in table["bands"] + ] + bindings["columns"] += added + prepare = next(row for row in rows if row["id"] == "nz.calibration.prepare") + prepare["inputs"].append(_slice("person", measured)) + prepare["outputs"] += [_owned("person", row["name"], "bool") for row in added] + prepare["outputs"][-len(added)]["dtype"] = "int64" + # R2C-25: the problem slices the unit column, or _compiled_problem + # (T/target_kernels.py:541-551) refuses every amount row. + problem["inputs"] += [ + _slice( + "person", + [row["name"] for row in added] + [*measured, TOY_STANDIN_OUTPUTS["unit"]], + ), + _slice("household", ["source_household_id"]), + ] + resources["gates"]["gates"] += [ + { + "id": "toy_target_profile_coverage", + "gate": "target_profile_coverage", + "phase": "terminal", + "criticality": "release_blocking", + "evidence_absent_blocks": True, + "parameters": {"required_families": ["income", "toy"]}, + }, + { + **prefit_gate_entry( + tables=[table["id"]], + evidence=list(TOY_PREFIT_EVIDENCE), + **TOY_PREFIT_SUPPORT, + ), + "id": "toy_measurement_prefit", + # R2C-18 leaves every alias unwired, so the gate fails every run; + # as a diagnostic it reports that without blocking the toy export. + "criticality": "diagnostic", + "evidence_absent_blocks": True, + }, + ] + + +def _pin_calibration_facts(resources, facts, root): + """Write the calibration facts and the ageing factors' facts as one feed. + + NZ's IRD ageing factors resolve against the pinned Chronicle feed the + compiler loads; the toy's ageing factors do the same against its own. + """ + from test_support.microcosm_build.reference_factors import factor_fixture + + _, factor_facts, _ = factor_fixture() + plain = json.loads(json.dumps(factor_facts, default=float)) + path, pin = write_pinned_artifact(root, [*facts, *plain]) + pin["source_commit"] = "c" * 40 + identity = chronicle_feed_identity(pin) + artifact = load_ledger_consumer_artifact(path, feed_pin=pin, lexical_floats=True) + declaration, _, _ = factor_fixture(feed_identity=identity) + declaration["id"] = "toy_central" + factor, receipt = resolve_reference_factor(declaration, artifact.facts, identity) + entries = [ + {"declaration": declaration, "operational_factor": factor, "receipt": receipt} + ] + resources["toy_calibration_feed_pin"] = pin + resources["toy_reference_aging"] = { + "schema_version": 1, + "feed_identity": identity, + "factors": entries, + } + return path + + +#: The packaged gate's required_evidence list (N/gates.json measurement_prefit). +TOY_PREFIT_EVIDENCE = ( + "native_band_report", + "omission_register", + "scenario_vsi", + "scenario_vex", + "scenario_vpt", + "identity_receipts", +) + + def make_nz_toy_fixture(root): """Prepare forty synthetic households and the complete transport declaration.""" base = make_entitlement_fixture(root, variants=False) @@ -782,6 +1121,10 @@ def make_nz_toy_fixture(root): problem = by_id["nz.targets.problem"] problem["inputs"].append(_slice("person", ["receives_other"])) _prepare(r, rows, problem) + _income_standin(r, rows, facts, problem) + sources["nz_calibration_facts"] = _pin_calibration_facts( + r, facts, root / "calibration-feed" + ) # A closed replay slice excludes every scratch cent, formula and reporting output. export_inputs = [ _slice( From 4ae5810edc1ac4c0f3ecdb063a535bd0c7e856f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 08:15:13 +0000 Subject: [PATCH 2/3] Test that the toy's rows refuse activation without the stand-in 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 Claude-Session: https://claude.ai/code/session_01LwQq6FpF7dJcTwxa1pX54W --- .../shared/test_nz_transport_toy_graph.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py index 5bfe68a94b..5d07658f56 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_toy_graph.py @@ -1005,3 +1005,24 @@ def check(records): assert names[int(np.argmax(hits[:, index]))] == table.indicators[band] check() + + +def test_without_the_toy_standin_its_six_rows_refuse_activation_by_name(toy): + """The drop-the-node mutant, built in the test: A's proof fails closed.""" + from test_support.microcosm_build.transport_nz_toy import ( + TOY_MEASUREMENT, + TOY_STANDIN_NODE, + ) + + fixture, _ = toy + spec = copy.deepcopy(fixture.spec) + nodes = spec["resources"]["transport_graph"]["nodes"] + nodes[:] = [row for row in nodes if row["id"] != TOY_STANDIN_NODE] + with pytest.raises(ValueError) as refused: + validate_transport_activation(spec) + message = str(refused.value) + _, bands = _standin_bands(fixture) + for band, _ in bands: + for role in ("count_reference", "amount_reference"): + assert f"target_references.{band[role]}; awaits" in message + assert f"measurement:{TOY_MEASUREMENT}" in message From 85d55af517ba463cc63bae7986642fb5c384ca0c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 3 Nov 2026 03:42:40 -0500 Subject: [PATCH 3/3] Prove each NZ calibration node's stand-in is its only writer 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..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 --- .../shared/test_nz_transport_declarations.py | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py index 195711ae62..3cf8c8acfe 100644 --- a/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py +++ b/packages/microcosm-build/tests/engine_free/shared/test_nz_transport_declarations.py @@ -864,16 +864,22 @@ def test_nz_packaged_graph_produces_region_and_bridge_columns_from_named_nodes() assert "region" not in pending and "Reg 17" not in pending -def test_nz_packaged_graph_compiles_with_only_pins_and_money_producers(): +@cache +def _nz_composed(): + """NZ's packaged graph, composed and compiled once per process.""" from microcosm.graph import compile_graph spec, config = _nz_composition_spec_and_config() + graph = compose.compose_transport_graph(spec, config) + return spec, config, graph, compile_graph(graph) + + +def test_nz_packaged_graph_compiles_with_only_pins_and_money_producers(): + _, config, _, compiled = _nz_composed() # No bridge or region root stays synthetic: CREATE declares none of them. assert _BRIDGE_AND_REGION.isdisjoint( (owned.entity, owned.column) for owned in config.create_outputs ) - graph = compose.compose_transport_graph(spec, config) - compiled = compile_graph(graph) assert "nz.package" in compiled.order assert "nz.scn.S0.income_decile" in compiled.order for variant in ("V1", "V2", "V3", "V4", "VHF"): @@ -881,6 +887,50 @@ def test_nz_packaged_graph_compiles_with_only_pins_and_money_producers(): assert f"nz.v.{variant}.gap" in compiled.order +def test_every_calibration_node_has_its_own_standin_as_the_only_writer(): + """R2C-15 on every calibration node, the variant branches' included. + + Composition checks the measurement proof on the skeleton alone: + ``_check_measurement_proofs`` runs before the scenario extensions append + their branches, so it never sees ``nz.v..calibrate``. For each + calibration node of the composed graph, exactly one compiled ancestor + declares the measurement, it is that branch's stand-in, and it is the + only ancestor whose outputs or EXPAND cells hold each proved column. + """ + spec, _, graph, compiled = _nz_composed() + proofs, refusals = compose.measurement_proofs(spec) + assert refusals == {} + columns = [tuple(name.split(".", 1)) for name in proofs[_MEASUREMENT]["columns"]] + _, cells = compose._precalibration_expansions(graph) + by_id = {node.id: node for node in graph.nodes} + roots = sorted( + node.id for node in graph.nodes if node.kernel.startswith("calibrate.") + ) + variants = ("V1", "V2", "V3", "V4", "VEX", "VHF", "VPT", "VSI") + assert roots == ["nz.calibrate", *(f"nz.v.{name}.calibrate" for name in variants)] + for root in roots: + branch = root.removesuffix(".calibrate") + standin = "nz.income.standin" if branch == "nz" else f"{branch}.income.standin" + ancestors = compose._ancestor_closure(compiled.predecessors, {root}) + declarers = sorted( + name + for name in ancestors + if (by_id[name].params or {}).get(compose.MEASUREMENT_PARAM) == _MEASUREMENT + ) + assert declarers == [standin], root + for entity, column in columns: + writers = sorted( + name + for name in ancestors + if (entity, column) in cells[name] + or any( + (output.entity, output.column) == (entity, column) + for output in by_id[name].outputs + ) + ) + assert writers == [standin], (root, f"{entity}.{column}") + + def test_nz_as_area_state_bindings_compare_the_declared_string_geography_codes(): resources = _spec()["resources"] support = resources["transport_atomic_geography_support"]["rows"]