Create the SPI income channel on the microcosm spine with the FRS-only second stage - #717
Conversation
…stages Three new manifest stages ahead of the untouched certified pair: frs_hmrc_spine_leaves (retained HMRC leaves aligned by raw person_id + the employer-pension derive), spi_support_channel (stack 10,000 zero-weight SPI-synthetic households at the pre-clone grain, gate the stratum count, allocate the 0.5 prior-mass share per region with the post-#710 semantics), and hmrc_spi_income_spine (SPI stage-1 QRF, FRS-only stage-2, base-channel dividend redraw after stage-2 training, band materialization, 208-fact classification, effective-mass gate). Four new generic operation kinds including the first row-appending manifest primitive (stack_zero_weight_donors). The spine driver runs all 14 stages and emits the .hmrc_replay.json sidecar. The certified national path, uk/gates.json, the data-shard contract, and the frozen HMRC manifest are all byte-unchanged. Refs #683. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Licensed acceptance run (e7) — resultsTwin channel-extended spine builds (a/b) at commit 4da1b09 on the pinned licensed inputs; evidence in Determinism — Strata receipt — exactly 10,000 SPI-synthetic households at the pre-clone grain; SPI channel mass share 0.500000 of total; exact-total conservation delta 0.0 against the sidecar total; per-region quotas and allocated masses recorded in the receipt. 208-fact replay — Effective-mass floor — Incapacity fence — did not trip: the raw spine carries no nonzero incapacity signal, so Nonzero-share screen (±0.02 vs the committed 145-column reference) — 32 columns compared, max |Δ| 0.0436, 6 beyond the band, adjudication pending (María):
All six are QRF-imputed surfaces on the SPI channel — the standing U14 class (seeded microcosm QRF distributional draws vs microimpute's per-call-reseeded beta-quantile draws, which realize different zero-incidence) at ~38% synthetic composition. Proposed disposition: receipt as U14-class signed differences on the unweighted-share instrument; the weighted-distributional comparison is the binding one at E10, per the #713 doctrine (composition-sensitive deltas are receipted, never tuned away). Reference-absent diagnostics ( Weighted-totals diagnostics — 62 overlap columns vs the licensed register, median |relative Δ| 0.235, receipted with the weight-regime caveat (register minted on the calibrated certified surface; the spine carries uncalibrated design weights + the 0.5-share SPI importance mass — the E4 adjudication precedent). Values stay local per the #609 EUL posture. PR stays draft until the share-screen adjudication and green CI. 🤖 Generated with Claude Code |
Bind the SPI donor and HMRC ODS pins into the build sidecar and the Logbook input-pins digest (input_artifact_pins), bind the replay sidecar's hash and report_kind into the build receipt, clear stale outputs before a run so an interrupted build can never leave an old sidecar beside a fresh H5, and extend the manifest-drift asserts to every load-bearing SPI stage parameter (predictors, outputs, the FRS-channel initialization map, redraw columns, gate declaration, allocation weight kind/conservation, effective-mass floor). The rewrites-provenance finding is deferred to #721. Refs #683, #717. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial-review disposition (Codex, branch diff vs main — verdict: needs-attention, 2 high / 2 medium)All four findings adjudicated; three fixed in
Full 🤖 Generated with Claude Code |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Early read while this is still in draft — the licensed acceptance and adversarial disposition looked complete enough that waiting for the un-draft would just delay the same review. I read spi_spine.py in full, the manifest declarations, and the driver wiring; 138 tests pass for me with the uk extra.
The design center of this PR — the drift asserts pinning every load-bearing manifest parameter to a reviewed code constant (_support_stage_parameters, _assert_income_stage_parameters) — is the right response to the adversarial finding and worth adopting as the house pattern for every stage whose manifest carries semantics: a manifest-only edit to a seed, share, stratum set, or output list now fails closed instead of silently steering the build. Also right: the outputs/rewrites producer split keeping one canonical producer per column across all 14 stages (with the producer-graph gap honestly deferred as #721 rather than half-fixed here); the leaves stage's alignment asymmetry (unknown raw identities fail loud, spine-only persons fill 0.0); the stage-count reconciliation replacing the incumbent's silent narrowing with fail-closed reviewed absences — maternity_allowance_reported never actually produced by the incumbent's own loader is exactly the kind of fact that would otherwise get re-discovered at E10; and the identity/conservation evidence riding checkpoint metadata so resume keeps the receipts.
Three small things, none blocking:
UKSPISupportChannelStageTransform.__call__silently defaults the weight kind:weight_kind=result.household_weight_kind or WeightKind.IMPORTANCE. The manifest assert already requiresweight_kind_out: importance, so if the support builder ever returnsNonehere that's a builder bug being papered over, not a legitimate default — an explicitraisewould match the fail-closed posture of everything around it.- The mutable
last_resulton frozen transforms (object.__setattr__) makescheckpoint_metadata()correct only for the run-then-harvest sequence the driver performs today. If a future resume path ever reconstructs transforms without executing them, theRuntimeErrorwill fire at harvest time — which is the right failure, but a one-line comment onlast_resultsaying "populated only by a live run; resume must re-run or skip harvesting" would keep the invariant from being discovered by stack trace. - The stage-2
seed + 1convention is asserted but not explained at the assert site — the reader finds "drifted from the reviewed value" without learning that stage-2 deliberately derives a distinct stream from the same reviewed seed. The assert message or a comment naming the convention would make the manifest self-explaining.
One question for the un-draft, not a finding: the incapacity_benefit_reported promotion (present-but-all-default under the nonzero-signal fence, documented for the licensed run) — the acceptance comment says the twin builds succeeded, so did the licensed run settle whether it promotes to a 30th stage-2 output, or does that decision ride to E10? The PR body and the acceptance comment each carry half of that story; whichever way it resolved, one sentence tying them together in the body would close it.
Process note: plan → conformance review → licensed twin acceptance → adversarial pass with re-run instruments, all before leaving draft, is the most complete pre-review gauntlet any PR in this migration has run. From my side there's nothing structural queued for the un-draft — with the three nits and the one question addressed, this is ready when you are.
…es disambiguated Manifest order after the merge: frs_brma -> was_wealth -> regional_property_uprating -> the three SPI-channel stages -> the certified pair at the tail, honoring the donor-inheritance rule that the SPI stacking must see the wealth-imputed FRS rows at E10. The WAS donor artifact role becomes was_qrf_donor (the #717 input-pin collector keys by role and the SPI donor already claims qrf_donor); E7's pin test now checks both donor artifacts. Coverage manifest regenerated for the merged roster; stage-count and resource-tuple expectations updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The merged roster stacks 10k SPI-synthetic households after the E5 stages, with channel-imputed property values the E5 uprating never saw; the deterministic-layer identity claims apply to the household_is_spi_synthetic == False population the wealth and uprating stages actually transformed. The stacked rows are E7's receipt surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same mechanical pattern as rounds 2-3, applied to the UK SPI income channel: regenerate the authored stage manifest from main's source_stages.json; add closed-world variants for the seven new operation kinds (strict_read_private_table, stack_zero_weight_donors, gate_zero_weight_strata, allocate_zero_weight_prior_mass, the two-stage weighted QRF pair, redraw_columns_from_fitted_qrf) and a formula form of derive; port main's new resource-list test to the typed seam (legacy_json rows carry the same intent); UK pin refreshed (ed0a0c36 -> e3ffbb45). No attested kernel modules changed in #717, so seed and US identities hold. Focused batches + lint green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kept The country-package conflict is additive on both sides: main's WAS bounds + regional land values stay in main's ordering, this PR's uk_national_targets.json + target_references.json append after the census row, and the #717 resource-tuple test carries all four. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SPI-spine PR added a second test pinning the UK resource tuple; both pins now carry uk_national_targets.json and target_references.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A concurrent push folded main into this branch as a merge and included the uv.lock regen for microcosm-frame's uk extra (stale since #717; CI syncs without --locked so it stayed green). The merge's content is otherwise identical to this rebased line, which supersedes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #683 (WS-E child of #145 under #665).
What
Three new manifest stages inserted contiguously before the (byte-untouched) certified pair, re-homing the existing SPI restoration machinery onto the raw E2–E4 spine:
frs_hmrc_spine_leaves— the 5 adjudicated retained HMRC leaves materialized fromadult.tab/benefits.taband aligned by raw identity (person_id = sernum×1000+person, a direct reindex — never positional), plus the incumbent'semployer_pension_contributions = 3 × employeederive.spi_support_channel— stacks exactly 10,000 zero-weight SPI-synthetic households at the pre-clone grain (uniform seeded donor draw, the incumbent's class, deliberately not weight-proportional), gates the stratum count mid-stage (e7_spi_synthetic_preclone, max 10,000 — the pre-clone re-derivation of the June 100k declarations), then allocates the 0.5 prior-mass share per(region,)stratum with the post-SPI prior allocation: spread stratum mass over the sampled quota, not donor weights #710 semantics: flat within stratum, donor weights never propagate, exact-total conservation, weights advance to IMPORTANCE.hmrc_spi_income_spine— SPI stage-1 QRF (age/gender/region; FRS-channelgift_aid/charitable_investment_giftszero-init), FRS-only stage-2 (29 outputs, trains on the base channel's raw dividends), then the base-channeldividend_incomeredraw from the stage-1 fit — deliberately ordered after stage-2 training — band materialization, the 208-fact classification with the 8 canonical fences byte-equal, and the effective-mass gate (1 ppm floor, strict raise at f100).Four new generic operation kinds:
stack_zero_weight_donors(the first row-appending manifest primitive),gate_zero_weight_strata,allocate_zero_weight_prior_mass,redraw_columns_from_fitted_qrf. The spine driver (tools/build_uk_frs_spine.py) runs all 14 stages, takes required--spi-tab/--hmrc-odspinned inputs, and emits the.hmrc_replay.jsonsidecar.Untouched, verified: the certified national-driver path,
uk/gates.json, the data-shardcontract.py(zero fingerprint churn), and the frozenhmrc_income_source_stages.json(shac0341af7…re-verified). New spine stages declare only new columns asoutputs; already-produced columns ride therewriteskey, so the 14-stageStagePlankeeps one canonical producer per column.Stage-count reconciliation
The issue's "29 FRS-only variables" vs the incumbent HEAD's 31-name list: 29 declared stage-2 outputs + 2 reviewed-absent (
incapacity_benefit_reported— present-but-all-default under the active nonzero-signal fence, promotion to a 30th output documented for the licensed run;maternity_allowance_reported— never produced by the incumbent's own FRS loader, which silently skips it) = 31. Fail-closed reviewed absences replace the incumbent's silent narrowing. Resolved by the licensed run: the nonzero-signal fence did not trip, soincapacity_benefit_reportedstays reviewed-absent and there is no 29→30 promotion in this PR. Nothing rides to E10 — if a future vintage carries real incapacity signal (e.g. the E11 FRS 2024-25 refresh), the fence raises at build time and forces the reviewed promotion then.Manifest-order coordination (with #714 / E5–E6)
E7's block sits immediately before the certified pair, which must stay at manifest positions [-2:] (frozen lockstep test). E5/E6 stages insert after
frs_brmaand beforefrs_hmrc_spine_leaves, so at E10 assembly the SPI donors inherit wealth/consumption columns (the incumbent's position-7 ordering). E7 edits neitheruk/gates.jsonnorcontract.py, so the only collision surface with #714 issource_stages.json+ the roster lists (UK_SOURCE_STAGE_NAMES,_STAGE_NAMES,uk_stage_implementations): whoever lands second unions the rosters with E5's stages ahead of E7's block and re-runs the targeted manifest tests. No fingerprint recompute is needed on E7's account.UK-specificity note
Generic (shared vocabulary/handlers): the four new op kinds and the outputs/
rewritesproducer-split pattern. UK-specific, with reasons: the threebuild/ukstage declarations anduk_runtime/spi_spine.py(SPI/FRS source semantics), the retained-leaves constituents and crosswalk doctrine, the HMRC ODS fact surface and its fences, andbuild_uk_spi_support_channel's channel labels/lineage columns.Signed differences (vs the incumbent at ebf733c, and one internal)
(region,)stratum at build with exact-total IMPORTANCE weights. Same semantics, moved seam, conservation receipted.gift_aid/charitable_investment_giftson the FRS channel (incumbent behavior, parity-bearing); the certified path's NaN-on-FRS record stays for that path._reportedintermediates are not stripped (first-class spine columns per the E2/E3 architecture; category/flag refresh preserved; the incumbent's export strip is deferred to E10's export-surface adjudication).uk_hmrc_spi_income_spine_208_fact_replay(not the certifieduk_hmrc_income_208_fact_replay): a deliberate identity separation so no verifier can accept a spine replay where the certified replay is required (the Close the schema-3 terminal-report path for non-grandfathered exact-k UK releases #712 defense-in-depth lesson). Fact surface, fence set, and summary semantics are byte-compatible; only the kind string differs. Flagged here because the implementation run pinned it in tests without logging it as a deviation.id_multiplier(synthetic IDs carry no independent meaning).Verification
test_us_multispine_pool_tool.pyfailures in the sandbox run were caused by a uk-only venv missingpolicyengine-us, not by this diff) andruff check .clean.gates.json/contract.pyuntouched;git diff --checkclean.Licensed acceptance (pending — PR stays draft until it lands)
The e7 evidence run (twin channel-extended spine builds →
data/ukds/acceptance/e7/) with eight instruments: twin-build payload identity, the ±0.02 nonzero-share screen vs the 145-column reference, weighted totals viameasure_uk_weighted_integrity_baselines.py, the 208-fact replay sidecar with byte-equal fences, the effective-mass floor receipt, the strata-count receipt, the permutation-identity receipt (QRF columns covered by twin-build determinism, thehmrc_spi_incomeprecedent), and the incapacity-fence adjudication record.🤖 Generated with Claude Code