Repository navigation
Replay Autumn Budget 2025 as the sixth event - #175
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Registry (105 measures, 1,512 source rows, exact Decimal accounting): the OBR costings lane's eight reviewed reversals (#56) matched by PMD title, #136's high-value council tax surcharge reversal as a dated window, and #136's measure keys from the replay inventory, which the scorecard DB ingest joins on. Each construction commences in the first year its reversal windows read. Fuel duty and the UC standard allowance/health element protections are named construction_pending gaps with their sources cited. - Infinite engine values (the abolished two-child limit) are named "inf" in the registry, as the premises audit names them. - The qualifier, Modal runner and CI guards list the event; the corpus version is uk-fiscal-event-replay/2, documented as naming the event set. - A sealed namespace's premises cover the events its run replayed: the qualifier passes its own events, and CI regenerates a sealed namespace's premises for its receipt's events. Both committed unsealed namespaces' premises are regenerated with the sixth event. - Tests: every replayed event agrees with the inventory cell for cell (within a penny); the AB2025 builder output is pinned against the committed registry; keys, gaps, the surcharge conversion and its refusals; property tests for json_scalar and first_window_year. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The summary lists every registry on disk; Autumn Budget 2025 appears as a seeded registry without a numerical replay. Sealed summaries are bound by their manifests and are not re-rendered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…last A release diff records each side's premises digest, and a sealed namespace's manifest binds its premises and summary. Regenerating them for every registered event would change those bytes when an event is added, so a namespace's premises and SUMMARY.md now cover the events it holds results for (an event directory with RUN_MANIFEST.json), or every registered event when it holds none. The legacy namespace keeps its five-event bytes; the 6.2.6 namespace, which holds no results on main, covers all six. The qualifier wrote SUMMARY.md before MODEL_DIAGNOSTICS.json existed, so CI's regeneration of a sealed namespace rendered a different summary (why #164's replay-engine and pytest checks fail, and why 6.4.1 attempt 2 cannot be committed as sealed). It now writes the summary last. Tests: namespace_events property test (held events in order, or all; directories without a manifest and unregistered names hold nothing); the summary's registry scoping; the qualifier passes --namespace and writes the summary after diagnostics. Each mutant of the three fails a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
w6/ab2025-event
branch
from
October 15, 2026 15:11
03c8169 to
4696e92
Compare
… theirs The scorecard DB ingest keys published OBR PMD scores by the replay registry's measure key. 17 AB2025 measures had no #136 key and got the builder's slug-and-digest key, which re-keyed their already-published scores (CI's populations export test caught the changed event archive). Those measures now take the inventory's own measure_key, so all 105 keys equal the inventory's and a from-scratch DB build exports the AB2025 archive byte for byte. The 6.2.6 premises are regenerated for the keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d6442 - Blocking: a sealed namespace whose events all failed before writing a run manifest fell back to every registered event, so its premises and summary would drift when another event was registered. A qualification run now writes CORPUS.json (corpus version and events) before any event runs, and uk_replay_variants.namespace_events decides from it first, then from held run manifests, then from every registered replay event. The premises audit and the summary both use it; an unregistered event in CORPUS.json is refused. - The legacy premises engine test regenerates with namespace_events. - The HVCTS test pins the complete converted specification against #136. - The Carer's Allowance overpayment review has its own administrative gap reason (model_or_data_gap); the 6.2.6 premises are regenerated for the AB2025 registry digest. Tests: namespace_events with a corpus record, held manifests and none; the qualifier writes the corpus before its first command. Mutants (corpus ignored, not written, unregistered accepted) each fail a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merge audit (W6), head cd7018f.
🤖 Generated with Claude Code |
MaxGhenis
marked this pull request as ready for review
October 16, 2026 03:10
MaxGhenis
added a commit
that referenced
this pull request
Oct 16, 2026
…183 (#187) From the independent reviews that approved #175 (cd7018f) and #183 (cb8d33f): - Tests pin the recorded-corpus rule at the summary and the premises audit, not only in namespace_events: registering another event changes neither. Reverting either to the held/all rule now fails a test (both survived). - verify-sealed treats CORPUS.json as evidence of a run, so a committed namespace whose events all failed must be sealed. - base_event refuses an extra property (the one surviving schema mutant). - The HVCTS test asserts #136's years are one contiguous run. - Stale wording in ci.yml, the audit's --namespace help and run_corpus now names the corpus record; the DIFF.json grids paragraph sits with the diff's outputs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 16, 2026
* Catch up on policyengine.py releases whose dispatch never arrived
A missing GitHub App, an outage or a cancelled run can lose W4's
post-release dispatch. The scheduled receiver run now first looks at PyPI:
for every policyengine release newer than the newest certified replay
variant that no request names yet (oldest first, at most three a run), it
builds a released_bundle request from primary artifacts and queues it.
pipeline/uk_replay_discover.py checks the wrapper wheel against PyPI's
digest, reads the packaged bundle manifest and the uk extra's pins, requires
the manifest's model wheel digest to equal PyPI's, takes the core digest
from PyPI, and checks the data release manifest certifies the bundle's
dataset (its SHA-256 and size come from there). The expected release bundle
is read from a scratch install with tokens removed. Rebuilt for 6.2.6, the
candidate block equals the request built by hand from the same sources.
The workflow's state checkout and enqueue steps move to shared scripts;
the drain tolerates a state branch that exists only locally.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal replay: at most two simulations across invocations
Each Modal invocation runs one replay worker, and separate invocations
shared no limit, so a third concurrent run would exceed the two the shared
account allows. Before app.run(), an invocation now takes one of two slots
in the Modal Dict scorecard-replay-simulation-slots: put(skip_if_exists=True)
is an atomic create-if-absent on the server. A lease outlasts the function
timeout; an expired one is reclaimed by whoever first creates its one-time
reclaim token. The slot is released in a finally, interrupts included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: generation claims, renewal before the run, a remote guard
Review of 14d6dab found three ways the two-slot lease could fail: a
reclaimer dying between creating its one-time token and taking the slot lost
that slot for good; a holder past its lease could pop a newer claim in
release, and a reclaimer's unconditional pop could delete a claim made after
it looked, each allowing a third simulation; and the property test asserted
something false once 24 ticks passed.
Every change of holder, and every renewal, now creates the slot's next
generation key with put(skip_if_exists=True), Modal's atomic
create-if-absent; nothing is popped, and a holder writes only its own key,
only to release or shorten it. A head key names the newest generation as a
hint (readers walk forward), and a random chain id separates keys written
after Modal expires idle entries.
main() renews the lease after the image build, just before the remote call,
and the remote function refuses to start unless its 3-hour timeout fits in
the lease, so a run ends before its slot can pass on even if the client
dies. A renewal after the lease lapsed is refused (exit 75). After ctrl-C,
Modal's client asks the server to stop the app's tasks
(modal/runner.py _disconnect), and the slot frees 10 minutes later instead
of at once.
Tests drive the protocol call by call: the review's three scenarios,
a holder dying after each possible step, a stale renewal after Modal
expired the chain, and a Hypothesis model check interleaving 2-4 holders'
Dict calls with clock jumps, crashes, interrupts and 7-day entry expiry
(no two runs overlap on a slot; both slots are free again once every lease
lapses). 20,000 generated schedules hold; each protection's deterministic
test fails when that protection is removed. Assumption, stated in the
docstring and RECIPE: no process stalls for days inside one protocol step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: hold the claim when Modal was not told to stop the run
Review of dff81d0:
- Modal's runner re-raises a lost connection (modal.exception
.ConnectionError) without sending its stop request (modal/runner.py,
the ConnectionError branch of _run_app), yet main() freed the slot ten
minutes later while the container could run for three hours. The grace
now applies only when app.run() itself exits after a caught ctrl-C, where
the runner awaited the stop request; any other exception after the run
started leaves the claim until the lease lapses, which the remote guard
puts after the run's end. A remote error now holds the slot until then
too (conservative).
- A writer stalled across Modal's 7-day expiry could overwrite head-{slot}
with its dead chain and hide a live holder. A protocol step (acquire or
renew) that took over a day now gives its claim back and writes no head.
- The remote guard keeps a 10-minute margin inside the lease for clock
differences and kill latency.
- The model check now caps stalls per protocol step, not per tick, and
models a lost connection (the run continues to its own end, nothing is
written). New tests: a week-stalled acquire cannot replace a live chain;
a lost connection keeps the claim at its full lease; a finished run frees
the slot at once; the stale-renewal scenario now asserts that the key
it would recreate is absent. 20,000 generated schedules hold; the head
test fails when the stale-step bail is removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: say exactly what holds under week-long stalls
Review of 4a5a272:
- The step's clock was read after the head response, so a response left
unread through a week asleep looked fresh. It is now read before the
first call.
- A head write landing a week late, after its keys expired, could point
readers at a dead chain and hide a live holder. A reader whose head names
a claim that is gone now blocks the slot for one lease (any hidden
holder's lease began before that write) instead of taking it.
- A claim whose head write fails is given back at once, on acquire and on
renewal, instead of holding the slot for a lease with no run.
- A result that returned is downloaded even if leaving the app then fails.
- The docstring and RECIPE now state the guarantee against Modal's 7-day
entry expiry, the two detected long-stall cases, and the one case not
covered (a head write 7+ days late naming a generation that still exists
while later ones expired), instead of an unconditional claim.
Tests: a head response read before a week asleep (with and without a
claim), a head written a week late, a failed head write on acquire and
renewal, a returned result surviving a failed app exit; the model's final
phase allows one blocked lease. Each new protection's test fails when the
protection is removed; 20,000 generated schedules hold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: day-scoped claim chains on Modal's clock
Review of bac8974 reproduced three failures by running the code:
1. A head write delayed 6.5 days plus a reader stalled 0.95 days recreated
expired generations and overlapped a live run, inside the guarantee as
stated. Any pointer can be rolled back by a late write, so the pointer
is gone. A slot now has one chain of claims per calendar day
(claim-{slot}-{day}-{generation}); a caller creates a chain's next
generation, with Modal's atomic create-if-absent, only when its newest
one is released or lapsed. A lease that crosses midnight holds a claim
in both days' chains, taken in order and given back together. A day's
chain matters for at most a day plus a lease, far inside the 7 days
after which Modal expires idle entries, so no claim that still matters
can have expired and a late write lands in a chain that governs
nothing. The guarantee no longer assumes anything about stalls. Head
keys, chain ids, the blocker claim, the stale-step bail and renewal are
removed.
2. A contender whose clock ran five hours fast took an occupied slot.
Leases now run on Modal's clock: main() measures its offset with a
small remote function, checks it again after waiting for a slot (drift
over 30 s stops the run, exit 75), and a lapsed claim is taken only
2 minutes past its expiry.
3. A Dict failure at release discarded a completed result. A failed
release is now reported and the result is still downloaded; the claim
lapses.
The slot is taken inside app.run(), after the image build, so a build no
longer consumes lease time.
Tests: the model check now has no stall cap and gives each holder its own
clock within the enforced drift, with jumps of any size (including 6.5 and
8 days) at any point, midnight crossings, crashes, interrupts, lost
connections and entry expiry; 20,000 generated schedules hold. New
deterministic tests cover the clock margin, a lease across midnight, a
refused second day, a step that outlasts its lease, the reviewer's late
write and five-hour-fast-clock scenarios, the offset measurement, a failed
release, and a clock that moved while waiting. Removing the takeover
margin, the newest-claim check, the second day's claim or the
time-left check each fails a deterministic test.
Verified on Modal: a real --preflight-only invocation ran the clock
function, took generation 0 in today's and tomorrow's chains (the lease
crossed UTC midnight), passed the remote guard and released both claims.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: Modal's clock for every decision; no early free on interrupt
Review of 3e058b5:
1. After ctrl-C the claim was shortened to 10 minutes, but Modal's
AppClientDisconnect returns without confirming the task stopped, so a
successor could start while the run continued. An interrupted run now
writes nothing: its claim lapses with its lease, which the remote guard
puts after the run's end. The grace and its constant are gone.
2. A local clock that jumped between the two offset measurements could
create an unsafe claim before the drift was detected. The slots now
read the time from Modal (the modal_time remote function) for every
decision, so this machine's clock plays no part; the offset measurement
and drift check are removed.
3. The week-late-write regression asserted nothing once the protected
holder had expired. It now resumes the stalled contender while the
current holder's run is going and asserts the slot stays its; it fails
when the newest-claim check is removed.
The model check gives each holder a Modal container clock within 30 s of
true time (the takeover margin is 120 s) and treats ctrl-C like a lost
connection; 20,000 generated schedules hold. A new test runs main() with
this machine's clock five hours fast and checks every claim carries
Modal's time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: answer the review of 2d727d8
Tests that can fail:
- main()'s clock test now fails if this machine's clock is read at all,
and seeds a lapsed claim (taken over) and a live one (refused), so an
offset from the local clock is caught too (B1).
- The worst clock corner (guard container 30 s slow, reading container
30 s fast, lease across midnight or not) is computed on the real class:
the takeover comes GUARD + CLOCK margin - 2 x skew = 660 s after the
run's end, and the same schedule overlaps by 60 s with the margins
removed (B2).
- The model check draws every clock read within the skew independently
(holders' Modal time and each run's guard), can jump to just around a
started lease's expiry, start a run as late as its guard allows and run
its whole timeout, use one slot or two, and drive one holder through its
calls, polls and start in a burst. A derandomised test asserts it finds
the overlap when both margins are removed (78-1,487 examples over 12
random seeds; budget 5,000). With the real margins it held for 30,000
examples (B2).
- A week-late release onto an expired, re-created key frees nothing for a
fresh contender (B3).
- A failed time-left read gives the claims back (a test that fails on the
third clock read, after a claim exists).
- Regressions fail instead of hanging: tests that expect a grant pass
wait_seconds=0, main()'s clock test also fails on any real sleep
(acquire() now looks up time.sleep when called), and the corner search
is bounded.
Mutations (each run alone, restored): an offset or local clock in main(),
zeroed margins, keys without the day, no newest-claim check, no lapsing,
only the first day's chain, no time-left check, the free check on
generation 0, release after interrupt, a raising guard, and the time-left
read outside the give-back path are all caught. Removing the epoch padding
is not, and stays safe by the reviewer's argument.
Code and text:
- CLOCK_SKEW_SECONDS states the 30 s clock assumption; the docstring and
RECIPE state it and the shared Modal workspace/environment assumption,
and drop "or shorten" (no holder shortens a claim any more).
- The dead LeaseLost is gone; a 6-hour wait without a slot raises
NoSlotFree (a TimeoutError) and main() exits 75 holding nothing.
- The final time-left clock read is inside the give-back path, so a failed
read returns the claims.
- The remote guard returns its refusal instead of raising, so main() knows
nothing ran, frees the slot at once and exits 75.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Modal slots: a guard refusal keeps the slot (review of 32d6e6f)
N1: a refusal does not show that nothing ran. Modal can retry an input
after an earlier attempt (a preemption or a lost input) that may still be
running; releasing at once could let a third simulation start. main() now
keeps the claim on a refusal, as for any other remote error, and still
exits 75 with a message that the slot stays held until its lease lapses.
The docstring and RECIPE state the assumption that freeing a slot on a
returned result rests on: Modal returns an input's result only after any
earlier attempt of that input has stopped.
Nits:
- Every main() test fails on a real wait (stub_main and the interrupt test
patch time.sleep), so a broken acquire fails in seconds.
- The worst-corner test is renamed to what it shows (both margins removed
overlap; either alone still leaves a gap).
- The factory test checks that a lease covering the timeout and margin
starts the run, not only that a short one is refused.
- A claim left too short by a stall after its create is given back, and
the next generation of the same day's chain is free.
- The docstring lists every way a slot can stay held longer than needed.
Mutations: release on refusal, a guard that always refuses, and no
give-back when time runs out are each caught by one new test; an acquire
that never grants fails 27 tests in about 2 seconds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Discovery: answer the review of 6d9f344
Built by a GPT-6.1 Sol lane and committed here because the lane's sandbox
could not write git metadata.
- Listing and per-version errors are caught; the drain no longer depends
on discovery's result.
- Malformed queued requests are read defensively.
- Up to three requests within 30 attempts, with persisted skip evidence:
immutable pin or model contradictions and commit-pinned dataset
disagreements are skipped; transient failures retry.
- Discovery has its own concurrency group; late W4 dispatches are
documented.
- Yanked releases and wheels are excluded.
- No certified baseline: logged, no candidates.
- Versions compared with packaging's Version.
- The dataset's repo_type is preferred, falling back to data_package.
- Installer and probe environments are sanitised; checkout credentials are
not persisted and authentication is scoped.
- The verified push loop is shared, failed rebases abort, and the drain
uses the verified SHA.
- A Hypothesis property over mixed good, failed, skipped, recorded and
yanked releases.
Co-Authored-By: GPT-6.1 Sol (Subfleet lane) <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Ingest every OBR policy-measures-database cell as a score (#169)
Max (2026-10-11): "scorecard doesnt need pe replications to add, we want
it to capture comprehensively so we can fill in with pe down the road."
The OBR policy measures database's original scorecards, from the committed
harvest, enter the database as external scores that publish without a
PolicyEngine result: 12,449 new tax cells (84 events, Budget 1970 to Autumn
Budget 2025) and 11,303 new spending cells (32 events, June Budget 2010 to
Autumn Budget 2025), beside the 552 cells already ingested. Keys come from
the replay inventory's builder, so replays attach to these claims later.
- scorecard_db/ingest_obr_pmd.py and obr_pmd_identity.py: an exhaustive,
disjoint source-identity partition; decimal GBP sums match per event,
table and head; all 8,312 zero cells kept; the archived workbook's
SHA-256 matches its manifest and every staged value matches its cell;
the 48,902 GDP extensions stay excluded.
- 78 additional worlds (52 hmt_pre_measures_*, 26 obr_pre_measures_*);
inventory classifications copied, historical cells left unclassified.
- All 22,215 inventory FY cells and 4,584 replay-registry pointers join
uniquely with matching values; three frozen measures keep their keys
through cell_matches() aliases.
- Browser export: per-event population files (84 files, largest request
1.28 MB); the initial list stays 5,631 rows; UK figures distinguish
captured claims, numerical counterparts and claims without results.
Built by a Subfleet lane (GPT-6.1 Sol) and committed here because the
lane's sandbox could not write git metadata; the lane's bundle file is not
committed.
Co-authored-by: GPT-6.1 Sol (Subfleet lane) <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Lock first three historical replay source vintages and contracts (#170)
* Lock first three historical replay source vintages and contracts
Phase 0 slice 1 of the 2010-2022 replay programme (docs/uk_replay/PLAN.md),
for Spring Statement 2022, Autumn Statement 2022 and Autumn Budget 2021:
- 18 source-role receipts covering 16 distinct workbooks, collected by
data/uk/events/collect_vintage_locks.py (offline --check); workbook bytes
are not committed.
- Schemas: vintage lock, event vintage selector, population manifest; three
bound selectors.
- Design docs: historical FRS requirements, the registry adapter, the
policy-world contract and the population manifest. The policy-world
decision and the annualisation bridge stay pending under cos d1344.
- 29 tests (tests/test_uk_vintage_locks.py).
Unverified items are listed in docs/uk_replay/PHASE0_SLICE1_REPORT.md.
Built by a Subfleet lane (GPT-6.1 Sol) and committed here because the
lane's sandbox could not write git metadata.
Co-Authored-By: GPT-6.1 Sol (Subfleet lane) <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vintage locks: answer the review of 4c5f918
- Autumn Statement 2022: the OBR's event-day economy, receipts and
expenditure files deferred 15 tables to 24 November (their sheets hold
only that notice; found by shared-string cells, checked against the
locked bytes). Each source now records them as deferred_sheets,
unavailable in the locked bytes, and all four OBR roles name the later
files of the same forecast (first archived 9 December 2022, per the
archive's CDX index) as later_versions, not locked. The lock digest and
its selector binding are updated.
- check_locks ties every source to its event: an OBR file's edition must
be the forecast label, every source but the cross-vintage HOFD must be
captured on or after the event date, a launch label is refused on a file
modified after the event, and deferred sheets must be workbook sheets.
Negative tests cover the review's two surviving mutations (another
event's source; a false launch label) and each new rule.
- Schemas: explicit patterns for every date, date-time and uri field (the
default format checker skips uri and date-time); event_slug is a pattern,
not a fixed list of three events.
- The collector matches column A labels exactly (A1..An, not AA..AZ).
- PHASE0_SLICE1_REPORT.md: the delivery section says what happened (commit
3209e0b, PR #170, this follow-up); local machine paths removed; the
sources section records the deferred tables. HISTORICAL_ADAPTER.md: the
variant interface is on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vintage locks: answer the review of 55e22e2
- Autumn Statement 2022: the event-day files also hold 24 sheets reading
"This table will not be updated for the November 2022 forecast" (economy
4, receipts 12, expenditure 8) beside the 15 deferred to 24 November.
Found by content with an XML parse of the locked bytes: every sheet with
either notice has exactly three non-empty cells (back link, title,
notice). The record is now one unavailable_sheets list per source with
each sheet's reason (deferred_to_later_release or
not_produced_for_this_forecast) and notice. Lock digest and selector
binding regenerated.
- check_locks refuses a DWP edition whose file name does not name the
event (the DWP files were captured in 2025, so only the name ties them),
and an OBR file modified after the event that is not later_revision.
Negative tests for both, and for an unavailable sheet missing from the
workbook.
- VINTAGE_LOCKS.md tabulates all 39 sheets; the report's counts, test
count and line wrapping are corrected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: GPT-6.1 Sol (Subfleet lane) <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Receiver sandbox: exercise a real run's needs in CI, more hardening (#171)
* Receiver sandbox: exercise a real run's needs in CI, more hardening
Follow-up to #163's last review:
- The receiver-sandbox CI stub now does, inside the sandbox, what a real
run needs before it writes output/probe.json: create a uv environment and
install from PyPI (network), add a git worktree of the copy, and run a
multiprocessing pool; the job checks probe.json came back.
- More systemd hardening: ProtectKernelModules, ProtectKernelLogs,
ProtectClock, RestrictNamespaces, LockPersonality and an empty
CapabilityBoundingSet.
- Docs and the script say that /tmp is private and that /dev/shm and
/dev/mqueue stay shared with the host (so multiprocessing works; nothing
later in the job runs from them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Receiver sandbox: answer the review of 1211d44
- The CI stub's pool uses the spawn start method, as the real compute's year workers do.
- The sandbox test checks all six added systemd properties, not two.
- The script header and the RELEASE_REPLAY paragraph are re-wrapped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Historical replays: decide the policy world and annualisation (cos d1344) (#172)
* Historical replays: decide the policy world and annualisation (cos d1344)
Max decided cos d1344 on 15 October 2026: the workstream makes the
methodology calls the Budget CI plan raises, with independent review. The
two calls phase 0 left pending, for Spring Statement 2022, Autumn Statement
2022 and Autumn Budget 2021:
- Policy world: published_end_state_same_weights.v1 (Option A). Each
event's expenditure workbook labels its welfare breakdown post measures
(3.7!B2, verified in the locked bytes); no locked source supplies a
complete pre-measures level series (November 2022's pre/post labels are
only on the aggregate tax- and spending-to-GDP charts C.16 and C.18), and
building one from the OBR costings would leak the holdout. Contract terms
in docs/uk_replay/contracts/published_end_state_same_weights.v1.md.
- Annualisation: fy_start_year.v1. policyengine-uk 2.125.1 and 2.130.1
(convert_to_fiscal_year_parameters) already treat period Y as the fiscal
year from 6 April Y (30 April sample; six parameter files day-weighted in
2.130.1); a two-period blend would mix two fiscal years' policy. Receipt
with engine evidence, limitations and every event's FY-to-period mapping
in docs/uk_replay/contracts/annualisation_fy_start_year.v1.json.
Both readings of each, the choice and recorded exceptions are in
docs/uk_replay/HISTORICAL_DECISIONS.md. The three selectors bind both
choices by SHA-256 and move to ready_for_population_build (methodology
resolved only; no population, target compilation or certification). Tests
check the digest bindings and the FY mapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* d1344 decisions: answer the review of b404faa
Both choices stand; the review's fixes are to the reasons, the contract
and the receipt (every cited cell re-read in the locked bytes, every engine
fact in the 2.130.1 wheel or the installed 2.125.1 source):
- B1: the November 2022 pre-measures series are listed in full (C.1, C.2,
C.8, C.11, C.15-C.18, C.21, C.D, T.7; indirect effects in T.1, T.5,
C.20), all aggregates. Post-measures is claimed only for the welfare
table; targets are classified pre/post one by one (3.4!B38 and Annex A
TA.4!B12 are pre-measures).
- B2: the published world runs the event's own law and forecast uprating,
with its policies recorded (e.g. the 19% basic rate in March 2022 Annex A
TA.2!C9, absent from 2.130.1).
- B3: annualisation adds a part-year rule: a flow parameter changing within
the fiscal year takes its day-weighted fiscal-year value in both worlds
(the engine's own fiscal_year_blend rule), otherwise the targets it moves
are excluded (NICs threshold July 2022, UC taper 2021, HSCL repeal Nov
2022). The blend alternative's reason is corrected.
- B4: the receipt names each engine's reform route (2.130.1 dated_reforms:
range keys reprocess; single-day keys change nothing; 2.125.1
Scenario.apply edits the processed tree) and requires a processed-value
check per measure-year; 2.130.1 is described as policyengine.py#617's
certified version, not a scorecard replay variant.
- B5: A's reason is that it needs no constructed bridge and keeps the
costings held out; calibration dependence is recorded for both options
and tagged calibrated_aggregate.
- B6: exceptions add the HOFD and DWP editions, the Autumn Budget 2021
release-equivalence waiver (policy world only, not its targets) and
cash-basis receipts (not targets until bridged).
- Nits: explicit literal/announced_effect; .gitattributes -text for the
bound files; digest checks in validate_selector with mutation tests; the
resolved annualisation form carries status and a known bridge_id; the
decision record is bound by digest; preserve_calendar_dates (33 files) and
the macro_vintage tag recorded; PLAN.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* d1344 decisions: answer the review of 37b85b4
- R1: reform_route states routes by entry point: a construction-time
reform (or apply_parameter_changes) in 2.130.1 goes onto the statutory
timeline; Scenario.apply on a built simulation, in either engine, and the
2.125.1 reform dict edit the annualised parameters, read at 1 January;
parameter_changes apply to every period. The decision record matches.
- R2: the primary threshold rise is keyed 2022-07-01 in 2.130.1 although
its comment says 6 July (verified in the wheel); intended fiscal-year
values come from the statute or the event's documents, never the
engine's timeline.
- Nits: 33 preserve_calendar_dates files in 2.130.1, 32 in 2.125.1; the
point-in-time clause maps to preserve_calendar_dates or flag-and-exclude,
and the engine's day-weighting of CGT and fuel duty is recorded;
day-weighted measure-years are computed and tagged; unlabelled levels are
classified by the publisher's cited statement of basis or excluded (DWP
Notes!B5 and Notes!B12 verified); the AS2022 DWP note on pre-measures
supplementary caseloads is recorded as an Option B lead; AB2021 receipts
are not targets until a release-equivalence review, in both files;
indirect effects added to the identity; the decision record is bound by a
structured decision_record / decision_record_sha256.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* UC health element reversal: reach policyengine-uk 2.130's parameters (#173)
* UC health element reversal: reach policyengine-uk 2.130's parameters
The 6.4.1 qualification (policyengine-uk 2.130.1) left Spring Statement
2025's UC health element reversal inert in 2026-2029, so the receipt was
incomplete and W4's gate failed every candidate.
Cause, read in the 2.130.1 wheel: Scenario.from_reform(dict) now keeps the
dict's dated keys in dated_reforms (utils/scenario.py). On an already-built
simulation, only Scenario.apply marks the simulation as built so that every
change becomes an override of the annualised parameters; the scenario's bare
simulation_modifier (_legacy_override_modifier) applies only single-day keys
and paths the statutory tree does not define. uc_health_scenario called the
bare modifier, so the "2026-01-01.2026-12-31" keys never reached the
parameters, the refreshed UC inputs kept 217.26 and both worlds matched.
The premise audit missed it because it reads intended values from the
registry's dict, not from the executed route.
- uc_health_scenario uses Scenario.apply when the engine has dated_reforms
and keeps the bare modifier for older engines, so legacy and 6.2.6
results reproduce exactly.
- assert_reform_reached: after the update, every year whose 1 January lies
in a reform window must read the window's value from the simulation's
processed parameters, or the run fails instead of going inert.
- Tests: both engine generations (fakes), a route that misses the
parameters is refused, and the opt-in engine test is version-aware
(2.130 needs the new-claimant status supplied for a household situation,
and its construction-time dict route now agrees). Ran the engine test on
2.89.2 and 2.125.1; 2.130.1 is exercised by the 6.4.1 re-qualification.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UC health route: answer the review of the fix
The fake parameter reads the reform's value only at 1 January in the reform's years, so a check that sampled 30 April now fails the test (the one mutation that survived review); leftover lines removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Receipts: an inert construction the premises say should move is a failure (#174)
* Receipts: an inert construction the premises say should move is a failure
Agreed with W4 on 2026-10-15: W4 owns whether a receipt blocks promotion
(fail on regressions against the base); W6 owns what a receipt says. An
in-force measure-year whose two worlds come out identical, while the run's
CONSTRUCTION_PREMISES.json sets a parameter to different values in the two
worlds that year, is a construction that did not reach the engine. The
receipt now records it as an integrity failure construction_inert (code,
detail, event, measure_key, year: the fields W4's vendored schema allows),
making the receipt failed. Inertness the premises predict stays an
incomplete grid with its inert_measure_years.
Applied to the 6.4.1 receipt's premises, it flags exactly the four SS2025
UC health element years that #173 fixes. Tests cover both cases, the field
set and schema validity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* construction_inert: answer review of 6aa7fc4
- Docstring and both docs say what a failure means: the intended change
did not show in the declared heads (the construction did not reach the
engine, or it does not bind on this population). Equal premises values
do not prove a pair should be inert, so such a pair stays incomplete.
- The detail names both worlds' values.
- QUALIFICATION_INTERFACE.md lists construction_inert among the
integrity-failed causes.
- New tests pin the pair's own year, measure and event (another event's
differing premises for the same key do not decide), and a Hypothesis
property: a pair fails exactly when its own year has a differing path.
Mutants that read the first year, any year, or merge all events'
premises each fail at least one test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* construction_inert: answer the nits in the review of f75fed8
- A third cause in the docstring and docs: the construction moves only
variables outside the declared heads.
- The receipt test puts another event's premises first, so merging
events' premises in either order is caught (the "first wins" mutant
survived before).
- The property test no longer claims to exercise another event; it passes
one event's premises, and event scoping is pinned at the receipt level.
- Drop "in the PLAN's sense": PLAN.md does not define the term.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* WIP: Autumn Budget 2025 in the event registry builder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Replay Autumn Budget 2025 as the sixth event
- Registry (105 measures, 1,512 source rows, exact Decimal accounting):
the OBR costings lane's eight reviewed reversals (#56) matched by PMD
title, #136's high-value council tax surcharge reversal as a dated
window, and #136's measure keys from the replay inventory, which the
scorecard DB ingest joins on. Each construction commences in the first
year its reversal windows read. Fuel duty and the UC standard
allowance/health element protections are named construction_pending
gaps with their sources cited.
- Infinite engine values (the abolished two-child limit) are named "inf"
in the registry, as the premises audit names them.
- The qualifier, Modal runner and CI guards list the event; the corpus
version is uk-fiscal-event-replay/2, documented as naming the event set.
- A sealed namespace's premises cover the events its run replayed: the
qualifier passes its own events, and CI regenerates a sealed namespace's
premises for its receipt's events. Both committed unsealed namespaces'
premises are regenerated with the sixth event.
- Tests: every replayed event agrees with the inventory cell for cell
(within a penny); the AB2025 builder output is pinned against the
committed registry; keys, gaps, the surcharge conversion and its
refusals; property tests for json_scalar and first_window_year.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Regenerate the unsealed summaries with the sixth registry inventory
The summary lists every registry on disk; Autumn Budget 2025 appears as a
seeded registry without a numerical replay. Sealed summaries are bound by
their manifests and are not re-rendered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Namespace premises and summaries cover the events they hold; summary last
A release diff records each side's premises digest, and a sealed
namespace's manifest binds its premises and summary. Regenerating them for
every registered event would change those bytes when an event is added, so
a namespace's premises and SUMMARY.md now cover the events it holds results
for (an event directory with RUN_MANIFEST.json), or every registered event
when it holds none. The legacy namespace keeps its five-event bytes; the
6.2.6 namespace, which holds no results on main, covers all six.
The qualifier wrote SUMMARY.md before MODEL_DIAGNOSTICS.json existed, so
CI's regeneration of a sealed namespace rendered a different summary (why
#164's replay-engine and pytest checks fail, and why 6.4.1 attempt 2 cannot
be committed as sealed). It now writes the summary last.
Tests: namespace_events property test (held events in order, or all;
directories without a manifest and unregistered names hold nothing); the
summary's registry scoping; the qualifier passes --namespace and writes the
summary after diagnostics. Each mutant of the three fails a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* AB2025 measure keys are all the inventory's, so published scores keep theirs
The scorecard DB ingest keys published OBR PMD scores by the replay
registry's measure key. 17 AB2025 measures had no #136 key and got the
builder's slug-and-digest key, which re-keyed their already-published
scores (CI's populations export test caught the changed event archive).
Those measures now take the inventory's own measure_key, so all 105 keys
equal the inventory's and a from-scratch DB build exports the AB2025
archive byte for byte. The 6.2.6 premises are regenerated for the keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* A qualification namespace records its corpus; answer the review of bfd6442
- Blocking: a sealed namespace whose events all failed before writing a
run manifest fell back to every registered event, so its premises and
summary would drift when another event was registered. A qualification
run now writes CORPUS.json (corpus version and events) before any event
runs, and uk_replay_variants.namespace_events decides from it first,
then from held run manifests, then from every registered replay event.
The premises audit and the summary both use it; an unregistered event in
CORPUS.json is refused.
- The legacy premises engine test regenerates with namespace_events.
- The HVCTS test pins the complete converted specification against #136.
- The Carer's Allowance overpayment review has its own administrative gap
reason (model_or_data_gap); the 6.2.6 premises are regenerated for the
AB2025 registry digest.
Tests: namespace_events with a corpus record, held manifests and none;
the qualifier writes the corpus before its first command. Mutants (corpus
ignored, not written, unregistered accepted) each fail a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Receipts: the base's own grid record (diff.base_events) and staging's grid lists
For W4's replay gate (d1344 decision record, agreed 2026-10-15): a candidate
gap is known only on positive evidence that the base had the same pair.
- The release diff records, per side, each event's grid as that side's
verified staging recorded it (DIFF.json base/head `grids`,
diff_uk_event_runs.staging_grid): supported_years from the run manifest
(staging does not record them), full_supported_grid_complete,
inert_measure_years (always present), and staging's missing and
unsupported measure-years with staging's meanings. Variant runs only: a
legacy-layout run has no grid, so no gap is ever inferred for it.
- diff.base_events copies the base side's grids from DIFF.json, with the
staging and run manifest digests the diff recorded; diff_sha256 binds it
and nothing is re-read. A requalified certified pair's base and
candidate share one result root, so a second read from the receipt's
checkout would have read the candidate (review of e4dee5e).
- Each corpus event carries staging's missing and unsupported lists too.
- Schema: additive and optional; a shared measure_years definition with
unique pairs and non-empty keys.
Tests: staging_grid values and the legacy gate; a property test that
base_events copies the grid verbatim with unsupported a subset of missing
(a swap fails); an integration test where base and candidate share a root
and differ; event_results lists; schema acceptance and refusals. Mutants
(swaps in staging_grid and event_results, no variant gate, base_events from
the head, no recorded grid) each fail a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* OBR policy measures database, March 2026 edition, as an edition delta
The March 2026 edition is locked from two identical Wayback id_ captures
(20260410184123, 20260423164140; workbook SHA-256 1364c625...), staged
reproducibly from the locked bytes into sources/obr-pmd-march-2026/, and
diffed cell by cell against the November 2025 edition: 24,302 identical,
0 changed, 246 new, 2 removed (Budget 2016 "Further government decisions",
FY2015-16, blank in March). Only the 246 new cells are ingested, all from
Spring Forecast 2026 (19 measures, 41 measure/head rows), each with its
edition, receipt digests and workbook coordinates. Every November claim,
source-row id, replay registry and published export row is unchanged; the
new event gets its own archive. A Spring Forecast 2026 replay registry is a
separate follow-up.
Built on a Subfleet lane (Sol, 20261015-154342-w6-pmd-march-2026); salvaged
after the lane's wall-time limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pin the corpus rule at its entry points; answer the nits on #175 and #183 (#187)
From the independent reviews that approved #175 (cd7018f) and #183 (cb8d33f):
- Tests pin the recorded-corpus rule at the summary and the premises audit,
not only in namespace_events: registering another event changes neither.
Reverting either to the held/all rule now fails a test (both survived).
- verify-sealed treats CORPUS.json as evidence of a run, so a committed
namespace whose events all failed must be sealed.
- base_event refuses an extra property (the one surviving schema mutant).
- The HVCTS test asserts #136's years are one contiguous run.
- Stale wording in ci.yml, the audit's --namespace help and run_corpus now
names the corpus record; the DIFF.json grids paragraph sits with the
diff's outputs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Historical events: source-accounting registries for SS2022, AS2022 and AB2021 (#184)
* Historical events: source-accounting registries for SS2022, AS2022 and AB2021
The first slice of the historical adapter (docs/uk_replay/HISTORICAL_ADAPTER.md),
on the existing PMD harvest with the locked sources (#170) and the d1344
methodology (#172):
- An event with a committed selector gets a registry that accounts for
every original source cell and fiscal year: scoring_fys is the
selector's (which must equal the source's fiscal years) and
calendar_years is its fy_start_year.v1 mapping (2021-2026, or 2022-2027
for AS2022), not the modern 2023-2030 window.
- Its execution is `requires_event_population`, bound to the selector's
SHA-256; compute_uk_event refuses such a registry outright, so nothing on
the modern certified population stands in for an event population. No
measure has a construction yet.
- The scorecard DB ingest joins only replay registries: these events'
published scores keep the inventory's per-row keys (the costings lane
keys a joint threshold freeze's income tax and NICs rows separately)
until an event can replay. Registry keys are the inventory's per title.
- A replay namespace's summary lists only replay events.
Tests: accounting and inventory agreement for all nine registries; each
historical registry's execution block, years and selector digest, with the
builder's output pinned to the committed bytes; selector-year and bridge
refusals; compute refusal; the ingest filter; the summary scope. Mutants
(no compute refusal, every registry joined, historical in the summary,
selector years unchecked, wrong selector digest) each fail a test. A
from-scratch DB build exports byte-identical published files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Historical registries: pin the gate at the entry point; answer the review of 8c8c6e1
- compute_uk_event.main's refusal is now tested at the entry point for the
three events under --dry-run, --guards and --years (removing the call
fails 9 tests; before, it passed every suite).
- The gate also refuses any event with a committed selector, so an edited
registry copy without the execution block is refused.
- The selector's bridge identity and event_slug checks are tested.
- Historical registries carry no modern certified bundle (its registry
rule says runs must execute against the modern population).
- The ingest's status literal is pinned to the builder's constant.
- Docs: the per-row key mapping a historical event needs before it can
replay (78 rows), and the stale table's commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Check processed replay values; revise AB2024 blended CGT for day-weighting engines (#186)
* Check processed replay values; revise AB2024 blended CGT for day-weighting engines
Every event replay reads each touched parameter from each simulation's own
processed period-Y tree before aggregating, compares it with the intended
annual value (the premises audit's function) and refuses a mismatch beyond
1e-9 relative, recording processed_value_mismatch in the receipt. On
policyengine-uk 2.130.1 a construction-time reform is re-annualised, and a
1 January window on a day-weighted (fiscal_year_blend) path bleeds into the
previous fiscal year: the frozen AB2024 CGT reversal moved CY2024 by about
GBP 575m with equal premises. The check refuses that world.
The AB2024 CGT construction revision (data/uk/events/construction_revisions.json)
is selected only when all three installed CGT rate parameters carry
fiscal_year_blend, and holds the pre-announcement rates through FY2024-25
(Finance Act 2025 s7: 18%/24% for disposals on or after 30 October 2024;
d1344 part-year rule keeps the engine's blend). Its SHA-256 is bound in run
manifests, artifacts and premises; staging, comparison and the release diff
recognise it. Frozen registries, legacy artifacts and sealed namespaces are
unchanged. The pooled-gains and unreversed-relief limitations remain.
Built on a Subfleet lane (Sol, 20261015-153911-w6-processed-value); salvaged
after the lane's wall-time limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Decide processed-value mismatches after both worlds run; answer the review of cce7cbb
The readback still happens before any aggregate, but whether a mismatch
refuses the measure-year is decided once both worlds are computed
(processed_value_failures). A mismatch refuses when it is in the certified
current law, when the measure is in force, or when the worlds' totals
differ. A pre-commencement mismatch whose worlds come out identical is
recorded under processed_value_notes. policyengine-uk 2.130.1 backdates a
construction-time change that starts on a parameter's first stated date,
which shows in AB2025's high-value council tax surcharge for 2025 to 2027.
Review fixes:
- compute.main-level wiring test with the real AB2024 CGT measure: revision
selection, each world's check and revision, the current-law readback, and
the statutory route's fiscal-year windows (four mutants caught)
- deferral rule tests across worlds, years and identical/differing worlds
- both values absent counts as agreement
- the comparison note comes from the revision registry's record
- the premises audit selects revisions for corpus events without a manifest
- run_corpus clears INTEGRITY_FAILURES.json before each event
- the Modal image carries stage_uk_ab2025.py
- QUALIFICATION_INTERFACE.md and RELEASE_REPLAY.md document
processed_value_mismatch and processed_value_notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Exercise the post-world decision through the managed runner; answer the review of 4679c54
- compute.main cases where a world's processed tree is wrong: the reversal
world, the certified world of a reversal, and both worlds and the certified
world of a two-world construction, where only the current-law readback sees
the certified simulation; and a world returning no evidence
- commences_fy must be a complete "YYYY-YY" label for consecutive years
(commencement_year); anything else is an error, not a deferral
- the interface, replay doc and premises description name the one accepted
mismatch exactly: an announcement world, before commencement, identical
worlds
The review's surviving mutants (post-world validation disabled, the
missing-evidence guard removed, the certified-law comparison dropped) and a
malformed-label mutant are each caught.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Certify the policyengine.py 6.4.1 replay results from qualification attempt 3 (#188)
* Certify the policyengine.py 6.4.1 replay results from qualification attempt 3
Commits the receiver's sealed namespace for
pe6.4.1_uk2.130.1_policyengine-uk-data-1.58.1 (request b1f9a93d, receipt
3b420b5e, replay-state 484eaa7) and its release diff against the legacy
variant. The variant record is the qualification's VARIANT.json plus a note.
CI's replay-engine matrix gains the variant.
All six corpus events have complete supported grids and no inert
measure-years. The run predates the processed-value check and the AB2024 CGT
revision (#186), so its AB2024 CGT period-2024 row carries the statutory
route's rate blend; RELEASE_REPLAY.md labels it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Compare a pre-#186 namespace's real replay without the evidence fields; answer the review of 500bf94
CI's push-only real replay recomputes one committed artifact with the current
code, which since #186 writes processed_parameter_values and
parameter_reform_routes on every variant artifact. The 6.4.1 namespace was
sealed before #186, so a plain cmp would fail on the first push to main.
pipeline/uk_replay_determinism.py compares whole bytes first, then, for a
committed artifact holding no evidence field, the canonical replay with
exactly those fields removed, with a notice. Every other byte must match.
Tests: the committed 6.4.1 artifact reproduces once evidence is removed; any
numerical change fails; a non-canonical replay never matches; Hypothesis
properties that removing exactly the evidence fields reproduces an older
artifact and any other difference fails; the CI step uses the module and the
engine matrix lists every certified variant; a new candidate's
latest_certified base is the 6.4.1 namespace.
Nits: the variants index is canonical again (note in sorted position, now
pinned by a test); the label cites annual_reform for the executed window and
the line wrap is fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* State verify-sealed's real triggers; answer the nits on #188 (#189)
- RELEASE_REPLAY.md: a replay namespace must be sealed when it holds an event
RUN_MANIFEST.json, a CORPUS.json, a receipt or a namespace manifest
(uk_replay_qualify.must_be_sealed), not whenever its result_root exists; a
premises-only namespace stays unsealed (nit on #187).
- The determinism test's evidence fixture now has the direction #186's code
writes for the private-school VAT forward delta on 2.130.1 (review of
f0ff21a).
- The any-other-difference property compares serialised bytes, so True/1 and
-0.0/0.0 differences are tested rather than skipped.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Derive discovered replay bundles statically from wheel manifests
* Isolate missed-dispatch discovery from replay queue writes
* Gate prepare and run on !cancelled(), not always()
always() is also true in a cancelled run, so a cancelled drain would still
select and run a request. !cancelled() keeps the intent (discovery or
enqueue failure never strands a queued request) without that. The record
job's existing always() conditions are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Certify the policyengine.py 6.5.0 replay results, the first with the processed-value check (#190)
Commits the receiver's sealed namespace for
pe6.5.0_uk2.130.1_policyengine-uk-data-1.58.1 (request d83eaf59, receipt
a7a67c26, replay-state 19a9ccd) and its release diff against 6.4.1. The
variant record is the qualification's VARIANT.json plus a note; CI's
replay-engine matrix gains the variant.
Integrity complete, no failures; every event's grid complete. Eight rows are
flagged, all AB2024 CGT main rates: the #186 revision moves period 2024 from
+£0.575bn to +£0.956bn with processed rates equal to intended, and flags
2025-2029 for construction only. No other row moved.
Tests follow the newest certified variant; the determinism tests pin that a
namespace sealed after #186 is compared whole.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Harden discovery metadata handoff and pin write-job guards
* Require trusted-publisher provenance for missed-dispatch discovery
* Document discovery security review evidence and mutation results
* Require the trusted publisher's four fields, not an exact publisher object
The provenance check compared the whole publisher object for equality, so a
field PyPI adds later would record a permanent skip for every genuine
release. Each identity field (kind, repository, workflow, environment) must
still be present with exactly the reviewed value; any other field is
ignored. Tests: each missing field refuses; a None or near-miss value
refuses; an added field does not.
The lane's evidence report moves out of the repository (its line
references would go stale); its substance is in the PR body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Retry every provenance status but 404; base the floor on published final versions
Review of 2043fa0: PyPI documents 403 as access "temporarily disabled by the
PyPI administrators" and 406 as an unrecognised Accept header
(https://docs.pypi.org/api/integrity/), yet both recorded a permanent skip
that suppressed the release for good. Only a 404 ("file has no provenance")
is permanent now; every other status and any network failure is retried.
The floor counted any stored request's version, so a request naming a
version never published (e.g. 999999999!0) would stop discovery for good. A
requested version now raises the floor only if PyPI lists it and it is a
plain final release; it still counts against duplicating itself, and an
unpublished candidate request counts once its version is published. The
lane's rule that a rejected or unsupported-kind request still sets the floor
is kept.
Mutants: 403-as-permanent, floor ignoring the PyPI listing, and floor
counting odd segments each fail a test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pin that a discovery batch naming one version twice is refused whole
Review of 2043fa0 found the guard's in-batch duplicate check untested. Two requests for one version, or a request and a skip, are refused before any write; the mutant dropping the check fails both cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: GPT-6.1 Sol (Subfleet lane) <noreply@openai.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft until CI passes and an independent review agrees. The local full suite passes: 1,840 passed, 15 skipped.
Adds Autumn Budget 2025 (26 November 2025) as the sixth replayed fiscal event, so it reruns on every certified pair and appears in release diffs. The corpus version becomes
uk-fiscal-event-replay/2.The registry
data/uk/events/autumn_budget_2025_measures.jsoncovers 105 measures and 1,512 OBR PMD source rows (every head and FY, 2025–26 to 2030–31). Decimal accounting is exact: rows in = rows classified, and net and absolute GBP in = classified, overall and per FY.expressible)construction_pendingmodel_or_data_gapRow-level totals over every head and costing year, gain to the Exchequer, £bn:
These are inventory totals, not one-year Budget costings.
Counterparts.
data/uk/obr_measure_reforms.yaml, OBR policy costings (mode 2): registry, offline certified compute, staging, descriptive comparison #56), matched by verbatim PMD title: dividend, savings and property income rates; the salary-sacrifice NICs cap; the Winter Fuel income test; the personal-tax and employer secondary-threshold freeze extensions; and removing the UC two-child limit.pe_gaprows for what the engine cannot express #136's high-value council tax surcharge reversal, its year-keyed values converted to one dated window (2028-01-01.2030-12-31). Any run the conversion cannot window is refused.Departures from #136, and why.
pipeline/compute_uk_event.pyannual_reform), so an annual counterfactual is not guessed. The note cites Port every Autumn Budget 2025 score into the scorecard, withpe_gaprows for what the engine cannot express #136's dated pre-Budget path for a later part-year construction.pe_gaprows for what the engine cannot express #136 switches rebalancing off. The replay inventory's 2.125.1 note records that this leaves the 217.26 health element for every claimant, which is not the pre-measure world. The costings lane leaves the measure without a construction too.pe_gaprows for what the engine cannot express #136's (existing_registry_key); the other 17 are the inventory's ownmeasure_key. The scorecard DB ingest joins registry measures to inventory rows on these keys and keys published scores by them (scorecard_db/ingest_obr_pmd.py). Any other key would re-key scores that already publish. A from-scratch DB build exports the AB2025 event archive byte for byte.Commencement. Each construction commences in the first year its reversal windows read:
The costings lane's
start_fyis the first nonzero OBR costing year, which can precede the mechanism. Staging treats identical worlds from commencement on as inert.Infinity. Removing the two-child limit sets
child.limit.child_countto infinity. The registry names it"inf", as the premises audit already does, so its bytes stay strict JSON.Wiring
YEARS.md,RECIPE.md) list the event.uk-fiscal-event-replay/2.QUALIFICATION_INTERFACE.mddocuments it as naming the event set. In a diff against a/1base, the added event haspresent.basefalse (diff_uk_event_runs.diff_sides).audit_uk_event_premises.namespace_events;compare_uk_event.write_summary). A release diff records each side's premises digest (diff_uk_event_runs,premises_sha256), and a sealed namespace's manifest binds its premises and summary. Regenerating them for every registered event would change those bytes whenever an event is added.--namespace.SUMMARY.mdlast. It used to write it beforeMODEL_DIAGNOSTICS.jsonexisted, so CI's regeneration of a sealed namespace rendered a summary with a diagnostics section the sealed one lacked. That is why Replay the five events on policyengine.py 6.2.6 and diff against the legacy run #164 fails CI, and why 6.4.1 attempt 2 (receipt4c317436…) cannot be committed as sealed. After this merges, re-qualifying 6.4.1 produces a namespace CI can regenerate, together with the first AB2025 headline.Checks run
build_uk_event_registry --event autumn_budget_2025, then--check, byte-identical (policyengine-uk 2.89.2).fiscal_year_blendorpreserve_calendar_datesmetadata;--checkmatches a fresh audit for both committed namespaces.start_fy, the latest window, one-sided infinity, and no contiguity or no constancy check on the surcharge run.scorecard_db.build_dbleaves no tracked-file drift, andexport_populationsreproduces every committed event archive, AB2025's included.ruff format --checkis clean.namespace_eventshas a property test: the result is the held events in order, or all of them; a directory without a manifest, or an unregistered name, holds nothing.--namespace, summary after diagnostics) each have a test, and each mutant of the three fails one.Pre-existing, not changed: main's builder does not reproduce the frozen Autumn Budget 2024 and Spring Budget 2024 registries. Only authored note text differs. Those bytes stay frozen.
Invariants
first_window_year(spec)is the minimum window start over all paths, and is one of the starts (property-tested).json_scalarnames ±infinity and returns every other value unchanged; its output always serialises withallow_nan=False(property-tested).namespace_events(root)returns exactly the registered events withroot/<event>/RUN_MANIFEST.json, in registry order, or every registered event when there are none (property-tested).After merge
/2). It replays all six events and gives the AB2025 PE vs OBR headline.axiom: n/a: replay harness and registry of OBR costings; no policy encoding.
🤖 Generated with Claude Code