UK source stages on the shared manifest: source_stages.json + StagePlan assembly - #690
UK source stages on the shared manifest: source_stages.json + StagePlan assembly#690juaristi22 wants to merge 3 commits into
Conversation
…an assembly
Copy the frozen HMRC manifest into canonical uk/source_stages.json, split at
operation granularity into the two runtime stage names, with incumbent-name
citations rephrased; the frozen original stays byte-identical behind a
sha-pinned lockstep test. Add the ten UK operation kinds plus the generic
materialize_rules_engine_predictors to the shared allowlist. The UK national
driver assembles via country_stage_plan(load_country_spec("uk"), ...) with
UKNationalStage kept as a one-release deprecated shim; StageRecords are
surfaced, including through checkpoint resume, and the UK frame validation
still runs after every stage. Shared rules-engine seam in frame/rules.py
(country assertion + predictor materializer), a thin deferred-import
PolicyEngine-UK adapter, and the microcosm-frame [uk] extra.
Workstream E1 of the UK migration (#145, #665).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
620da35 to
925d0bb
Compare
An absent country extra no longer skips the engine assertion — the UK handler map serves country "uk" by construction, so the handler now defaults the dataset country to "uk" and refuses a runtime context declaring any other country. Regression tests cover the wrong-country engine with no extra and the non-UK context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codex adversarial review (branch diff vs origin/main): verdict
Review focus covered: lockstep fidelity to the frozen manifest, StagePlan/checkpoint semantics, the Logbook-preamble rebase reconciliation, the shared |
… column The frozen manifest's outputs listed the SPI concept "state_pension", but the stage writes the auxiliary column hmrc_spi_state_pension_income — the model input is formula-owned and never a frame column here. Outputs became load-bearing when country_stage_plan compiled them into StagePlan.produces, so the full-rung licensed-data build refused to stage. The copy now declares the persisted truth; operation payloads keep the concept name; the lockstep test records the correction as a declared exception. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Licensed-data acceptance complete (E1 exit criterion): full-rung control-vs-candidate builds are byte-identical at every stage boundary, with identical gate verdicts. All licensed inputs stayed local; this comment carries only hashes, statuses, and already-public observables. Setup: two full-rung staging builds from identical inputs (certified Results:
One defect found and fixed by this run (c4cd761): the frozen manifest's declared output Both failed attempts and all four builds landed hash-chained Logbook rows (#666 wiring exercised end to end, including failure dispositions). |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Byte-identical checkpoints from identical licensed inputs on both sides, with all 13 terminal-gate statuses and detail payloads matching, is about as good as a dispatch-migration acceptance gets. Both sides blocking at uk_weight_ratio and reproducing #630 Finding 2 bit-for-bit on the new carrier is a nice bonus signal — it shows the migration preserved even the failure.
I went in expecting the lockstep test to be the soft spot, since "allows differences only at three enumerated paths" is usually where drift hides. It isn't:
assert stage1["operations"] + stage2["operations"] == expected_operationsFull structural equality of the concatenated operations against the frozen original, plus artifacts, grain and outputs compared the same way, with each deviation constructed explicitly rather than skipped by a wildcard — and test_frozen_original_bytes_are_pinned making the baseline itself immutable. That's the right shape and it earns the split.
The thing I'd act on before the WS-E children land: none of this PR's UK verification runs in CI.
.github/workflows/test.yml does:
run: uv sync --all-packages --extra usThis PR introduces the [uk] extra and the policyengine_uk adapter, so test_policyengine_uk_adapter.py, test_uk_source_runtime.py, test_uk_source_stages.py and the shim-vs-StagePlan payload-equivalence test are all requires_uk — skipped on every CI run. You say as much ("the local run is the UK evidence"), and for this PR the licensed acceptance more than compensates.
The problem is forward-looking. This is the structural prerequisite for eleven more WS-E children (#678–#688), each of which extends the per-operation dispatch seam introduced here. Every one of them will land with its UK surface verified only by whatever local run its author remembered to do, and no regression in this seam will be caught by CI on any subsequent PR — including PRs that aren't about the UK at all.
Epic #665 lists "UK CI lane (to be filed)" under workstream A. I checked, and it's still unfiled — there's no open issue for it. This PR is the one that makes it acute rather than theoretical, so it's worth filing now with this PR as the motivating case, and worth deciding whether it blocks the WS-E children or merely races them.
One observation worth carrying into the workstream
The state_pension → hmrc_spi_state_pension_income correction is more interesting than a typo fix. Your own note says outputs "became load-bearing when country_stage_plan compiled them into StagePlan.produces" — so the frozen manifest carried a wrong output name harmlessly for as long as nothing read it, and the migration is what made it detectable. That implies the other frozen manifests may carry the same class of latent error, dormant until they too become load-bearing.
If that's cheap to check ahead of time — validating declared outputs against persisted columns for the manifests not yet migrated — it would turn a series of licensed-run surprises into one upfront sweep. Given each discovery currently costs a credentialed build to find, that seems worth an hour.
Smaller
UKNationalStagekept as a one-release deprecated shim with the conversion internal is the right call, and having the payload-equivalence test compare shim againstStagePlanrather than asserting the shim away is what makes the deprecation safe.- Executing one-stage
StagePlans per checkpoint boundary after full-plan assembly validation is the detail that keeps stage names — and therefore checkpoint identities — valid across resume. Easy to have gotten wrong in the other order. - Requiring the adapter's declared country to match the dataset's, and refusing already-present columns in
materialize_rules_engine_predictors, are both the fail-closed choices. Good that the protocol test proves the policyengine-uk import stays deferred, so ordinary runs don't pay for it.
Declare the frs_spine stage in uk/source_stages.json: 14 sha+size-pinned FRS 2023-24 tabs, generic operations, and an 82-column load-bearing output surface (direct raw mappings only — the derived layer, stochastic layer, and imputations stay with later WS-E children). New uk_runtime/frs_spine.py ports the incumbent's entity/ID scheme (household sort invariant included), gross4 design weights, and the raw column mappings with WEEKS_IN_YEAR applied uniformly (signed difference at three incumbent *52 call sites). country_stage_plan gains country-agnostic stage_names subsetting so the spine and national pipelines share one manifest; the national driver selects its two stages explicitly. New tools/build_uk_frs_spine.py driver (fail-closed pins, atomic write, Logbook row per attempt, nonzero-shares parity mode, no seed — the build is structurally deterministic). Workstream E2 of the UK migration (#145, #665); stacks on E1 (#690). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workstream E1 of the UK migration (#145, master epic #665). Closes #677.
What
Migrates the UK country package off its bespoke source-stage machinery onto the shared, country-agnostic path — the structural prerequisite for every other WS-E ingest/imputation stage. Behavior does not change: this is a dispatch migration, verified by a payload-equivalence test and the full suite.
uk/source_stages.json— a realSourceManifest, carrying the two runtime stages. The frozenhmrc_income_source_stages.jsondeclared one stage holding all 10 operations; the copy splits at operation granularity intofrs_hmrc_retained_leaves(2 ops, the 5 retained-leaf outputs) andhmrc_spi_income(8 ops, the original 14 outputs), order preserved. The frozen original stays byte-identical as the June evidence anchor, behind a sha-pinned lockstep test that allows differences only at three enumerated citation paths — rephrased becauseFORBIDDEN_SOURCE_DEPENDENCIESsubstring-matches the incumbent repo's name inside them (every DOI, UKDS study number, sha, and size is unchanged). It retires at the FRS 2024-25 refresh (E11).source_manifest.py— the 10 UK operation kinds plus a new genericmaterialize_rules_engine_predictorsjoinALLOWED_SOURCE_OPERATION_KINDS. The validator itself is untouched.uk_runtime/national_build.py—build_uk_national_datasetaccepts sharedplan.Stageentries or aStagePlan; execution goes throughStagePlan.run, so every stage now yields aStageRecord(surfaced onUKNationalBuildResult, including through checkpoint resume).validate_uk_national_framestill runs after every stage.UKNationalStageremains as a one-release deprecated shim, converted internally.tools/build_uk_national_dataset.py— assembles stages viacountry_stage_plan(load_country_spec("uk"), uk_stage_implementations(...)), the BE whole-stage-implementations pattern. Stage names are unchanged, so checkpoint identities remain valid.uk_runtime/source_runtime.py(new) — the implementations-map builder plus the per-operation dispatch seam later WS-E children extend.frame/rules.py— sharedassert_rules_engine_countryandmaterialize_rules_engine_predictors(refuses missing/mismatched country declarations, empty predictor lists, already-present columns, and shape drift).frame/adapters/policyengine_uk.py(new) — a thin deferred-import PolicyEngine-UK adapter (country = "uk"); the US adapter now declarescountry = "us"; microcosm-frame gains an opt-in[uk]extra. This is the ratified WS-E rules-engine seam (Port UK compact source stages and 2024-25 refresh from uk-data #145 U1): stages that need formula-computed predictors declare them in the manifest and materialize through the adapter, with the engine's country required to match the dataset's.UK-specificity note
Per the country-agnostic doctrine, the UK-specific surface of this PR is: the stage declarations in
uk/source_stages.json(HMRC/SPI/FRS/UKDS identities), the whole-stage wiring inuk_runtime/source_runtime.py, the UK CLI driver boundary, and thepolicyengine_ukadapter +[uk]extra. Everything else — the allowlist entries, thematerialize_rules_engine_predictorsoperation, the country assertion, the StagePlan execution path — is shared and usable by any country.Implementation choices (no forced deviations from the reviewed plan)
StagePlans per boundary after full-plan assembly validation, so stage names survive resume.frame/rules.py(no existing US helper to extend).frs_hmrc_retained_leavesstage metadata derives from the runtime'sFRS_HMRC_RETAINED_LEAF_SOURCE_EVIDENCE(DonorSpec refuses an unnamed donor).Verification
[uk]extra installed — note CI'sus-only lane skipsrequires_uktests, the known WS-A gap, so the local run is the UK evidence).test_uk_source_stages.py), runtime seam (test_uk_source_runtime.py), adapter (test_policyengine_uk_adapter.py), and a shim-vs-StagePlan payload-equivalence test on the national-build fixture.ruff checkclean; changed files format-clean (26 pre-existing repo-wideformat --checkfailures are outside this surface and untouched).git diffclean on alluk/*.jsonexcept the two declared edits:country_package.jsonresource line, newsource_stages.json).The licensed-data acceptance from #677 is complete (see the acceptance comment below): full-rung control (origin/main) and candidate builds from identical licensed inputs are byte-identical at both stage-boundary checkpoints, with all 13 terminal-gate statuses and detail payloads identical. Both sides block at
uk_weight_ratio(#630 Finding 2, reproducing bit-for-bit on the new carrier) — the pre-existing release blocker, independent of this PR. The run surfaced and fixed one manifest defect only reachable with licensed data: the frozenstate_pensionoutput is a SPI concept, not a persisted column (corrected tohmrc_spi_state_pension_income, declared in the lockstep test).🤖 Generated with Claude Code