Repository navigation
Enforce source-household weight-quality gates and per-cell support for the NZ graph (G8a) - #1202
Conversation
Carry a vintage-scoped source-household concept from explicit donor lineage through CREATE and benefit-unit construction. Follow the schema-addition precedent by bumping the schema to 3 and recording provenance as unmapped in all four engine adapters. Add declared source aggregation and absolute ESS floors while preserving the legacy row-gate result bytes. Adopt the ruled calibrated household ESS 4000 and ratio 5 screens in the NZ spec. Collect required, scenario-complete diagnostics for the declared reporting grid. Aggregate clone weights and dollar contributions before computing support metrics. Suppress failing cells and omit their scenario bands; missing diagnostics, scenarios or lineage prevent export. Complete explicit gap output declarations and refuse undeclared reporting classifications. Track ESS 16000 and cell ESS 400/share 5% as diagnostic targets. Invariants checked: - No-clone ESS/ratio exact equality and cloned ESS non-increase: Hypothesis properties in test_transport_weight_quality.py. - Weight scale invariance of ESS, ratio and every cell diagnostic: Hypothesis properties in the same file. - Increasing one source donor's weights never decreases its dollar share: Hypothesis property compared with independently aggregated contributions. - Missing lineage, diagnostics and scenarios block: named unit-gate failures and end-to-end entitlement graph regressions. - UK/US row-gate compatibility: 1435 direct and 16 complete binding-result byte comparisons against dispatched HEAD, plus unchanged country tests. Verification: 1310 passed and two availability skips across 28 targeted files. All 44 new test functions / 61 parametrized cases fail under relevant runtime mutants, including the four required defects. CI-plan verification, ruff check, format check and git diff --check pass. Exact commands and counts, failing mutant ids and E lines, and the initial five fixture failures with their passing rerun are in the lane report (kept by the NZ hub). Built in two lane attempts on GPT-6.1 Sol; the first was lost before reporting and the second audited and finished its work. No population build or pinned donor H5 read was made. Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
The tracked ±20% targets (cell ESS 400, donor share 5%) and the required cells, scenarios and aggregation column are declared twice in a spec: on the cell_support gate's contract and on the cell_support_target gate. Nothing kept the two copies equal, so a support-only edit could leave the tracked target reporting against different numbers (review finding on #1202). _cell_contract now refuses a spec where any shared key disagrees, and names the gate, both keys and both values. Lists compare as sets. Tests: a matching target builds; drift in each of the five keys is refused; the reviewer's support-only mutant is refused. With the guard disabled in memory, six of the seven new cases fail. The NZ spec test also pins the contract's target values and the shared cell and scenario lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tracked ±20% targets (cell ESS 400, donor share 5%) and the required cells, scenarios and aggregation column are declared twice in a spec: on the cell_support gate's contract and on the cell_support_target gate. Nothing kept the two copies equal, so a support-only edit could leave the tracked target reporting against different numbers (review finding on #1202). _cell_contract now refuses a spec where any shared key disagrees, and names the gate, both keys and both values. Lists compare after sorting, so order does not matter but duplicates do. Tests: a matching target builds; drift in each of the five keys is refused; the reviewer's support-only mutant is refused. With the guard disabled in memory, six of the seven new cases fail. The NZ spec test also pins the contract's target values and the shared cell and scenario lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8c00677 to
e73481d
Compare
The round-2 reviewer's probes on e73481d found that the guard: - skipped a target gate declared "not_applicable": null, which the gate parser treats as active, so drift on that gate escaped; - let an omitted shared value through, though the target binding needs all five; - raised a bare TypeError on a list mixing types or holding mappings. A gate entry now counts as active when not_applicable is absent or null, as GateSelectionSpec reads it, for both the target gate and the support contract. Each shared value must be declared on both sides. Lists compare as multisets ordered by canonical JSON, and only when both sides are lists, so a malformed list or a string standing in for a list is refused by name. Tests: 11 new cases (lists in another order build; a null-marked target is checked; each of five omitted values is refused; mixed, mapping and string lists are refused by name; a null-marked contract is the contract). Four in-memory mutants (the e73481d guard, the presence-only check, order-sensitive lists, sorting a non-list) each fail at least one case, and every new case fails under one of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged by the NZ hub (session local_e407a527) at 16f8308 with --squash. Gates: |
…follow-up) (#1219) * Refuse a malformed cell-support contract with a named error The round-3 review of #1202 found two older paths in _cell_contract that failed closed with bare errors on a malformed spec: a contract missing a target_* threshold raised KeyError while the thresholds were read, and matching lists of mappings on both gates reached a set() comparison and raised TypeError. _require_cell_contract_shape now runs first. It requires every key the contract is read for, and requires required_cells and required_scenarios to be non-empty lists of non-empty strings, naming the gate and the offending key or value. Tests: each of the eight omitted keys; mapping, mixed, empty, string and blank values for both lists; and the reviewer's matching-mapping case. All 19 fail with the check removed. One #1202 case, a string contract list, is now refused by this earlier check, and its expected message says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Require the cell contract's weight kind and entity by name _cell_contract reads required_weight_kind and required_weight_entity, but the shape check did not list them, so a contract missing either passed the check and then raised a bare KeyError. Both are now required, with an omission test each (red without the fix: 2 failed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of #343, alongside #1183 (G8). This is package G8a of the NZ graph build: the weight-quality gates set by the NZ method card's MC15 item, enforced end to end. The NZ graph stays inactive; G8 activates it.
The thresholds this enforces
The method card sets a ±40% sampling-precision budget per reported cell as binding, and tracks ±20% as a target. The resulting thresholds live in
build/nz/gates.jsonas spec data:weight_ess, on source-household-aggregated calibrated household weightsweight_ratio, same aggregationWhat changes
Source-household identity. The pinned donor has 57,240 household rows but 47,277 original source households, because some are cloned. A new country-neutral concept carries a vintage-scoped source-household key from explicit donor lineage through CREATE and benefit-unit construction (
microcosm-frametransport.py,unit_construction.py,concepts.py).Aggregated weight gates.
transport/weight_quality.pysums clone weights by source key.weight_essandweight_ratiogain an optional declared aggregation, an absolute ESS floor, and a refusal when the weights aren't calibrated household weights or lineage is missing.Per-cell diagnostics.
takeup.gap@1now computes six metrics per cell × scenario over the declared reporting grid:The diagnostics are a typed required artifact. The entitlement graph wires them into the battery, so their absence blocks export, and failing cells are suppressed. Undeclared reporting classifications are refused.
NZ spec.
gates.jsonactivates only the two ruled weight gates and adds the cell-support and tracked-target entries; every othernot_applicableentry is unchanged.scenarios.jsondeclares the 40 decile × unit-type cells and the three receipt-group cells.country_spec.pyallowlists the two new gate names.One set of cell targets. The tracked targets (cell ESS 400, donor share 5%) and the required cells, scenarios and aggregation column are written on both the cell-support contract and the tracked-target gate. The build refuses a spec where the two copies disagree, naming the gate, both keys and both values.
TypeError. Two older paths in_cell_contractstill fail closed with bare errors on malformed specs, outside this check: matching lists of mappings on both sides reach a laterset()and raiseTypeError, and a contract missing atarget_*threshold raisesKeyErrorbefore this check runs. A follow-up validates presence and types first.not_applicableis absent or null, as the gate parser reads it. That rule now applies to both the target gate and the support contract.Invariants, scoped to the new code
Not in this PR
income_decileorreceipt_group. The graph refuses to run cells without them, which is intended until G8 or a later package supplies them.transport_graph.jsonorentitlement_graph.jsonfor NZ yet; that is Activate the NZ graph against pinned Chronicle facts (NZ graph G8) #1183.Tests
The build lanes: 1,310 passed, with 2 existing availability skips, across 28 targeted test files, one file per run. 61 new test cases each fail under in-memory mutants. Four of the mutants are required by the brief: row-level weights used instead of aggregated, the absolute floor ignored, diagnostics absence not enforced, and suppression not applied.
tools/ci_test_plan.py verify, ruff andgit diff --checkpass.The hub, on the final tree:
test_transport_graph.py(no country name or float literal in transport)test_nz_spec_package.py, after the drift guardtest_concepts.pytest_transport_weight_quality.pytest_transport_source_household_lineage.pytest_transport_gate_bindings.pytest_concept_mapping.pytest_transport_takeup_kernels.pytest_transport_entitlement_graph.py, after the drift guardtest_transport_gate_bindings.py, after the drift guardNo frozen file is touched.
Built in two GPT-6.1 Sol lane attempts from the NZ hub's brief. The first was lost before reporting; the second audited its work file by file and finished it.
Review
An independent GPT-6.1 Sol review at
a392f97freturned APPROVE_WITH_NITS, "Needs Max: no", with no blocking finding.ci_test_plan.py verify, ruff,graph_acceptance_burndown.py --verifyandgit diff --checkpass.Its three nits, and what happened to each:
e73481d7(item 5 above). The reviewer's mutant now fails, as do drift cases for each of the five shared keys: six of the seven new cases fail with the guard disabled in memory. The NZ spec test also pins the contract's copy.gates.jsoncites d1281. The ruling and the adopted method card are in the NZ hub's decision record, not in this checkout. The lane's historical test and mutant counts above are the lane's reports; the review's own counts are the independent check.Round 2
A delta review of
e73481d7lost its first two attempts to Codex transport failures. Probes the second attempt saved found three gaps in the new guard:"not_applicable": nullwas skipped, though the gate parser treats it as active;TypeError.Fixed in
16f8308e, as item 5 describes, with 11 new cases. Four in-memory mutants each fail at least one of them, and every new case fails under one: thee73481d7guard, a presence-onlynot_applicablecheck, order-sensitive lists, and sorting a non-list. ### Round 3A GPT-6.1 Sol delta review of
16f8308ereturned APPROVE_WITH_NITS, "Needs Max: no".git diff --checkpass.Previous review rounds were lost to quota limits; their saved evidence is in the hub's records.
🤖 Generated with Claude Code