Repository navigation
Compare UK cuts with the current build of their own line - #1574
Conversation
A UK cut will check and certify its candidate against the current build of its own release line, as the Hub's releases.json names it (microcosm#1095), instead of the enhanced FRS. This adds the resolution, before any check uses it. - microcosm-data's loader exposes CertifiedRelease, resolve_certified, download_certified and download_build_file: the build-folder resolution it already does for load(), returning the build's record, without enforcing the installed engine's versions, since a comparison reads a build under the engine it runs. - uk_runtime/cut_reference.py resolves the national or dense line's current build, checks its certification evidence by its fields (the fetch runs without the signing key), and keeps a strict record that later steps read instead of resolving again. - tools/resolve_uk_reference.py checks a line's current build read-only, or fetches it with its record for runs outside run_uk_cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both countries score each artifact with microcosm.calibrate's canonical loss, but each wrote its own comparison on top: per target, whose absolute relative error is lower, the counts, the loss gap and the family counts. microcosm.build.head_to_head now holds that comparison once, with no verdict, as microcosm.build.input_mass did for the input-mass gate (#609). The US head-to-head scorer's _comparison_payload calls it and keeps its own payload names, so its scorecard is unchanged: on 300 random fixtures (ties, NaN, signed zeros, empty surfaces) its JSON is byte-identical to main's, and a new test pins the payload. The UK cut's report moves onto the module next (microcosm#1095). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cut's candidate is now compared with the current build of the national line, which the Hub's releases.json names, instead of the enhanced FRS (microcosm#1095), and the comparison is evidence with no verdict, as the US head-to-head is. - run_uk_cut resolves the reference once per cut, in a new reference step before the spine, into <cut>/reference/ with its record. The national, score and certify steps read that record; none resolves again, so a promotion mid-cut cannot split it. --re-resolve-reference sets it aside. status says when the current build has moved; stage 1 compares with the reference by default. The incumbent_efrs input and its gates.json pin selector are gone. - candidate_score builds on microcosm.build.head_to_head. Its receipt, score_vs_reference.json, schema 2, names the reference and carries the losses, per-target comparisons and family counts, with no verdict. Measures the reference cannot carry are pruned under reference_unresolvable_measures.json, scoped per reference build; the enhanced FRS's eight entries retire with it. - The national role, the build CLI (--reference-h5, --reference-record) and the score command follow; the dense role refuses the national role's reference flags. - The certifier requires a schema-2 receipt for this candidate and the cut's own reference, and the certification (schema 2) names that reference. The release contract moves to schema 2 and binds score_vs_reference.json, which the assembler now publishes. Only new builds are validated against the contract, at the spec they are assembled under, so schema 1 keeps no path. The input-mass, export-surface and input-coverage gates still read the enhanced-FRS references; they move to the current build next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The input-mass gate compared every cut with the enhanced FRS's weighted totals (microcosm#1095). It now compares with the current build of the cut's own line, the build the cut's reference step resolved. - uk/gates.json names the reference kind, current_build, not a file. The report records the build (reference_label, reference_identity) and its totals digest. The tolerance is 5.9984285545209834: the exact maximum drift between the consecutive promoted national cuts a6299504 and eeb6093e over the 259 columns at least three source households carry, with no headroom, as María signed on 2026-11-10. A zeroed or missing column still fails at any tolerance. - The reference step (run_uk_cut, and tools/resolve_uk_reference.py for a dense build) writes input_mass_reference.json beside the reference: schema 2, the build's export totals and distinct source-household carriers at its own solved weights. uk_recorded_solve_year_frame gives a build back its solve by the factor its mass log records, since an older policyengine-uk's projection differs from the installed one. - A column one or two source households carry on either side is still checked, but its figures leave the report (withheld_thin_columns). - Exclusions are signed per reference build (<release>@<build>) and go dormant when a promotion moves past it. The enhanced FRS's three entries, its registry entry, the hosted totals input and their pins retire; microcosm-data's contract checks that a report names its build instead of pinning the eFRS identity and digests. Digests re-pinned. - certify refuses an input-mass reference that is not the cut's reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The export-surface and release-input-coverage gates compared every UK cut with the frozen enhanced-FRS reference. A cut now compares with the current build of its own line, the build its reference step records. - The reference step writes reference_surface.json beside the reference: the columns the build exports and the loader inputs it carries, names and flags only. An input is carried by the coverage gate's own rule (uk_effective_input_signal: non-default signal on the reviewed share of effective mass), flagged thin when one or two source households carry it. - Export surface: the reference side is that surface. The declared extras shrink from 250 to the 26 the current national build does not export (the dense line's geography and clone columns); an entry the reference carries is reported as redundant, not failed. - Input coverage: the required inputs are the ones the reference carries, less the reviewed exclusions. The manifest moves to schema 4 and keeps the exclusions, the effective-mass policy and the stage-family contracts; the preflight stops checking the frozen enhanced-FRS surface and ledger. The gate refuses a reference measured at another floor, and an empty required set. - The certifier takes --reference-surface and refuses one that names another build than the cut's reference; run_uk_cut passes it. - Dense builds take --reference-surface as a graph source, beside --input-mass-reference, and refuse the two if they name different builds; without it the two gates are evidence_absent. The national role refuses it. - Battery digests re-pinned in contract.py and its test copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The operator docs described the enhanced-FRS reference, the incumbent flags and the rule-1 verdict. They now walk through the reference step, the files it writes, the --reference-* flags of the national role, the certifier and the dense build, and the score as a report with no verdict. - uk-cut-anyone.md: the reference step, its directory, and how to check or move what a cut compares with; the eFRS and input-mass input rows go. - Runbooks 806, 623 and 762: resolve the reference, then pass it to the build, the scorer, the certifier and the dense build. - uk-staging-operations.md, gate-battery-contract.md and the README: the score_vs_reference artifact, the reference_surface artifact and the current_build reference rule. - A changelog fragment for the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The reference step's evidence (input-mass totals and the reference surface) loaded the reference with the compact national loader and passed it through the release export boundary. Both refuse a published dense file: it carries one clone_index name on every table, and the consumers' area-code names the boundary writes. The national line never showed it, since its files carry neither. The evidence now reads the file with the row-wise reader, which handles both lines, and uk_published_release_frame maps a published file's area-code names back to the ladder's before the boundary applies today's drops and names, so the reference leaves exactly as the candidate does. A test writes a dense-format file and computes both pieces of evidence from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The input-coverage gate's report lists, for every required column, its signal rows and effective mass. The report is copied into the published release directory. Since the required inputs now include the ones the reference carries on one or two source households, a column's figures could rest on fewer cases than the UKDS floor of three (CD171). The gate counts the distinct source households behind each column's effective signal (household_source_id) and, for one or two, reports only "<3" and withholds the figures, in the details and in the failure message. The verdict still stands on the real values. A frame without lineage (a hand-made fixture; every built frame carries it) reports as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The input-mass gate's tolerance is symmetric, and at the 5.998 measured between the last two national builds it failed no drop short of zero: a column could lose 99% of its total and pass. maximum_relative_drop now bounds the share of its reference total a column may lose: 0.9, the same pair's largest drop (0.8831) rounded up for headroom, as María ruled on 2026-11-10. The check sits in the UK wrapper beside the shared gate, which the US line also uses and which keeps its semantics. A reviewed exclusion covers both checks and is stale only if the column passes both; a thin column's drop fails by name only. The policy takes the limit with no default, gates.json declares it, and the release contract requires the declared value and an empty excess_drops on a passing report. Digests re-pinned. On the real pairs the gate passes with the limit: national eeb6093e against a6299504 (largest drop 0.8831), and dense 38605444 against 83802786 (largest drop 0.58). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. #1550 adds six exported columns the current builds (national eeb6093e, dense 38605444) do not export: the PIP points (microcosm#1095 P1), the trading allowance claim status and full-relief carrier (microcosm#1439) and the UC limited capability flags (microcosm#1095 Y1). Measured against those builds, the export-surface gate needs each declared as new since the reference, naming its PR; each leaves once a promotion moves the reference past it. Digests re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uilds Part of #1095. #1550 (microcosm#1439) stops writing self_employment_gross_receipts: the trading allowance stage stores each trader's claim status and leaves gross receipts at the engine's zero. Both current builds (national eeb6093e, dense 38605444) still carry the receipts encoding microcosm#1428 wrote, so a cut measured against them would fail on the column in three gates. María signed the three exclusions on 2026-11-10: - export surface: a reviewed export exclusion (runtime constant and its gates.json mirror), and the release boundary drops the column, as it drops every reviewed export exclusion; - input coverage: a reviewed exclusion the manifest generator declares for inputs retired since the reference (manifest regenerated); - input mass: a signed approval for each current build, scoped to its release line, in force from 2026-11-10 to 2027-05-10. Each goes dormant once a promotion moves the reference past it. Digests re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The target and national kernels hash weighted_integrity for its exclusion clock, so every import it makes must be hashed too. This branch had given it three: the reference evidence's dataset readers (rowwise_dataset, weight_year), which no target value needs, and receipt_counts, which the input-mass gate counts source households with. The reference evidence's input-mass payload moves to cut_reference, beside the writer that calls it, so weighted_integrity reaches no dataset reader. receipt_counts is hashed beside weighted_integrity in the target, problem and national gate kernels; the national gate kernel runs it. The record of unclassified imports stays as it was, since it may only shrink. The input-mass register test now expects the two signed entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of 8ea0ee1: I traced how the reference is resolved, how the cut resumes, the dense and national roles, and the certifier, and ran the PR's engine-free tests against the stubbed Hub ( The four safeguards, and where each is enforced:
A releases.json that is missing falls back to the pointer file, and Should
Nit
Checked and fine
CI at 8ea0ee1: 5 checks pass; the engine-free, engine-uk and engine-us jobs (3.13 and 3.14) are still pending. Verdict: the record-once design holds and the fetch-time checks are sound. S1 to S3 are small guards worth adding before the next cut. S4 and S5 are scope questions for you. 🤖 Generated with Claude Code |
Part of #1095. From the review of #1574. - S1: a cut cannot compare with itself. After the cut's own promotion the line's current build is the candidate, so run_uk_cut refuses to re-resolve to it (checked before the recorded reference moves), and the certifier refuses a reference whose sha256 or build is the candidate's. - S2: a reference withdrawn after the fetch stops the certify step. The check reads releases.json and never moves the cut to another build; --re-resolve-reference stays the operator's move. status reports it too. - S3: each line compares with its own. The dense build refuses an input-mass reference or surface that is not a dense build's; the certifier refuses a reference record that is not the national line's; resolve_uk_reference.py refuses offline mode, as run_uk_cut does, since a cached releases.json can name a build that is no longer current. - N6: the diagnostics build block no longer carries score_vs_enhanced_frs; uk-full-build-graph.md and weighted_integrity's docstrings name the current-build reference. - N7: runbook 762 says the dense line's local score is unchanged. - N8: the cut receipt records the national build's own comparison and whether --re-resolve-reference superseded it. - S4: uk-cut-anyone.md says a line with no current build cannot be cut yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. From the review of #1574 (S4), on María's ruling: a line with no current build (a new line, or one whose only current build was withdrawn) could not be cut at all. - resolve_uk_cut_reference, fetch_uk_cut_reference and check_uk_cut_reference take a build= pin. A pinned build passes the same certification checks as the current one, is refused if withdrawn, and is recorded as pinned: the reference record gains selection (current or pinned), and the certification's reference block carries it, which the release contract checks. - tools/run_uk_cut.py takes --reference-build; a cut that already compares with another build keeps it unless --re-resolve-reference is given too. status says when the reference is pinned. resolve_uk_reference.py takes --build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. From the review of #1574 (S5), on María's ruling: every check compares a cut with the build before it, so drift could add up across cuts unseen. Each national certification now records its change since a fixed baseline in a since_baseline block: each input column's total as a ratio of the baseline's, the inputs the baseline carried, and which of them this build no longer carries. It stays light by chaining: - the reference step keeps the reference's own certification beside it and continues its record, or starts one at the reference when it has none, for the columns at least three source households carry; - the input-mass gate multiplies in the ratios it already measures, and the coverage gate names the baseline's inputs the candidate no longer carries, by its own rule; - the certifier copies both into the certification, which the release contract checks. No baseline dataset is ever loaded. On the real national pair the input-mass side is arithmetic over 259 columns, and the coverage side adds one pass over the candidate's columns inside a gate that already loads them. The record carries no verdict. The dense line's published evidence carries none of these gates, so its chain restarts at each reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. The fragment still said 26, from before the six columns microcosm#1550 adds were declared; gates.json declares 32. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. Fixed in 555f76e, with a test for each:
On María's rulings:
|
|
Round 2 at 2d3ab8b I diffed 8ea0ee1..2d3ab8b and ran the ten affected engine-free test files at the head with the Hub stubbed: 595 pass, none skipped. I then ran the 17 new or extended tests against the 8ea0ee1 source. All 17 fail or error there. The four new
Should
Nit
Not a finding: the record has no threshold, so it shows drift without bounding it. The dense line has no chain at all ( CI at 2d3ab8b: all 12 checks pass. Verdict: S1, S2, S4 and N6 to N8 are fixed, each with a test. S3 is fixed in the parts that matter. S5 now has the record, which should list the columns that leave it and flag a restart before the chain starts at the next promotion. 🤖 Generated with Claude Code |
… restart Part of #1095. From round 2 of the review of #1574. - A column that leaves the record of change since the baseline (absent on a side, a zero reference total, or one or two source households on a side) stays listed in input_mass_left, with its reason, its last ratio and the last build that carried it, and the list carries forward from cut to cut. The last ratio is one the reference's certification already recorded, so the list adds no figure. - The chain no longer restarts without a trace. It refuses a missing copy of the reference's evidence instead of reading it as a reference with no chain, and a resumed cut keeps that copy again from the Hub. A national chain whose baseline is not eeb6093e has restarted, and the certification records restarted_from, which the release contract checks. - A reference withdrawn after the cut is certified now stops the cut: the certify step checks before its early return, and the publish step checks too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. Fixed in 8247032, with tests:
On the bound: the record still carries no verdict. Whether it needs one is still open between us. |
Part of #1095. Main moved the lock to policyengine-uk 2.177.7 (#1572) and signed person.trading_allowance_full_relief as unresolvable on the enhanced FRS in incumbent_unresolvable_measures.json, which this branch retires: the score compares with the current build, whose register is reference_unresolvable_measures.json. The retired register stays deleted, and its test now counts nine entries. Rule 1's probe bound from #1572 (three probes per signed measure, and the clean probe) carries over to prune_reference_unresolvable, with its tests on this branch's names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1095. María's ruling of 2026-11-11, after merging #1572. Five SPI rows filter on person.trading_allowance_full_relief since #1439. The current national build (eeb6093e) exports neither the claim status nor the carrier, both new since it, and policyengine-uk 2.177.7 does not define the carrier. #1572 signed the measure on the enhanced FRS, in the register this branch retires; the comparison with the current build needs the same entry for that build, or its score refuses those rows. The entry runs from 2026-11-11 to 2027-02-11, three months as #1572's does, and goes dormant once a promotion moves the reference past eeb6093e. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Round 3 at 23de0f3 I diffed 2d3ab8b..8247032 and ran the eight changed test files at 8247032 with the Hub stubbed: 600 pass. With those tests on the 2d3ab8b source, all 12 new or extended tests fail. 79ccc4b only merges main after #1572. In the files cited below, the merge changes only one evidence pin in
Follow-up, not a blocker: I'd set a bound at the measured edge once a cut or two of the record exist. The dense line having no record is fine for now if #1095 tracks it. CI at 23de0f3: lint, wheels, select-countries, integration-uk and installed-emitter pass; engine-free, engine-uk and engine-us (3.13 and 3.14) were still running after 45 minutes, so I will check them again later. All three are fixed, each with a test, and I have no new findings. Thanks, María. 🤖 Generated with Claude Code |
|
Thanks, Vahid. The two follow-ups, a bound at the measured edge once a cut or two have recorded the change and the dense line's record, are now tracked in #1095: #1095 (comment). |
|
CI at 23de0f3 has now finished: all 12 checks pass, including engine-free, engine-uk and engine-us on 3.13 and 3.14. Thanks for tracking the two follow-ups in #1095. 🤖 Generated with Claude Code |
Part of #1095.
Now that the migration in #665 is complete, a UK cut checks and certifies its candidate against the current build of its own line, as
releases.jsoninpolicyengine/populace-uk-privatenames it, instead of the enhanced FRS: the national line against the national current build, the dense line against the dense one.Decisions (María, 2026-11-10)
microcosm.build.head_to_head) builds both; the US tool's payload is byte-identical on 300 random fixtures.What changes
Reference resolution (
uk_runtime/cut_reference.py,tools/resolve_uk_reference.py). microcosm-data's loader exposes the build-folder resolution; the fetch checks the nationalrelease_certification.json(shippable, names this file, registered key) or the densegate_summary.json(every gate passed).tools/run_uk_cut.pygains areferencestep before the spine, writing the dataset,reference.json,input_mass_reference.jsonandreference_surface.jsonunder<cut>/reference/;--re-resolve-referencesets it aside.--reference-build, or--buildonresolve_uk_reference.py). The pinned build passes the same checks as the current one, is refused if withdrawn, and is recorded aspinnedin the reference record and the certification.resolve_uk_reference.pyrefuses offline mode, as the runner does.Shared head-to-head module, which the US tool now uses.
Score and certification. The receipt is
score_vs_reference.json(schema 2, no verdict). The certification and the release contract move to schema 2 with areferenceblock; unresolvable measures are registered per reference build.Input mass compares each column's total with the reference's at the reference's own solved weights (the factor its mass log records). Exclusions are keyed
<release>@<build>. A column one or two source households carry on either side is checked, but its figures are withheld.Export surface and input coverage.
--reference-surface; the national role refuses it.Dense reference files are read with the row-wise reader, and a published file's area-code names map back before the export boundary. Both broke on a dense file.
Disclosure. The coverage gate withholds per-column figures for a column one or two source households carry, since the required set now includes the reference's thin inputs and the report is published with the release. The target and national kernels now hash
receipt_countsbesideweighted_integrity, as their import walk requires.Columns UK November event data, PR2: feed re-pin, trading allowance claim status, Scottish council tax and NSND, young people and PIP rows, UC take-up age fit, PIP points, UC limited capability #1550 changed. The six columns UK November event data, PR2: feed re-pin, trading allowance claim status, Scottish council tax and NSND, young people and PIP rows, UC take-up age fit, PIP points, UC limited capability #1550 adds are declared as new since the current builds.
person.self_employment_gross_receipts, which UK November event data, PR2: feed re-pin, trading allowance claim status, Scottish council tax and NSND, young people and PIP rows, UC take-up age fit, PIP points, UC limited capability #1550 (UK trading allowance: store the claim status once policyengine-uk#2765 is released #1439) stops writing and both current builds still carry, gets three exclusions María signed on 2026-11-10:Each goes dormant once a promotion moves the reference past it.
Change since a fixed baseline. Every check compares a cut with the build before it, so drift could add up across cuts unseen. Each national certification now records it in a
since_baselineblock, which the release contract checks:It chains, so no baseline dataset is loaded. The reference step keeps the reference's certification and continues its record, or starts one at the reference. The input-mass gate multiplies in the ratios it already measures, and the coverage gate names the baseline's inputs the candidate no longer carries. A column one or two source households carry leaves the chain. The record carries no verdict. The national chain starts at eeb6093e. The dense line's published evidence carries none of these gates, so its chain restarts at each reference.
Docs and changelog:
uk-cut-anyone.md, runbooks 623, 762 and 806,uk-staging-operations.md,gate-battery-contract.md, the README, andchangelog.d/1095-certify-against-current-build.changed.md.The battery digests and the release-cut part digests are re-pinned in each commit that moves the spec.
Testing
@axiom-foundation/orrerypackage;test_spawned_workers_score_identically_to_sequential_cli, which needs Python 3.14.7.The files touched by the last fixes were run again afterwards with no failures.
tools/ci_test_plan.py verifypasses, and the coverage manifest regenerates unchanged.ee8fa0353: 3,587 tests, with the orrery test the only failure. Nine skip because the pinned feeds or artifacts they read are not on this machine.tools/ci_test_plan.py verifypasses, and the coverage manifest regenerates unchanged.Before the next cut
tools/run_uk_cut.py's assemble, publish and promote steps still expect a cut tag the assembler no longer prints; that is a separate fix.🤖 Generated with Claude Code