Repository navigation
Let anyone build, sign and certify a Microcosm UK cut - #1405
juaristi22 wants to merge 60 commits into
Conversation
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>
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>
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>
The UK hub's rehearsal patches (the 13 commits above) were validated under a different Ruff configuration: sort one import block and bind the resume-validation fake's loop variables as defaults (B023). Their load and disk refusal thresholds were the shared build host's constants; they are now defaults the operator can change (--max-load1, --min-free-gb), so the tool also runs on a builder's laptop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review finding: the check compared two models' input lists, but Microcosm stores formula variables too (is_uc_claimant, is_claimant_or_partner...), so a newer model removing a stored formula variable went unreported, and a stored formula-to-input variable was wrongly listed as defaulted. The handoff now downloads the regional cut's microdata at its manifest pin, checks its sha256, and reads only the column names (pandas table-format entity tables, or the flat per-variable layout; anything else refused). dropped = stored columns the build model defined and the certified model does not; defaulted = certified inputs the file does not store that were not build-model inputs. Measured on the 2.132.6 dense cut under 2.147.2: nothing dropped, 16 defaulted, matching the UK hub's independent probe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96a9314 spliced the new NTS and LCFS section into the middle of the LCFS row and left a fragment of the old upload paragraph behind. Restore the row and drop the fragment; the wording is otherwise unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The doc now says who may run a cut (UKDS licence cover), that the runner's first real run is the last check before anyone relies on it, and that a 2.151.0 national and dense pair shipped together; the dated pre-Budget route plan and its runbook pointer are gone. The changelog fragments name the runner's signer binding, Logbook chaining and Orrery guard, and the certifier's operator-set limits and credits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi, could you review this PR? It takes over #1348 and #1350, and the description lists what changed since María's review. Before your verification run we also need your own signing key. The registry gives each builder their own key, so nobody shares a secret. Here's how to make one:
I'll then register it as Once that's in, you can run the recipe under Verification in the description from your laptop. Export the key with: export MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY="$(security find-generic-password -a "$USER" -s microcosm-uk-gate-signing-key -w)"
uv run --no-sync python -m microcosm.data.gate_signing_keys check # ok: uk-gate-vahid-ahmadi-2026, activeThe run needs:
Run it without 🤖 Generated with Claude Code |
The 272c987 consumer artifact that main's uk/chronicle_feed.json pins is now in policyengine/microcosm-uk-build-inputs (Hub commit 5d28b8f0), uploaded unchanged from the build microcosm#1359 pinned. Both files match the pin, and the runner's fetch verifies them from the derived path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@juaristi22 round 1, at Verdict: the registry and the runner are sound and the takeover is faithful, but don't merge yet: one must (the accepted-mismatch route) and five shoulds, mostly about where the signing key and the UKDS inputs travel. What holds up
Findings
Takeover and earlier reviews
CI
What I ran
What I didn't read or run
Vahid will generate his signing key himself and post only the fingerprint here separately. 🤖 Generated with Claude Code |
…evidence policyengine.py#699 accepts a regional cut built on another model only with --regional-input-surface-evidence from its own scripts/audit_regional_input_surface.py, so the tool's route refused after the worktree and sync (review of #1348, finding 1). The tool's own comparison is replaced by that audit, as the ruling there asked: - before the worktree, it refuses a base without the audit and downloads every regional microdata H5 at its manifest pin, checked by sha256; - in the worktree, it installs the target policyengine-uk and core, checks the audit's flags, runs it, and refuses evidence that is not build-relative (schema 2) or that drops a stored column; - certify-data then gets the evidence with the accepted reason, and the PR body lists the defaulted inputs, the never-modelled count and the limitations. --audited-by and --regional-limitation are required with a mismatch, and --allow-dropped-columns is gone. policyengine.py commands no longer inherit the caller's UV_PROJECT_ENVIRONMENT or VIRTUAL_ENV. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every runner step and preflight test, and every policyengine.py command the certifier ran, inherited MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY, including uv installs and policyengine.py's test suite (Vahid's review of #1405, item 4). The runner now passes it only to the spine, national build and certification, which sign, and to assembly and publication, which verify; the national dry run, the score, the stage-1 comparison and the preflight test runs get no key. policyengine.py reads no Microcosm key, so the certifier withholds it from all its commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`generate` printed the key on stdout, where an operator running it in a terminal kept it in scrollback and agent transcripts, and the documented keychain recipe put it in `security`'s argv, visible to `ps` (Vahid's review of #1405, items 3 and 8). `generate --keychain-service NAME` now stores the key in the macOS login keychain through `security -i` on stdin, reads it back to confirm, and prints only the fingerprint; without it, `generate` refuses to write a key to a terminal and still pipes it to a secret store otherwise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`modal run` creates the app, with its secrets, before the entrypoint runs, so the documented "plan only" call also sent HF_TOKEN and the signing key to Modal (Vahid's review of #1405, item 5). Planning is now plain `python tools/modal_uk_cut.py`, which contacts no Modal service. The launcher attaches credentials only when MICROCOSM_UK_CUT_MODAL_RUN=1, and the entrypoint refuses anything but that with --run. The docstring says what Modal holds: the ephemeral secret for the run, the volume's UKDS files for anyone in the workspace, and the key's custody. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Following Vahid's review of #1405 (items 2, 3, 5 and 6): - Signing keys: an HMAC signature is checkable only by a key holder, a retired key's holder can still mint reports that verify, and revocation binds only checkouts that include it; a public-key scheme is tracked separately. - The key recipes use `generate --keychain-service` and pass the key to one command instead of exporting it to the shell. - Modal: what a run sends and holds (the ephemeral secret, the volume's UKDS files readable across the workspace, the key's custody), and that planning is plain Python. - The input repository with the UKDS zips is gated, not private: its file list is public, and access approval is what admits a builder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi thanks for the review. Here's round 1 point by point; the head is now
On the takeover. The 13 CI. The run on the new head is in progress. I'll re-run any The touched suites pass locally: registry, runner, Modal stub, both certify suites and the local preflight. 🤖 Generated with Claude Code |
policyengine.py#699, after its own review, refuses a mismatch reason, auditor or limitation without a printable non-space character, such as a zero-width space alone, but only after the worktree, the sync and the audit have run. The certifier now applies the same test at parse time, to every --regional-limitation. The doc notes that certify-data, too, now needs uv and PyPI, since it re-reads both models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi, a follow-up on round 1. The head is now
The route still waits on #699 merging. Your fingerprint is all that's left before the verification run. 🤖 Generated with Claude Code |
|
@juaristi22 round 2, at Verdict: the must is fixed, and so are four of the five shoulds. The fifth, the gated repo, is now documented and left to the repo owners. Two new shoulds are small. One licence question is for Vahid and Max, not for this PR. The route still waits on policyengine.py#699 merging, and CI was still running when I looked. Round 1, item by item
Key storage (833ea03)I read this code and didn't run it. No key was generated, and no keychain was touched.
So the key never appears in argv, in an environment dump, in a log or in a file. One problem: A. should: One fact for the doc, not a defect: an item that Gated, not private: for Vahid and MaxThese are the facts. Read anonymously, Whether that is enough for UKDS End User Licence data is a licence question. Vahid and Max should decide it with whoever holds the UKDS licences, not this PR. Two points to weigh: public file names that show which UKDS studies PolicyEngine holds, and access that depends on a manual approval step with no written check. The doc now describes the setup accurately, so nothing here blocks the PR. New findingsB. should: C. nit: the opt-in variable alone attaches the secrets, without D. nit: secrets still reach third-party code in two places.
E. nit: the Modal refusal says CIRun 40404384382 on What I ran
Not approving here. Vahid will post his fingerprint separately. 🤖 Generated with Claude Code |
`generate --keychain-service` added the key with `security add-generic-password -U`, which silently replaces an existing item, and the doc's rotation steps reused the same service name, so rotating, or running it twice, lost the old key and left its cuts unverifiable (Vahid's round 2 on #1405, item A). It now refuses when the service already holds a key and adds without -U. The doc stores each rotated key under a new service name, and says the login keychain shields a key from other users, not from the user's own processes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…upload The rebuild tool attached HF_TOKEN, which can write to the private repositories, whenever it was set, so even its "plan (free)" modal run sent the token to Modal (Vahid's round 2 on #1405, item B). Planning is now plain Python, the token is attached only with MICROCOSM_UK_CHRONICLE_MODAL_UPLOAD=1, and a rebuild without --upload sends no credential. For the cut wrapper: - A refusal after the opt-in now says the credentials were already sent (item C). - Missing credentials are asked for one command, not exported (item E). - The container's uv sync runs without HF_TOKEN or the signing key (item D). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The certifier gave HF_TOKEN and HUGGING_FACE_TOKEN, which can write to the private repositories, to every policyengine.py command, including uv sync, uv lock and uv pip install (Vahid's round 2 on #1405, item D). Those steps now run without any Hugging Face token. policyengine.py's own scripts and tests keep them, since its audit, certify-data, UC floor and dataset tests read the private cut repositories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi, round 2 is addressed; the head is now
Locally, the touched suites pass: both registry suites, runner, the Modal stub for both tools, both certify suites, and the local preflight. CI is running on the new head, and I'll hold further pushes until your fingerprint, so it can finish. For the verification run, these are in place:
Two things to check on your side first:
🤖 Generated with Claude Code |
|
@juaristi22 my signing key is generated and stored in my login keychain, under service Please register it as 🤖 Generated with Claude Code |
uk-gate-vahid-ahmadi-2026 (e9ab40bd…), active, held in his laptop's login keychain, so he can build, sign and verify the runner's first real cut (#1405). He generated it with `generate --keychain-service` and posted only the fingerprint. The other two keys are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@vahid-ahmadi, your key is registered as I checked the inputs on the Hub without downloading anything. Every pinned archive is at its revision, and the Hub's own sha256 for it equals the pin. That covers:
The HMRC workbook is still served by gov.uk at its pinned size. Access to the gated The recipe is under Verification in the description:
Please report only which steps passed and how long each took, from CI restarted on the new head. If an 🤖 Generated with Claude Code |
|
@vahid-ahmadi, here is how to run the verification build from your laptop. It is the runner's first real end-to-end run, and the last check before #1405 merges. What to expect
Steps1. Check out this branch, clean, and sync it. git clone https://github.com/PolicyEngine/microcosm.git ~/microcosm-verify && cd ~/microcosm-verify
git fetch origin claude/uk-cut-anyone-takeover-974500
git switch --detach FETCH_HEAD
git log -1 --format=%h # expect 928874f14 or later
uv sync --all-packages --locked --extra uk2. Check the key with MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY="$(security find-generic-password -a "$USER" -s microcosm-uk-gate-signing-key -w)" \
uv run --no-sync python -m microcosm.data.gate_signing_keys check3. Fetch the inputs. This downloads about 9 GB and verifies every file against its pin. Every line should say HF_TOKEN="$(cat ~/.cache/huggingface/token)" \
uv run --no-sync python tools/run_uk_cut.py fetch --work-dir ~/microcosm-uk-work4. Run the cut.
HF_TOKEN="$(cat ~/.cache/huggingface/token)" \
MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY="$(security find-generic-password -a "$USER" -s microcosm-uk-gate-signing-key -w)" \
caffeinate -ims uv run --no-sync python tools/run_uk_cut.py all \
--label verify-1405 --work-dir ~/microcosm-uk-work \
--allow-off-main "María Juaristi: verification run before merging #1405"
5. If a step fails, fix the cause and run the same command again. It resumes at the failed step without repeating finished ones, and never overwrites a finished output.
6. Report back. When it finishes, it prints the assembled cut's tag and the receipt path. Post here only this: uv run --no-sync python tools/run_uk_cut.py status --label verify-1405 --work-dir ~/microcosm-uk-work
python3 -c "import json,sys; r=json.load(open(sys.argv[1])); [print(k, s.get('status'), s.get('seconds')) for k, s in r['steps'].items()]" \
~/microcosm-uk-work/cuts/verify-1405-$(git rev-parse --short=9 HEAD)/cut_receipt.jsonThat is which steps passed and how long each took. Please post no figures from the data: no gate values, totals or file contents. If something fails, post the step name and the runner's 🤖 Generated with Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@MaxGhenis, could you run the verification build for this PR on your build Mac? Vahid's laptop is too loaded to take a 3-hour run. This is the runner's first real end-to-end cut and the last check before #1405 merges. It will also be the first cut signed with the team key, What to expect
Steps1. Check out this branch, clean, and sync it. git clone https://github.com/PolicyEngine/microcosm.git ~/microcosm-verify && cd ~/microcosm-verify
git fetch origin claude/uk-cut-anyone-takeover-974500
git switch --detach FETCH_HEAD
git log -1 --format=%h # expect 17e8147cd or later
uv sync --all-packages --locked --extra uk2. Check the team key with MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY="$(agent-secret get microcosm/uk-gate-signing-key-team maxghenis)" \
uv run --no-sync python -m microcosm.data.gate_signing_keys check3. Fetch the inputs. This downloads about 9 GB and verifies each file against its pin; every line should say HF_TOKEN="$(agent-secret get HUGGING_FACE_TOKEN_MAX maxghenis)" \
uv run --no-sync python tools/run_uk_cut.py fetch --work-dir ~/microcosm-uk-work4. Run the cut.
HF_TOKEN="$(agent-secret get HUGGING_FACE_TOKEN_MAX maxghenis)" \
MICROCOSM_UK_TERMINAL_GATE_SIGNING_KEY="$(agent-secret get microcosm/uk-gate-signing-key-team maxghenis)" \
caffeinate -ims uv run --no-sync python tools/run_uk_cut.py all \
--label verify-1405 --work-dir ~/microcosm-uk-work \
--allow-off-main "María Juaristi: verification run before merging #1405"5. If a step fails, fix the cause and run the same command again. It resumes at the failed step without repeating finished ones.
6. Report back here, with only which steps passed and how long each took: uv run --no-sync python tools/run_uk_cut.py status --label verify-1405 --work-dir ~/microcosm-uk-work
python3 -c "import json,sys; r=json.load(open(sys.argv[1])); [print(k, s.get('status'), s.get('seconds')) for k, s in r['steps'].items()]" \
~/microcosm-uk-work/cuts/verify-1405-$(git rev-parse --short=9 HEAD)/cut_receipt.jsonPlease post no figures from the data. If something fails, post the step name and the runner's 🤖 Generated with Claude Code |
|
@juaristi22 verification run at
The refusal: I've left the constant values out. Both columns look like they come from the alcohol inputs in #1381, which is in this branch through the main merge. If so, a cut from main may hit the same gate, not just this PR. 🤖 Generated with Claude Code |
|
@juaristi22 a summary of what the verification run did, to go with the step times above. Built on my laptop (about 12.5 GB, in
What left the laptop: only what the national step stages for every cut.
What it shows:
🤖 Generated with Claude Code |
Takes over #1348 and #1350 as one PR, as Max asked. It carries all 31 of #1348's commits and #1350's one commit, with their authorship kept, so it supersedes both.
What it carries
microcosm.data.gate_signing_keys;tools/run_uk_cut.pyandtools/uk_cut_inputs.json;tools/modal_uk_cut.pyandtools/modal_rebuild_uk_chronicle_artifact.py;tools/certify_uk_cut_in_policyengine_py.py;docs/uk-cut-anyone.md.uk-gate-team-2026(dd31e325…).What changed since
María's review of #1348 (25 Oct)
scripts/audit_regional_input_surface.pyreplaces this tool's own comparison.--regional-input-surface-evidencetocertify-data, and the PR body lists what area runs default.--audited-byand--regional-limitationare now required with a mismatch, and--allow-dropped-columnsis gone.UV_PROJECT_ENVIRONMENT.attestation.signing_key_sha256from the reports of finished steps. It refuses any other key and names the signer.certify-uk-<id>anywhere in a remote branch's name or an open PR's head, socertify-uk-a133f37a-nationalis caught;allend to end: Vahid's run; see Verification.UK_GATE_SIGNING_KEY_ENVis imported, not copied (fc288e1).--reservation-toolwith--reservation-caller), and a named tool that is missing fails.--max-load1sets one. Its limits pass as keyword arguments throughresource_guard,Shellandmodel_surface.--pepy-repois now required.--co-authorand--pr-body-footer.Also
559b69e9and11051052), with their archive digests checked. Every hosted archive must now carry both pins. The doc notes that the LCFS zip is a repack of the extracted files.held_innames his agent store and says agent sessions on his build Mac may read it. María's key66bf0e29…stays active.chronicle_feed.json(b769292). It readschronicle-uk/<source commit>/at main and checks it against the feed's digests, so a feed re-pin no longer breaks the runner.5d28b8f0), uploaded unchanged from the build UK: impute alcohol, tobacco and gambling consumption so policyengine-uk's excise duties stop scoring zero #1359 pinned. The runner'sfetch --only chronicle_ukfetched and verified both files from the derived path.--no-publish-orrery, as the hand-run launchers do.Since Vahid's round 1 (at
6c6039941)generate --keychain-service NAMEstores the key throughsecurity -ion stdin, reads it back and prints only the fingerprint, sofingerprint.txtis no longer needed. Plaingeneraterefuses to print a key to a terminal. The doc and this description now pass the key to one command at a time instead of exporting it.modal runattaches credentials only withMICROCOSM_UK_CUT_MODAL_RUN=1 … --run. The doc says what a run puts on Modal: the ephemeral secret, the volume (readable across the workspace), and the key's custody.policyengine-uk-data-privateis gated, with a public file list, and that approving access is what admits a builder.Tests
On this head, 850 tests pass locally and none fail. They cover:
Lint and format are clean. The full suite runs in CI.
Verification
The last check before merging is the runner's first real run, which Vahid Ahmadi makes from his laptop with his own key.
What it needs:
policyengine/populace-uk-privateandpolicyengine/populace-uk-staging;Leave out
--publish. Report back which steps passed and how long each took, fromstatusand thestepsincut_receipt.json, but no figures. Outputs stay on the laptop.What's left
f14690ee. Until then a base without the audit is refused before anything is downloaded.The Modal wrappers have still never run on Modal; a separate plan replaces them.
Supersedes #1348 and #1350.
axiom: n/a: build infrastructure (no policy encoding)
🤖 Generated with Claude Code