Skip to content

Register one UK full-build graph with all-geography calibration - #901

Draft
juaristi22 wants to merge 280 commits into
microcosm-us-launch-integration-20260909from
uk-full-build-graph-registration
Draft

juaristi22 wants to merge 280 commits into
microcosm-us-launch-integration-20260909from
uk-full-build-graph-registration

Conversation

@juaristi22

@juaristi22 juaristi22 commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

UK population construction and calibration currently span source-spine, national and joint-local drivers, with transformations and acceptance evidence outside the executable graph. This change registers one full build from canonical raw FRS sources through geographic replication, target compilation, calibration, optional exact-count sizing, gates, diagnostics and checked export.

Stacked on #893 at 1cebe3547543bf2917a7af7362bfbb22a05bf964; review against microcosm-us-launch-integration-20260909.

  • Calibrate all applicable geographic levels together by default. --target-geographies country explicitly filters the same graph. Pool copies K, exported household count k and target scope remain independent; failed support or fit checks never narrow scope automatically.
  • Remove executable frs_hmrc_retained_leaves and hmrc_spi_income stages; retain the canonical spine replacements already present in Consolidate the US survey graph build and enrichment pipeline #893. Retire independent national/joint-local scientific drivers. Legacy command names route into the new full-build/readiness interfaces, but retired flags fail explicitly: these are entry-point shims, not compatibility with old invocation scripts. Use microcosm-build-uk / tools/build_uk_full.py for the maintained build interface.
  • Reuse shared execution, typed artifacts, target selection, ordered sparse problem/result codecs, gate evidence and staged file publication. Add explicit same-kind weight-update contracts and correct pandas index alignment for graph writes on sampled populations.
  • Preserve UK source/model boundaries, seeds, sampling before cloning, geographic draws, identifiers and solver/selection algorithms. Include the full national register, band edges, independent source pins and national/local checks. Chronicle census cells supply household targets; regional rows remain in calibration while country totals control country-wins reconciliation.
  • Restore stage, fit, gate and diagnostic evidence from verified storage. Retain source-phase manifests after downstream exceptions. Checked H5 readback precedes an immutable candidate package and unsigned certification-readiness artifact with explicit missing comparisons.

The coverage manifest also drops the historical pre-spine hmrc_cgt_gains entry, which already declared hmrc_cgt_gains_spine as its replacement. Canonical CGT coverage remains required at build, with pinned source verification, capital-gains output/rewrite checks, the conserved-mass receipt and all 76 published facts fenced from calibration. This removes validation of the old certified-input-H5 route, not canonical CGT construction.

The context mutation guard now includes immutable frame metadata, mass records and declared column order. _context_digest checks for input mutation around kernel execution; it does not directly construct node keys. The shared implementation source changes deliberately change implementation and cache identities. The H1 parity refresh also resolves inherited stale implementation pins: direct input and numerical fixture bytes remain unchanged, and old/new node identities were independently reconstructed. The PR workflow now runs for non-main base branches so stacked PRs receive CI; that trigger remains after merge.

The latest parent adds supplied-parent US export verification and moves US atomic assignment after initial cloning, with a typed geography-gate artifact consumed before financial fitting. Its shared artifact is opt-in. The UK graph retains its existing ladder and assignment method; adopting shared atomic assignment and changing UK stage order remain subsequent work.

Validation includes direct-versus-graph target/matrix/solver parity, required replay, real preflight refusal before solving/export, CLI failure/export evidence, and UK source-spine H2 parity. UK regression runs covered 2,018 distinct cases; corrected fixture cases passed fresh reruns, and the inherited licensed-reference mismatch below remains explicit. Both UK engine and UK-spine jobs passed on the preceding pushed head.

On the merged source, all 521 source-provenance audit cases, 89 shared/UK adapter checks and nine previously failing survey/default/manifest cases pass. Python 3.13 and 3.14 produce identical 438 audit contracts and 1,071 references. All six wheels were rebuilt: 475 Python files and 155 resources match source, wheel and installation, and all 54 installed versions match the lock. The engine-free wheel environment passes 125 shared/UK/export checks; canonical CLI help retains the all-geography default.

All 69 affected parent US acceptance cases pass across seven files, including export comparison, post-clone identity, financial/population graph execution, age-calibration replay and origin-budget integrity checks.

The final ordered regression passes all 101 cases: the complete 58-case PUF file followed by all 43 affected survey/budget cases from CI. All cases execute without skips, errors or failures. An observer checks every teardown and confirms zero changes to sealed producer identities or source bytes.

The reviewed repairs and merged parent are pushed at 051fb972b19d319d58277bd63306d0d0e0947ce2. GitHub CI on this exact head is running; local acceptance does not substitute for its final result.

CI repairs cover issues introduced by this stack and failures in the moving parent:

  • Keep synthetic context fixtures consistent with declared column-order metadata; register the shared weight-update implementation; update only independently reconstructed current identity expectations and lock admission.
  • Admit eight exact public parameter CSVs needed by the locked US engine only after origin, version, installed-record and content checks. Preserve network, child-process and unlisted-data refusals. All 127 bootstrap controls and 43 maintained seed/loader/inventory checks pass; the complete guarded Linux diagnostic passes on 051fb972, with all 65 source stamps verified against that exact commit. Its output remains candidate-only. Align the expected module list with all 44 maintained calibration modules.
  • Reconstruct four omitted solver-default options in independent survey diagnostics while retaining exact comparison and numerical expectations. Repair stale fixture/API expectations and check optional ACS dependencies at their actual fixture boundary; all 422 engine cases execute and pass.
  • Classify current source/model preparation separately from population operators. Bind reviewed functions, callers and finite selector definitions; preserve the raw population-operator scan, exact gate-accessor restrictions and adversarial controls. The static audit documents its limits and adds no whole-module provenance exemption.
  • Restore a mocked PUF resolver through pytest cleanup instead of reloading its whole production module. The previous teardown changed 82 sealed function identities and caused later budget checks to fail. The repaired predecessor leaves zero identity changes in source and installed wheels; production integrity checks remain intact.

One local test with a licensed cached eFRS file exposes an inherited reference mismatch: the frozen extraction uses PolicyEngine-UK 2.89.0, while both parent and current locks use 2.97.0. The unchanged parent generator reproduces the difference against the pinned H5. The historical reference remains unchanged.

A native f001 smoke reached the UC capital-coherence donor-support refusal, reproduced by a direct call to the maintained helper. The available ladder has the older digest. A full native run against the corrected ladder and native certification remain outstanding; this PR does not publish or certify a production dataset. The maintained command, operation inventory and migration boundaries are documented in docs/uk-full-build-graph.md.

Reconciliation update (2026-09-14, head 434fb64d)

The branch is fast-forwarded onto the current microcosm-us-launch-integration-20260909 head (2300e56b, zero behind main) plus the #918 head (072c2e88, charter amendments 25 and 26). Until #893 takes #918, the diff against the base shows #918's graph changes as well as this PR's; none of the graph-package edits below are this PR's own.

Resolution rules for the 34 conflicting files:

Shared-package change (flagged): microcosm-calibrate gains TargetSpec.to_dict / TargetSpec.from_dict, the one dict codec for a spec carried by any artifact. TargetRegistry.to_json / from_json and the UK full-build target-registry artifact use it; registry bytes and content versions are unchanged. Raised because the UK reader had to repeat the schema-8 hierarchy decode that #855 put inline in from_json.

Verification on the reconciled tree: graph suite 775/775; spec-engine, lock and merge-touched US tests 1242 passed plus a 72-test confirmation lane; UK full-build, evidence, adapter and calibrate tests 415 passed plus 51 on the six files the fixes touched; registry codec tests 30/30; tools/spec_engine_coverage.py --check 42156/42156 and 41/41; tools/ci_test_groups.py --verify ok; ruff check clean. The engine and wheels CI shards were not run locally. A native full build against the committed references has not been run on this head; it waits for the Chronicle schema fix that lands on main next and the rebase that picks it up.

Deferred by design, to be decided once the stage reordering fixes how weights flow into calibration: adopting the US origin-budget (per-household grouped bounds) for the UK solve, including its reference weight; and declaring the executor design cap on the calibrated node.

MaxGhenis and others added 13 commits September 13, 2026 09:18
The reviewer returned its verdict through the tool result because its
session had no write tool, so the text is filed here unedited rather
than restated. Records the round's scope: F1, F3-F6 accepted, F2 not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The projection handed every node `population.frame.mass_log`. For an
ordinary node that is the version's *cumulative* log, updated after each
member, while its key binds only the version's structural boundary and
the owners of the columns it declared. Adding or re-parameterising an
unrelated sibling therefore changed what a node was shown without moving
its key, so a cache hit could replay output computed against a different
log.

`run_graph` now records each version's log as that version is admitted --
cold execution and a restored hit both reach the same line -- and projects
ordinary nodes from that boundary. A structural node still receives the
cumulative log, because `keys.py` binds its base *and* every ordinary
member of that version through `members`.

The properties distinguish the two cases: an unread same-version appender
is invisible to an ordinary member and moves neither its key nor its
stored bytes, cold-with-sibling equals cold-without and replays across,
while the same appender is visible to the structural boundary whose key
it moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Frame` deeply freezes its metadata and its mass records, but a frozen
dataclass still yields to `object.__setattr__`. Passing the version's own
`_FrozenMapping` leaves and `MassChangeRecord`s by reference therefore made
every kernel -- and anything retaining a context past its own mutation
check -- a live handle on the population: one `object.__setattr__` on a
shared record rewrote the version's log, and unlike a table nothing would
have noticed.

The projection now hands out a deep copy of the metadata and rebuilt mass
records, through the same rule `_observer_snapshot` already followed (now
one shared `_detached_record`). `_context_digest` binds all three fields:
the metadata through the frame format's own store codec, each mass record
field by field, and the projected column order. The codec is guarded, so
a value `Frame` would never have admitted is reported as the mutation it
is rather than raising while the comparison is computed.

The properties cover both halves, and are not vacuous: the rewrite is
asserted to have landed on the retained copy before the live version and
the next node are asserted unchanged. The observer property now rewrites
nested metadata and mass records too -- rewriting tables alone never
touched these fields.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The review's F2 asked for a design-kind `WeightUpdate` to re-anchor
`Population.design_weights`, on the grounds that a later calibration cap
is otherwise "off by k" and that an `EXPAND` afterwards leaves anchors
"mixed". Read against the source, the first is the declared semantics and
the second is not what the code does.

An anchor is the design weight a row entered the population carrying.
`Population.design_weights` is captured once, at CREATE
(`Population.from_frame`), and afterwards only carried by stable entity id
(`_carry_design_weights`), which `patch` hands to `Population.from_frame`
explicitly so the re-derive-from-the-frame default is never taken.
`_carry_design_weights` maps an EXPAND's *copied* rows back to their
source row's original anchor; only a row with no lineage at all reads the
current frame, because it has no earlier weight to be anchored on. So the
values move and no anchor does -- for retained, cloned and entrant rows
alike -- which the new properties assert by running the same EXPAND over
an updated and an un-updated population and comparing the anchors.

`max_weight_ratio` with `weight_anchor='design'` is therefore still
relative to the original design weights, which is what its refusal has
always said ("original design weight"). Re-anchoring would let an
unrelated normalization silently widen every cap declared upstream of it
by that normalization's factor. The review's own T2 case is asserted as
the intended outcome: calibrated weights equal to design weights doubled
by an update are refused at a cap of 1.5 and admitted exactly at 2.0,
reporting a realized ratio of 2.0. The `WeightUpdate` docstring and
amendment 25 now say all of this instead of "ancestry is untouched".

F4: the required-replay property only showed that a hit succeeds. The
stored record's axis binding is now rewritten to the same ids in reverse,
and to a short axis, and each is refused on a run that must hit the
record with no kernel called -- so the check runs on the replay path and
rejects there. A third property re-files the record unchanged and still
replays, so the two refusals are about the binding rather than about
re-filing. The weak test is renamed to the claim it does support.

F5: amendment 25 claimed a re-solve of an existing calibration as a case
it covers. `calibrate.adam@1` emits no `receipt['weight_update']`, so
that declaration would be refused as unverifiable; it is now marked a
future consumer adaptation, in the amendment and in the two docstrings
that repeated it.

F6: the node mass-policy mismatch message said "weight transition's" on a
path both declarations reach.

`decl.py` is re-locked for the amendment-25 text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ntity

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`_remapped_expand_memberships` aligns clone ordinals: a copied group needs
the same number of copies of every incumbent member, so cloning household
10 without copying persons 1 and 2 would have been refused rather than
proving anything about anchors. The clone now carries its members, which
also lets the property assert the remapped memberships; the entrant
household still joins with none, which is allowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… plan

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the microcosm-us-launch-integration-20260909 branch from 83f2561 to 15ebde8 Compare September 13, 2026 20:39
MaxGhenis and others added 6 commits September 13, 2026 21:45
Merge origin/uk-full-build-graph-registration (051fb97) into the #893
integration head (2300e56) after taking origin/uk-context-framing-fix-20260913
(072c2e8, amendments 25 and 26).

Resolution rules: the graph package, its tests, the charter and the interface
lock are #918's; #901's own draft of WeightUpdate and the frame-context fields,
its test_weight_update.py and its charter section are dropped. US-owned tests
and tools/spec_seed_identity_diagnostics.py are #893's; #901's 10 September
review fixes to them (and the us_spine_stage_contracts.json fixture and the
VerifiedParameterAssets test) are superseded. UK files #901 rewrote into shims
or cut down are #901's. Stage roster is 29: #901's 28 plus frs_relationships
from #903; the retained-leaves / SPI pair stays retired.

Ports of HEAD-side UK changes into #901's modules: #903's frs_relationships
transform in uk_runtime/spine_build.py and its ons_household_type enum-domain
gate artifact through one shared uk_spine_gate_artifacts() used by the
spine-phase gate kernel, the full-build final gates and the driver test; #855's
hierarchy decode in graph_targets.registry_from_payload and on the local
target surface (_surface_records encodes it as TargetRegistry.to_json does,
_local_specs decodes it and keeps it out of metadata), so the schema-8
hierarchy the shared uk_local_target_surface now emits survives the surface
artifact and reaches diagnostics. Invented UK test registries take their
hierarchy from one shared tests/uk_hierarchy_fixtures.py helper.

Dropped run_graph(population_retention="lazy") from full_build_cli: the shared
executor on main has no such mode (see the #893 reconciliation record).

Pins regenerated on the merged tree: calibrate H1 parity, the H2 uk_spine.json
fixture, the UK country-bundle spec digest, the UK release-input coverage
manifest, APPROVED_UV_LOCK_SHA256 / LOCK_SHA256 for the merged uv.lock, and the
microcosm.graph/kernel.py contract in the US implementation inventory.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rtifact

TargetSpec.to_dict / from_dict are the single encoding for a spec carried by
any artifact: TargetRegistry.to_json / from_json and the UK full-build
target-registry artifact (graph_targets.registry_payload /
registry_from_payload) now share it instead of repeating the schema-8
hierarchy decode inline. to_dict is dataclasses.asdict, so registry JSON
bytes and content versions are unchanged; from_dict accepts the nested
hierarchy mapping, an already-decoded CalibrationHierarchy, or None, and
keeps the constructor's target-id check. Shared-package change (microcosm-
calibrate); flagged for the PR body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reconciliation onto the #893 head took #893's engine-parameter-file
admission and its test, and dropped this branch's VerifiedParameterAssets
approach with its test_spec_seed_identity_parameter_assets.py. The
spec-seed-diagnostics job still listed that file by name, so its bootstrap
step failed on collection. The five remaining files pass locally with the
job's flags (102 tests); every other hard-coded test path in the workflow
exists.

Co-Authored-By: Claude Fable 5.1 <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.

3 participants