Skip to content

Let anyone build, sign and certify a Microcosm UK cut - #1348

Draft
MaxGhenis wants to merge 15 commits into
mainfrom
uk-cut-anyone
Draft

MaxGhenis wants to merge 15 commits into
mainfrom
uk-cut-anyone

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Lets any team member or agent session build, sign and assemble a Microcosm UK national cut, and open its policyengine.py certification, without the one machine that held the key and some of the inputs. Every input is fetched by the pins the build already enforces. Draft until the independent review agrees.

What's in it

  1. Signing-key registry (microcosm.data.gate_signing_keys).
    • The gap. UK gate reports are HMAC-SHA256 signed, and every verifier trusted whichever key was in MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY.
    • Verification. The publisher's schema-4 checks (battery report, release-cut certification, dense report) now accept only a registered active or retired key.
    • Signing. Every UK signer requires an active one: the gate battery, the certifier, the scoped re-signer and the local preflight.
    • The national role checks its key before loading any input; a missing key used to fail only after the solve (runbook F8).
    • The registry holds one key per builder. HMAC verification needs the signer's secret, so a cut is signed, certified and published by one key holder.
    • Continuity. It starts with the key that signed every cut on record, 66bf0e29…. The team key dd31e325… is registered separately in Register a team UK gate signing key (hold: needs María's agreement) #1350, held for María's agreement.
    • CLI. python -m microcosm.data.gate_signing_keys {check,list,generate}.
  2. tools/run_uk_cut.py: one resumable command. fetch → preflight → spine → national (dry run first) → score (only if the in-build evaluation did not pass) → certify → stage-1 (diagnostic) → assemble → publish preflight → publish (opt-in).
    • Done-checks read the exact fields the tools write (build record, score receipt, certification, assembly summary), so re-running resumes.
    • Never destructive. It never rebuilds over a spine, overwrites a calibration run (it moves one aside only with --retry-national) or re-assembles a release.
    • Inspection only. --publish is always --no-latest --tag-only, so main's files and latest-*.json never move (runbook Q12, F14).
    • From main. It builds only from origin/main, unless --allow-off-main "<who agreed>".
  3. tools/uk_cut_inputs.json says only where each input lives. Digests come from source_stages.json, frs_release.json, chronicle_feed.json, gates.json and the mass-reference pin.
    • Verified here on 10/22:
      • FRS, SPI, WAS and ETB come byte-exact from the gated zips in policyengine-uk-data-private (all 18 tabs match their pins).
      • The HMRC ODS comes from gov.uk.
      • The incumbent eFRS comes from HF.
      • The input-mass reference comes from the new private policyengine/microcosm-uk-build-inputs. It was reproduced byte-for-byte to fd41cb5f… at microcosm 0eac9bbbd with policyengine-uk 2.89.0; today's engine emits 2 more columns, and the shared 131 totals are identical.
    • Pending:
      • NTS 2002-2024 and LCFS 2024-25 need a one-time upload by a UKDS licence holder (exact commands in the doc).
      • The Chronicle UK artifact is being rebuilt on Modal.
  4. tools/modal_rebuild_uk_chronicle_artifact.py rebuilds the pinned Chronicle UK artifact from the public repo, which needs no R2 credentials. It hosts the artifact privately only if both digests reproduce; otherwise it uploads diagnostics.
  5. tools/certify_uk_cut_in_policyengine_py.py: the policyengine.py certification handoff, as Net-new UK stages: UC deduction attributes, bus fares, WAS debt #685 did it by hand.
  6. docs/uk-cut-anyone.md documents all of this, plus key rotation and revocation and the regional routes, with a recommendation.

Regions (runbook Q13, María's call)

  • The blocker. The only dense cut is on 2.132.6, so policyengine.py refuses to keep area routing for a national cut on a newer policyengine-uk.
  • Measured cost of keeping it anyway (2.132.6 dense under 2.146.4):
    • no column is dropped on load;
    • PIP categories still load (they became formulas in 2.144.0, and stored values win);
    • 13 inputs default in area runs, including salary_sacrifice_returned_to_income and local_bus_uncapped_single_fare;
    • national figures are unaffected.
  • Recommendation:
    • an accepted, recorded mismatch (policyengine.py#699) for the Sat/Mon re-cuts;
    • a dense cut on the final version for the Budget pair, if it can be started by Monday; otherwise the accepted mismatch.

Invariants (tested)

  • Signing key:
    • An unregistered key never signs or verifies.
    • A registered key verifies only itself.
    • Active signs and verifies, retired only verifies, revoked does neither (Hypothesis).
    • A report signed under an active key passes the publisher's dense check, still passes once retired, and fails once revoked or unregistered (round trip).
    • The key behind every cut on record stays registered.
  • Fetch:
    • A file whose bytes miss its pin is never installed, and no partial file is left behind (Hypothesis).
    • A second fetch downloads nothing.
    • A corrupted installed file is replaced, never trusted.
    • Archive digests are checked when pinned.
  • Runner:
    • Each step runs once, and a second all runs nothing.
    • A failed step stops the run, and a rerun resumes at it without repeating finished steps.
    • A half-built spine is never rebuilt over.
    • A retried national run keeps the failed attempt.
    • A candidate that loses the incumbent score is never certified.
    • Every argv is accepted by its tool's own parser, or uses only flags its --help declares.
    • Publication is inspection-only for any tag.
  • Pins. The runner's pins are the build's pins (lockstep with chronicle_feed.json, frs_release.json, gates.json and source_stages.json).
  • Handoff:
    • Only cut tags are certified, never @main.
    • The constant rewrite touches quoted literals only, and is invertible.
    • The input-surface change is exact set algebra (Hypothesis).

Local results

  • New tests: registry 17 (microcosm-data) + 15 (microcosm-build), runner 27, handoff 13 (+3 properties), all passing.
  • Full engine-free suite (4 workers): 20,243 passed, 6 failed.
    • 1 was mine: the retired-package reference guard. It is fixed by allowlisting the inputs manifest and the doc, both of which name the gated input repo, as frs_release.json does.
    • 5 are environmental. 4 Orrery parser tests need the Orrery contract's node modules, which this checkout doesn't have installed, and 1 asserts the UK engine is absent, but this venv has the uk extra.

axiom: n/a: build infrastructure (no policy encoding)

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 22, 2026 07:07
Every UK verifier trusted whichever key was in the environment, so any
32-byte key that signed a report also verified it. Register the keys that
may sign (one per builder: HMAC means the verifier needs the signer's
secret, so a cut is signed, certified and published by one key holder),
and check the registry at every UK signer and verifier:

- the publisher's schema-4 checks (battery, certification, dense report)
  accept only active or retired keys;
- the gate battery, the certifier and the scoped re-signer sign only with
  an active key;
- the national role refuses an unusable key before loading inputs, not
  after the solve (runbook F8);
- the local release-candidate preflight names an unregistered key.

The registry starts with the key that signed every UK cut on record
(66bf0e29...). Tests keep their throwaway keys through a root-conftest
admission; tests marked committed_gate_key_registry see the real one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The national role now checks its key before loading inputs, so the tests
that drive a national build through stubs need one, as the others do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/run_uk_cut.py runs fetch -> preflight -> spine -> national (dry run
first) -> score (only when the in-build evaluation did not pass) ->
certify -> stage-1 comparison (diagnostic) -> assemble -> inspection
publish, from a clean checkout of main. Each step has a done-check over
its real outputs, so `all` resumes where it stopped and never rebuilds
over a spine, overwrites a calibration run or re-assembles a release.
Publication is opt-in (--publish) and always --no-latest --tag-only, so
main's files and the latest-*.json pointers never move (runbook Q12/F14).

Inputs come from tools/uk_cut_inputs.json, which says only where each
input lives; every digest is read from the pins the build itself checks
(source_stages.json, frs_release.json, chronicle_feed.json, gates.json).
FRS, SPI, WAS and ETB come byte-exact from the gated UKDS zips already in
policyengine-uk-data-private; NTS 2002-2024 and LCFS 2024-25 are declared
there pending a one-time upload. The Chronicle artifact and the input-mass
reference come from the new private dataset
policyengine/microcosm-uk-build-inputs.

tools/modal_rebuild_uk_chronicle_artifact.py rebuilds the Chronicle UK
artifact from the public repo on Modal and uploads it only if its
digests reproduce the pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Does what policyengine.py#685 did by hand, from one command, on a fresh
branch from policyengine.py's main: one-writer check, regional-route
pre-check (refuses a dense cut on another policyengine-uk before anything
is written, naming the routes), certify-data at the cut's own model and
core, uv lock + release_lock, generate --include-tros, policyengine.py's
own certified-constant rewrite plus the local-area tag, snapshot refresh,
check --published-spm --include-tros --strict-tros, the UC floor and the
tests. It commits; it pushes and opens a draft only when asked, and never
merges or releases. The PR body lists the judgment calls (UC floor
acceptance, dropped overlays, warnings, failing tests) as a checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 22, 2026 07:41
docs/uk-cut-anyone.md: the one command, what it never does, where every
input lives and how each missing one gets hosted (NTS/LCFS upload, the
Chronicle rebuild, the mass-reference recipe), the signing-key registry
with rotation and revocation, the policyengine.py handoff, and the four
regional routes. CLAUDE.md points at it from the release boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/uk_cut_inputs.json names policyengine-uk-data-private, which hosts the
licensed UKDS archives and the sha-pinned incumbent, for the reason
uk/frs_release.json already does: a download location checked against
committed pins, never an import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 8 commits October 22, 2026 08:02
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the certification handoff accepts a local-area cut built on another
policyengine-uk, it now lists the regional file's input columns the newer
model no longer defines (ignored on load; refused unless
--allow-dropped-columns) and the newer model's inputs the file cannot carry
(defaulted), and puts both in the pull request. A built input that became a
formula is in neither: its stored values still load. Property tests pin
the set algebra.

Measured for the 2.132.6 dense cut under 2.146.4: nothing dropped; 13
inputs defaulted, among them salary_sacrifice_returned_to_income and
local_bus_uncapped_single_fare. The doc records this and the regional
recommendation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repo's addopts already pass -q, so the summary line can be absent and
the check printed pytest's footer instead of a result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The schema-3 terminal-report check (exact-K release ids) verified under
whatever key POPULACE_UK_TERMINAL_GATE_SIGNING_KEY held. No schema-3
report is published (every UK terminal report on the Hub is schema 4,
signed by 66bf0e29...), so binding it to the registry breaks nothing and
leaves no UK verifier that trusts an unregistered key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Hosted files are downloaded beside their destination and moved in after
  verification instead of copied out of the Hub cache, so the 7.4 GB
  Chronicle artifact does not occupy the disk twice.
- The national dry run is skipped only when it finished, not whenever the
  state file mentions it.
- The free-disk check walks up to an existing ancestor of the work dir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/modal_uk_cut.py clones a pushed commit on main, syncs its lock with
the uk extra and runs tools/run_uk_cut.py all with the work directory on
a Modal volume, passing HF_TOKEN and the signing key to that run only.
Plan-only by default; processing UKDS data there needs the data owners'
OK first (the doc says so).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilt on Modal from public Chronicle at abf0921: both the facts
(2b64fecf...) and manifest (b90fb1b8...) digests reproduce the committed
feed pin byte for byte, so the rebuild is deterministic across machines
and needs no R2 credentials. Hosted in policyengine/microcosm-uk-build-inputs
at d4c8b191; the bundle took 2h24m and peaked at 35.5 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant