Repository navigation
UK local release: publish a K=15, 150,000-household dense dataset in at most two builds (#1301) - #1307
Conversation
Independent adversarial review of this PR at ea68813Run with Claude Code on 16–17 October 2026 at ea68813: seven independent review lenses (correctness, method, generated artifacts, tests, claims against the diff, disclosure, a second fresh-eyes pass), then one to three skeptics per finding who tried to refute it; (k/n) is how many agreed. No builds and no feed-gated tests were run. The PR head has not moved since the review. #1307 can merge on code and CI grounds. All 10 checks pass at ea68813, and about 2,300 tests on the touched surfaces pass locally. No finding shows wrong numbers in a build, or a release gate that fails open. Two things need settling before the PR is used for the release:
BlockingThese don't block the merge itself. They block using the drafter for PR-B, which is only days away, so fix them in this PR.
The Cross-PR finding X-s01 blocks build 1. See below. Should fix
Minor
Cross-PR
Disclosure check
Tests and tools runEngine-free pytest on the touched surfaces:
Spot runs:
Repo tools in check mode:
Probes, on synthetic inputs only, covered: the L0 search with a stubbed optimizer, base against PR; the smoke test's mass curve; walker and Other checks:
Checked and found sound
Refuted during verification
Not covered
🤖 Generated with Claude Code |
The dense release contract's disclosure walk refused three shapes of per-area support figure: a figure under its name in a record with an area_code or beneath an area key, a min_ or minimum_ name outside the floors, and "rows 7 < 50" text. The #1307 review found shapes that slipped past it: a code-keyed map under a figure's name, a columnar table, a record identified by geography_id or code, figures in a list, max_ and median_ names, and any *.json that did not parse, which the walk skipped. No writer emits these today, but the walk is the last check before a release leaves the machine, so it should not depend on today's writers. The walk now refuses an area's rows, Kish ESS or distinct sources, as a number or as numeric text, in a record identified by area_code, geography_id or a code that holds an area code; under an area key beneath the figure's name, through level keys and lists; in a table with an area column, as parallel lists or pandas {row: value} columns; and as a minimum, maximum or median of a figure, however nested, outside the declared floors. A shipped *.json the walk cannot parse, decode or walk now fails rather than being skipped. Failures still name where a figure is, never its value. The text rule is unchanged. The refusal tests' fixtures use placeholder codes (E06999999, E06999998) where they paired a real local authority with plausible support figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response to the adversarial reviewEleven commits on top of ea68813, ending at b10840d. Every finding is either fixed or ruled on below. The touched tests pass locally, the CI test plan verifies, and ruff is clean. The PR body has been rewritten to match: the commit list, the build-2 recipe, the scope of Blocking
Should fix
Minor
Cross-PR
🤖 Generated with Claude Code |
Re-review of this PR at b10840dRun with Claude Code on 17 October 2026, after the eleven commits answering the first review, the response comment and the rewritten body.
VerdictNot ready for build 1. Almost every first-round finding is fixed, and CI is green (10/10). But one fix, 66c871e's widened disclosure walk, now refuses every real dense release at assembly. There is also a disclosure question about the drafter's national counts that needs María's ruling before build 1. Blocking
For María's ruling (disclosure; nothing is published yet)
Should fix (low)
Nits
First-round dispositionsMost are fixed in code; the reviewers verified each one.
Cross-PR
Disclosure checkNo breach in what changed since the first review: the eleven commit messages and their added lines, the runbook, the changelog, the register reasons, the regenerated rationale strings, the body and the response. The forward-looking question is above. Tests and tools run
🤖 Generated with Claude Code |
…1301) A K=15 dense release has to be publishable before the Budget in at most two builds. The five-fold rotated holdout repeats the dense solve five times, and no candidate has ever passed the incumbent-surface assessment, so both stood between a green gate battery and a release. They become report-only diagnostics, and the release says what it does not claim instead. --rotated-holdout replaces --skip-holdout, with no alias: a dense build skips the holdout unless asked and records the skip, and the national line, which has no holdout node, refuses the new flag and keeps recording skip_holdout false. The pre-flight accepts an unrequested holdout and still wants five folds and a finite mean when one was run. The scorer reads the active reference count from the membership file and scores a candidate with no holdout. The size evaluation runs its incumbent steps only behind --incumbent-diagnostic. Certification readiness stops gating on the scorecards or a skipped holdout. The contract's dense check is split so that the candidate's own joins (dataset, diagnostics, gate report, clean code, Ledger pins, measure exclusions) always run; previously they lived behind the incumbent files and would have been skipped with them. The three incumbent files ship all together or not at all. Their identities and the pinned row inventory refuse; their measured limits are recorded in the manifest, not enforced. The coverage carries holdout.claim, incumbent.compared and a size block, and the manifest's limitations must match them. The assembler takes --incumbent-manifest as optional, requires the full gate report, from which it names the reviewed exclusions the gates stood on (names and reasons only, never the measured error), and prints a --tag-only publish command. The holdout node's code hash moves, so the dense solve re-keys; that is intended before build 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dense release ships the signed local gate report, both diagnostics files and the candidate manifest, and all four carried per-area support figures: an area's rows, Kish ESS and distinct source households. The area-support gate put them in each reviewed exclusion and in its failure lines and reported each level's minima; the weakest-area diagnostics and the support-limited misses listed them per area; the manifest's support block held the minima. The signed report cannot be redacted after the build, so the gate itself now names only the floors an area misses, records a reviewed exclusion as its shortfalls and its signed record, and counts the areas checked and below the floor by level. The diagnostics rows and the support-limited misses carry support_below_floor instead, UKAreaFit's figure fields become optional so earlier diagnostics still parse, and the manifest's support block counts areas. The figures stay in the run's area-support table, which no release ships. Weights and per-cell estimates ship as before. The dense contract walks every shipped JSON document, the release manifest's strings included, and refuses a support figure in a per-area record, a minimum of one outside the declared floors, or a shortfall worded with its measured value. A refusal names where the figure sits, never its value. The full gate kernel now also hashes gates and terminal_gates, so an edit to a gate evaluator or a register loader re-keys the gate nodes. This commit re-keys them, which is intended before build 1. After a passed read-back, the best-effort refresh upload could fail and set UPLOAD_FAILED, which the delivery contract refuses beside a passed read-back. The refusal escaped the dense driver before it recorded the staging delivery and spooled the Logbook row, so the build exited 1 and the assembler then refused the run. Once read-back has given its verdict, the verdict owns last_error_code; a failed refresh still shows as an upload attempt without a success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1301) The local size doctrine asked for 100,000 households, which K=15 cannot reach: at the solver's L0 floor of 1e-7 the size search's open mass fell short, and the 100,000-household attempt then spent its third probe re-running 1e-7 on a bracket that had collapsed onto the floor. The #1124 ruling, as amended for #1301, sets 150,000 households at K=15 with the draw, refit floor and refit penalty unchanged, and names only the gates and the pre-flight as publication requirements; the holdout and the incumbent comparison are opt-in diagnostics since the first #1301 commit. calibrate() takes a budget_search_bracket whose default stays [1e-7, 10], so the US contract and every other caller search exactly as before. The UK size search passes its own bracket, from the new UK_LOCAL_L0_SEARCH_FLOOR (1e-10) up to the solver's ceiling, through select_uk_dataset_size's default, which the holdout folds inherit. The floor is a parameter of the search, draw, refit and holdout nodes, since the doctrine module is not in their code hash, and the size receipt records the bracket the search ran in, read from the search's own receipt; the pre-flight checks it on a sized run. The search itself refused nothing and wasted probes. A warm start outside the bracket was clamped to the bound while the receipt recorded the value asked for, so --selection-initial-lambda 2.5e-8 silently became 1e-7; it is now refused, in the solver, in the size module, in the graph config and when the command is parsed, together with a warm start given without --dataset-households. A warm start is probed at exactly the value given. No penalty within 1e-6 decades of a measured one is probed again. A probe on the floor or ceiling that asks to go past it stops the search as search_floor_reached or search_ceiling_reached, and a bracket narrowed onto a measured penalty as bracket_exhausted; both receipts record the stop and the bracket. The warm edge probe follows the first probe's own steer: a warm start on the floor that asked for a smaller penalty was sent half a decade upward. Editing solve.py, dataset_size.py, local_doctrine.py and full_measure.py re-keys the dense solve, the size nodes, target compilation and the holdout, which is intended before build 1. The calibrate parity pins and the US spec-engine pins that attest solve.py move with the final pin pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failing local cell or a below-floor area found in build 1 has to be signable before build 2 without changing the solve; otherwise the sign-off re-keys and re-runs the whole dense build. Neither could be: uk_local_target_fit read no register at all, and the local reference generator built its support-floor cell deferrals from local_area_support_exclusions.json, so signing an area there would also have deferred its cells. uk_local_target_fit now reads its own register, uk/local_target_fit_reviewed_exclusions.json, committed empty and keyed by the gate's family/area_code/metric cell names. An in-force entry lifts the fence for its cell only and the cell stays bound in the solve; stale, expired and not-yet-in-force entries fail. The gate evaluates entries on the review date the other register-reading gates share rather than the wall clock, honours release_lines, and records evidence_absent without a review date. The size evaluation keeps signed cells on its red-row ledger, since they still breach the bound. The area-support exclusions become gate-only. The binding honours release_lines, so an entry for another line stands aside unchecked, while the bound-despite-floor approvals stay window-checked on every line. A constituency key is honoured like a local-authority one, and the assembler names each exclusion's own grain in the release limitations. The Isles of Scilly and City of London cell deferrals and the families bound despite the floor move into the generator as SUPPORT_FLOOR_DEFERRED_AREA_IDS and SUPPORT_FLOOR_BOUND_FAMILIES. Lockstep tests keep the deferred areas inside the register's local-authority exclusions and the bound families equal to its approvals, and a perturbation test signs synthetic entries at both grains and checks that the generator's deferrals do not move. The committed local references and membership regenerate byte-identical from the pinned feed. The gates.json edit moves the gate-battery contract digests, which are re-pinned with the other derived pins in the final pin pass. The new resource joins the country package and so re-keys target compilation and everything below it, which is intended before build 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Most high-value homes cells at constituency and local-authority grain sit
below about one household weight at the release size, so the family needs
a control above them. Three two-level targets
(microcosm.high_value_homes.over_{1m,1_5m,2m}_by_region) bind the derived
Land Registry stock at the nine English regions and Wales. They copy the
local targets' measurement block and record-set selector, so each row is
the cross-grain control for its area cells and the three no_higher_control
local routes become stale and go.
The rows must not bind on the national line: there main_residence_value
is the WAS donor's, not a Land Registry placement. A new contract key,
calibration_lines, names the lines a target binds on. The region rows
declare ["local"]. load_uk_national_target_inputs, the release-cut
certifier's calibration reference and the UC matrix diagnosis drop them
right after the compile, before reconciliation, the measure exclusions and
the frozen-register check, so the national registry version is unchanged;
register_completeness.calibration_line receipts the drop on both lines.
Every national compile now merges the derived facts as the local ones
already did (both full_targets loaders, the certifier, the parity tool's
national receipt with an HVCTS ledger_only rationale, the national
reference generator, the incumbent-surface evaluation), so all committed
references compile on every line. The generator's region-tier fan-out now
honours area_scope by level, giving Wales at country level and no
Scottish or Northern Irish cell, and records a fan-out outcome.
The bound values are per-household measures in measure_simulation, read
like the bus boardings: the stored main_residence_value and tenure_type
through high_value_home_mask at each threshold's frame-basis value, as the
local metrics and the release gates read them. A persisted column of any
of them is refused.
The regenerated references, membership, parity receipts, census and
registry_parity accounting follow in the pin commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every register edit that changes the compiled problem has to be on the branch before the first local release build, because the second build reuses that solve and may change only gate-only registers. obr.ni_employer becomes a measured diagnostic in the calibration measure exclusions. The engine's ni_employer is gross of the Employment Allowance, which OBR's receipts are net of, and the row's calendar-2025 window takes a quarter of FY2024-25 that the engine prices on FY2025-26 rules. Modelling the allowance alone would close only part of the gap. The entry tracks policyengine-uk#1918 until the drafted issue that splits the allowance out of it is open, and takes the three-month window of the obr.vat engine-factor diagnostic. The UC support tool's protected probe stops asking for the row, which a new run's matrix no longer carries. The high-value homes region rows now control that family on the local line. The local reference generator therefore defers its constituency level whole (high_value_homes_constituency_grain_deferred), after the English band-H precedent, with a roster guard on the 575 England and Wales seats. A new signed small-cell rule, hvcts_la_household_floor, defers the local-authority cells under 400 homes, about two household weights at 150,000 households. In the generator's second pass the rule finds no active constituency cell, and the region rows are not local targets, so it reaches the authority cells only. The family's binding adjudication now records where it binds, and that the seat rank correlations, out of sample once the seats are deferred, decide its renewal. The three no_higher_control local routes already went with the region control. Orkney and Shetland are pre-signed gate-only exclusions of the area-support floor, scoped to the local line, so the first build needs no sign-off for them. Their cells stay bound and the solve does not move. The regenerated local references, membership and compile-parity receipt, and the counts that move with them, follow in the pin commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The local release is built at most twice: build 1's failing cells and below-floor areas are signed in gate-only registers, and build 2 at the sign-off's head must recompute only the calibrated gates, the export, the package and certification. Today every UK register feeds the country fingerprint, which the target kernels, the preflight, the holdout and the spine gates hash, so a sign-off re-keys everything from target compilation down and build 2 is a full rebuild of 10 to 16 hours. Those nodes now key on a solve fingerprint, ResolvedCountrySpec.fingerprint_excluding over every packaged resource but uk_runtime.solve_scope.UK_GATE_ONLY_RESOURCES: the target-fit, local target-fit and input-mass registers and local_area_support_exclusions.json, which the gate-only area-support change left without a solve reader. The excluded names are bound under their own domain tag. The source preflight runs under its own kernel, uk.full-gates.preflight@1, because the dense solve takes its report as an input; the calibrated gates keep the full fingerprint, so a sign-off re-keys them and nothing before them. Each gate kernel refuses the other's phase. The degenerate register stays solve-scoped, since the release contract pins its digest, and the national graph is unchanged. --gate-review-date gives the calibrated gates their own clock, by default --review-date. It may not precede the solve clock or, when given, be later than today, and only the calibrated node carries it, so a later sign-off date re-runs the gates alone. The export bindings record both clocks. --require-solve-reuse replays the spine and preflight phases and then every strict ancestor of uk.full.gates.calibrated with resume="require" before calibration, and turns a store miss into SolveReuseError naming the missing nodes, so a build 2 whose inputs drifted fails in minutes instead of silently re-solving. It is refused with --resume forbid, and the national role refuses both flags. test_uk_solve_scope.py holds the scoping sound from both sides. An AST scan of the build source and tools allows only reviewed modules to name a gate-only register, and none of the target-compile, solve, spine or generator modules. Every gate that reads one is a terminal gate. Editing each one leaves every solve kernel's hash unchanged while the calibrated gates' moves, and a solve-scoped edit moves them all. The preparation test checks the same on the node keys of a real prepared graph, and the engine admission test traps every gate-only read through target compilation and the preflight. Node keys from target compilation down move once; no pin does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dense release candidate blocked only by national target-fit rows, local target-fit cells or areas below the support floor can be released by a second build once those failures are signed in gate-only registers. Writing that sign-off by hand from a blocked build is slow and easy to get wrong: an entry that is premature at build 2's gate clock, a stale entry left in place, or a figure copied into a reason. tools/draft_uk_dense_signoff.py reads build 1's run directory (the full gate report, the signed local gate report, the target and area-support tables and the Logbook row) and checks them against one another and against the checkout's gates.json. It drafts release_lines ["local"] entries for every signable failure, the removal of every entry build 2 would fail and the register pins (test_uk_terminal_gates.py for the two target-fit registers, test_uk_weighted_integrity.py for the area exclusions), as a git-apply patch with a counts-only PR body. Any other release-blocking failure makes the build unsignable. The local reference generator keeps its own support-floor constants, lockstep-tested against the area-support register, so the tool never edits the bound_despite_support_floor block and never retires or renews the exclusions of the areas whose cells the generator defers. If build 2 would fail one of them, the area gate is reported unsignable with what to do: a stale entry needs the generator, and so the solve, to move; an expired one is renewed by hand. It never touches the degenerate register either, whose digest the contract pins. Reasons cite the build-1 attempt id and the full gate report's sha256, carry no figures and pass the release contract's disclosure walk, since build 2's signed local report ships them. Per-row errors and per-area support figures go only to a private review file outside the repository. A checkout whose gates.json wires no local target-fit register reports local cells as unsignable. --verify checks that build 2 stood on exactly the committed entries, that no release-blocking gate failed and that its signed local report passes the dense contract and its disclosure walk. With --build-1 it also reports whether build 2 reused build 1's solve. The three fixed-clock target-fit tests in test_uk_terminal_gates.py now read the register as the national line does, so local-line sign-offs approved after their clock leave them unchanged. The runbook's between-builds section now holds the second-build steps too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earlier #1301 commits changed what the committed surfaces derive from and left every regenerated file and moved pin to one pass, so that each is computed once, from the final code, against the pinned 735820c feed. The high-value homes region targets join the contract's registry-parity scope as unmapped declarations, since uk-data has no such metric. The national references and membership gain their 30 region-tier cells, which the national line still drops after the compile; the national compile-parity receipt records them as ledger_only. The local references, membership and compile-parity receipt lose exactly the high-value homes cells that the constituency-level deferral and the signed 400-home authority floor now defer, and nothing else; the local census names the region rows in the family's fence, description and source. Under the doctrine's target-weight rule the compiled local-line surface still meets both family loss-share floors. The gates.json edit that wired the local target-fit register moves the gate-battery policy, manifest and fingerprint pins and the dense line's gate digests; the spine, calibration-seam and release-cut part digests are unchanged. calibrate()'s budget_search_bracket moves the US seed-protocol and seed-map hashes, the spec-engine coverage attestation, the loader's golden vector, the multi-spine pool tool's spec hash and the calibrate parity case's implementation hash and node key, re-pinned on arm64/darwin/py3.14; its pinned bytes are unchanged. The count pins move to the regenerated files, and the local generator's contract-refusal test gets a constituency roster the new 575-seat deferral mask accepts, as it already had for the council-tax masks. National cuts certified before this commit fail validate_release_dir at the new head, as with every gates.json edit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine issue that splits the Employment Allowance out of policyengine-uk#1918 is now open as policyengine-uk#2459, so the obr.ni_employer measure exclusion tracks it, as its adjudication said it would once the issue was open. #1918 keeps the under-21 and apprentice zero rates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s sign-offs (#1301) --require-solve-reuse admitted three requests that cost the two-build release its second build, and each surfaced late. Without --graph-store the store defaults to <out>/.graph-store, and build 2 writes a new --out, so the store is empty and the build stopped with SolveReuseError only after preparing its inputs. Pointed at build 1's --out, nothing stopped it: publication replaced build 1's bundle, the one its sign-off was drafted from and that draft_uk_dense_signoff.py --verify --build-1 reads. And without --gate-review-date the calibrated gates evaluate on build 1's --review-date, so every sign-off approved since fails as premature, but only after the solve replay, and a third build follows. validate_cli_args now refuses, before anything runs, a solve-reuse build without --graph-store or with one that is not a directory, and one whose gate clock (--gate-review-date, else --review-date) precedes the approved_on of any entry the calibrated gates would hold in force. The refusal names the earliest gate clock that puts them all in force. Those entries come from the four gate-only registers through the gates' own loaders and line rule (battery_bindings._in_line, on the local line), plus the area-support register's bound_despite_support_floor approvals, which that gate window-checks on every line. An unsplit clock stays admitted whenever nothing is premature on it, and a build that reuses no solve is unchanged. The dense attempt refuses an --out that already holds a bundle (its build.json completion marker) before the attempt opens, as the national line refuses an occupied --out, so nothing is written into build 1's directory; dry runs are not refused. rowwise_cli becomes a reviewed reader of the gate-only registers, limited to gate_only_signoffs, and a lockstep test holds that function to UK_GATE_ONLY_RESOURCES. The solve-reuse tests stand synthetic registers in for the committed ones, so none depends on what is signed on the day it runs, and the replay test now follows the recipe: build 1's store and a new --out. The two-build recipe in docs/uk-full-build-graph.md now has build 1 pass --review-date and --graph-store, and build 2 repeat both with a new --out, --gate-review-date and --require-solve-reuse; the guard lists the new refusals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The national targets apply their measure exclusions, and the national problem resolves its measures, in measure_simulation, but _NationalKernel's implementation hash named full_targets and full_measure and not the module they call. An edit to the measures alone therefore left the national targets, the problem and the nodes after them as cache hits computed by the old code. The dense target kernels already hash the module. Leaving it out would not have kept the national graph's keys: they move at this head anyway, since full_measure, full_targets, ledger_targets and local_target_census changed and so did the country-spec fingerprint. So the fix costs no extra cache miss. A test like the dense one holds both national kernels to the module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sub-1e-7 smoke test ran on gates at the default init_mean, which stay near 0.999 from 1e-6 down, so its open mass crept up by under a hundredth of a record a decade and the monotonicity check held on a flat curve. It also passed when the solver lifted every penalty to 1e-7, the floor the size search now goes below, and when Adam's eps was raised to 1e-6, which stops penalties below 1e-8 from steering. The test now seeds the gates with contribution_initialization, as the UK size search does, which starts them at an open mass of about 210 of 400, and asserts that each decade from 1e-7 to 1e-10 opens at least ten more records of mass. Informed gates alone do not get there on 400 records: each record carries about 1/400 of a target, a pull on its gate that swamps penalties this small, and below 1e-7 the steps sit within seed noise, with a step falling on three of four seeds. Loss scales 3e5 times the targets cut that pull to about 1e-8, and at the UK solve's learning rate of 0.15 the open mass runs 3.5, 42, 211 and 247 across the four penalties on this machine. Over 20 optimizer seeds on two fixture draws the smallest step was 32. Both mutations above now fail the test. Test-only: solve.py and the calibrate parity pins do not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dense release contract's disclosure walk refused three shapes of per-area support figure: a figure under its name in a record with an area_code or beneath an area key, a min_ or minimum_ name outside the floors, and "rows 7 < 50" text. The #1307 review found shapes that slipped past it: a code-keyed map under a figure's name, a columnar table, a record identified by geography_id or code, figures in a list, max_ and median_ names, and any *.json that did not parse, which the walk skipped. No writer emits these today, but the walk is the last check before a release leaves the machine, so it should not depend on today's writers. The walk now refuses an area's rows, Kish ESS or distinct sources, as a number or as numeric text, in a record identified by area_code, geography_id or a code that holds an area code; under an area key beneath the figure's name, through level keys and lists; in a table with an area column, as parallel lists or pandas {row: value} columns; and as a minimum, maximum or median of a figure, however nested, outside the declared floors. A shipped *.json the walk cannot parse, decode or walk now fails rather than being skipped. Failures still name where a figure is, never its value. The text rule is unchanged. The refusal tests' fixtures use placeholder codes (E06999999, E06999998) where they paired a real local authority with plausible support figures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ence (#1301) A dense release names every reviewed exclusion the uk_target_fit, uk_local_target_fit and uk_input_mass_parity gates stood on, and the contract checked the local target-fit names against the signed local gate report. The two national gates' names come from the full gate report, which does not ship, so the contract could only hold the manifest's limitations to the coverage's own list: a hand edit that dropped a national name from both still validated, where the same edit to a local cell was refused. The build's candidate manifest projection now records, for each of the three gates, the sorted names it stood on, dormant entries left out as the assembler leaves them out, under reviewed_exclusions_stood_on. The release ships that manifest and hash-joins it to the dataset, the diagnostics and the signed report, and the contract now holds the coverage's names to the record gate by gate, so the limitations follow it; a candidate without the record is rebuilt. The record repeats names the limitations already ship. Like the measure-exclusion, holdout and size records the contract already reads from it, the manifest is unsigned: an edit to it as well would still pass. graph_terminal.py is in the implementation hash of the preflight and calibrated gate, holdout, export, package and inspection-export kernels, and contract.py in the certification kernel's, so the dense graph re-keys from uk.full.gates.preflight on, the dense solve included, as the earlier #1301 edits to graph_terminal.py do. No national kernel hashes either file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dense assembler's docstrings, the runbook and the changelog said a reviewed-exclusion limitation carries "names and reasons only, never the measured error". The gate's own measurement of the row is indeed not copied, but the reason is the register's reviewer prose, copied verbatim up to 300 characters, and it can carry figures: the input-mass register's reasons cite national totals and a percentage. Nothing published is wrong today; the wording promised more than the code checks, so it now says what is copied and that a reason may cite figures of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ext figures (#1301) The drafter's PR body printed stale-risk and near-bound counts for every register. A local cell signed just beyond the 25% bound has its family/area_code/metric key in the same patch, so a stale-risk count there tells a reader that a named local authority or constituency estimate lies within a point of the bound: a sub-GOR figure with no disclosure check. Only the national register's counts now reach the PR body and the receipt. The local registers' counts go to the tool's standard output and stay as per-row flags in PRIVATE_review.csv, and the body's disclosure line now lists exactly what the body carries. Reviewer free text was checked only for '%', and the release contract's disclosure walk catches only an area's support worded as "rows N <". A household reference with its weight, "N percent above its target" or "7 rows against the 50-row floor" passed into the committed reasons. --class-note text, --area-name names and --adjudication may now carry digits only in issue references and ISO dates, and --approved-by, which lands in every entry, must be a GitHub login; all four are checked before the build is read. The runbook and the changelog fragment say what the checks cover and what they miss, and the runbook's manual scan now covers signoff.patch as well as PR_BODY.md. --out was refused only inside the tool's own checkout, so the private review file could land in another checkout or a sign-off worktree. Any --out inside a git work tree is now refused, found by git rev-parse from --out or its nearest existing parent, and .gitignore covers PRIVATE_review.csv and signoff-draft/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1301) The sign-off draft removed a national target-fit entry that applies on every line whenever build 2 would fail it on the local line, for instance because dense build 1 fits the row inside the bound, and replaced one with a local-only renewal when build 1 still breaches the row and the entry is out of its window. The register is shared with the national line, which may still stand on that approval, so the next national build or certification would fail on a row someone had already signed. Such an entry is now never removed or replaced: it is rescoped to release_lines ["national"], otherwise unchanged, and the receipt says why. An expired one also goes to review for the national line to renew or retire. Kept beside newer approvals it breaks the national graph test's review invariant, so the draft is then reported unsignable until that is done. A row build 1 still breaches under such an entry needs a hand sign-off, because a register key holds one entry and no local-only entry fits beside it. --verify checks that every rescope was committed as drafted. Local-cell entries are still removed, since the national line reads no local cell. The gate-clock range came only from the gate-only registers, so build 2 and --verify could pass on the day a measure exclusion expired and assembly refuse it the next, while renewing the exclusion changes the solve and forces a third build. The drafter now reads the measure exclusions build 1's solve ran under from its candidate manifest or, when the gates stopped build 1 before the export, from the target compilation's materialized surface, and takes the local line's degenerate-register entries from the checkout. The range ends at the earliest expiry among these and the in-line gate-only entries, which the receipt and the PR body print as the assembly deadline, and a --gate-clock past a solve-scoped expiry is refused. --verify fails once the deadline has passed and warns from two days before it. The runbook's build-1 command now passes --review-date and --graph-store, whose defaults do not carry over to build 2, and build 2 repeats both with a new --out, --gate-review-date and --require-solve-reuse. It also chains its Logbook row on build 1's: the build does not check this, but the Logbook store and the archive export refuse a fork or a second genesis on the uk/local chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rows it leaves bound (#1301) The hvcts_la_household_floor rule said a cell under 400 homes "rests on one or two sample households" and that "one household's placement decides" whether it passes, and the constituency deferral said a cell "resting on one or two sample households is fitted by noise". These are expectations from Land Registry-derived stock at 150,000 households, not counts of sample records, but they read like counts. The generator also copies both texts into the committed membership: the signed_rationale of 598 authority cells and 1,725 constituency cells, and six signed deferrals. Both texts now state the expectation: a cell below 400 homes is about two household weights at 150,000 households, so its fit would be noise. The rule text now also names the two region rows below the floor: microcosm.high_value_homes.over_2m_by_region@E12000001 (North East) and @W92000004 (Wales). The rule defers every £2m authority cell in both regions, so each row is its region's only £2m control. Region rows are the family's control and outside the rule's scope by design, so both stay bound. Each is signed gate-only (uk_target_fit, release_lines ["local"]) only if build 1 measures it past 25%. Neither is signed in advance, because an entry on a row that fits fails the gate as stale. The changelog says the same. The local references, membership and compile-parity receipt were regenerated on the pinned 735820c feed. Only the 2,329 rationale strings in the membership move. The counts stay at 22,986 active of 27,115 candidates. The references and the receipt are byte-identical, and the census check reports current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The measure-exclusion register has no line scoping, so the obr.ni_employer entry drops the row from the national objective as well as the local one. Its adjudication now says so, and says what that means for the frozen national build. A national run given a frozen scoring register derived before this entry fails closed, because the re-derived register differs, so that build's frozen register is derived from a head that carries the entry. The reason no longer calls the gap "not a weight question". The parts it names (the Employment Allowance, the calendar-2025 window and the under-21 zero rate) explain roughly half of the excess. The rest sits in the pay level and is under investigation. It may be a conflict between the bound pay targets and OBR's NICs base rather than a weighting defect. The phrases test_employer_nics_are_a_measured_diagnostic checks are kept. The solve-registers changelog now says both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The L0 bracket changelog said callers on the default bracket "search as before". They keep the same bracket and the same cold path. On the warm path, though, a start outside the bracket now raises instead of being clamped, and the search now stops at a bound instead of bisecting toward it. test_the_frozen_count_is_the_committed_active_reference_count compared two reads of the same membership key, so it could not catch drift. It now pins 22,986, the count test_uk_battery_bindings.py pins as candidate_targets from the committed local references, and says the two pins move together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase (#1301) The dense contract's disclosure walk runs only after both builds: at assembly, in the release pre-flight, at publication and in the sign-off verify. A refusal there could cost a re-run of the gates for a finding a reviewer can weigh. validate_release_dir now returns the walk's findings as warnings, and an empty list on every other line. The dense assembler prints them and lists them in its summary, the release pre-flight prints them without failing, publication repeats them as Python warnings, and draft_uk_dense_signoff.py --verify reports a figure in build 2's signed local report as a warning. The walk over the drafted entries still refuses the draft: re-drafting costs nothing, and those entries are committed to the public repo in the sign-off PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drafter scopes an all-lines national target-fit entry that build 2 would fail on the local line to the national line, unchanged otherwise, because the national line still stands on it. --verify --draft-receipt checked that the committed entry was national-only, but not that the sign-off left the rest of it alone, so an extended expiry or a reworded reason would have changed the national line's approval under cover of the local line's sign-off. The receipt now keeps each rescoped entry as committed before the sign-off (rescoped_from: committed register text, not build 1's evidence), and --verify fails when any field but release_lines differs from it in the committed register, naming the entry and the fields. A receipt that does not record the entry fails too, asking for a new draft. The module docstring says what --draft-receipt checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The drafter's assembly deadline and gate-clock range disagreed with the gates they predict. The area-support gate window-checks every bound_despite_support_floor family whatever its release lines (battery_bindings._evaluate_area_support), but the drafter kept only the families on the local line; and the input-mass gate holds its in-line entries to their windows on build 2's gate clock, but the drafter left them out. Nothing is affected today (the only bound family applies on every line, and the input-mass entries expire in April 2027), but a national-scoped family or an input-mass entry expiring first would have let the receipt print a range build 2 then fails. gate_clock_entries now gathers, for draft and --verify alike, the local line's entries of each wired register after the sign-off, every bound family, and the local line's input-mass entries, which the tool reads (input_mass_in_line) but never drafts. Their expiries join the measure exclusions and degenerate entries in the deadline, and their approvals, with the degenerate register's, start the range. An input-mass entry out of its window at the gate clock makes the draft unsignable until it is renewed by hand, as an out-of-window bound family already did, so the printed range is never empty. Tests cover a national-scoped family and an input-mass entry at each end of the range, an out-of-line input-mass entry standing aside, the unsignable case and --verify's deadline; dropping either source fails them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The pre-replay guard of --require-solve-reuse checked only that build 2's gate clock put every in-line gate-only sign-off in force from its approval. An in-line entry in force on build 1's clock but expired on build 2's was admitted, and the calibrated gates failed it after the replay. The degenerate register, solve-scoped but evaluated by those gates on the gate clock, was not checked at all, and the end of the gate-clock range the sign-off drafter prints (its assembly deadline) was never enforced, so a gate clock past an expiry the release depends on went through. The guard now reads every window the drafter bounds build 2's gate clock by (solve_reuse_windows): the gate-only registers' entries the calibrated gates hold on the local line, the degenerate register's local entries, and the calibration measure exclusions the solve applied (--measure-exclusions, else the packaged register), which assembly and upload check on the day they run. A gate clock before any approval or after any expiry is refused before anything runs. The refusal names the entries out of their window and the usable range: on or after the latest approval, on or before the earliest expiry, the latest usable gate clock. When the windows do not overlap it says no gate clock is usable, and an expired degenerate entry or measure exclusion is flagged as needing a third build, since renewing one changes the solve. The earliest-expiry arithmetic is shared: rowwise_cli.earliest_expiry is what the drafter's assembly_deadline returns. A lockstep test holds the drafter's gate_clock_entries to the guard's windows on the same synthetic registers, line rule included. The solve-reuse tests now stand in the degenerate register and the measure exclusions too, so none depends on what is committed on the day it runs. The premature test's "in force" target-fit entry had in fact expired before the solve clock; it now covers the gate clock it names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) The guard reads each register through the calibrated gates' line rule (battery_bindings._in_line), so an entry scoped to the national line neither refuses nor admits build 2's gate clock. Only the target-fit register's rule was pinned: with _in_line dropped for the local target-fit, input-mass or area-exclusion register, the guard's tests still passed. A parametrized test now holds every register the guard reads to the rule, the degenerate register included, for a premature and for an expired entry: scoped to the national line, the entry is set aside and the gate clock admitted; on the local line, or on every line, the same entry refuses it. Dropping the line rule for each of the five registers in turn fails the test, and only the target-fit mutation failed an earlier test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dense attempt refused a solve-reuse build into an --out that held a bundle, but keyed only on its build.json completion marker. A failed build-2 attempt leaves failure.json and a Logbook spool row instead, so a retry into the same --out was admitted and spooled a second attempt's row beside the first. draft_uk_dense_signoff.py --verify then refuses the run directory (its spool names two attempts), and the assembler takes the last row by file name. refuse_occupied_solve_reuse_output now refuses an --out holding build.json, failure.json or any logbook-spool/*.json row, naming what it found, still before the attempt opens and without writing into the directory. Other files, and a spool without rows, are not an attempt. The --require-solve-reuse help names the three markers. Tests refuse each marker on its own, admit the same directory without one, and run a failed second build whose retry into its --out is refused with the directory unchanged; keying on build.json alone fails them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runbook, the graph doc and the two changelog fragments now say what the preceding commits enforce: the allowlisted references, URLs and login shape the free-text guard accepts; the input-mass and bound-despite-floor entries in the gate-clock range and the assembly deadline; build 2's refusal of a gate clock outside that range and of an --out holding any earlier attempt; and --verify's check that a rescoped entry changed only its release lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On main after #1302: 25,327 compiled cells. The 18 Scottish BRMA rows are ledger-only by construction, and no signed value moves. The UC drift rationales now read their fixture/ours bands off the rows, and the support floor's deferred cells carry their own signed rationale. The release-blocking preflight gate uk_ledger_compile_parity_local_incumbent_2025 reads this receipt, so whichever of #1305, #1306, #1307 and #1308 lands last regenerates it on the combined tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ls (#1300) Every committed surface has off-surface cells, so the first reconciliation pass only records the UC bridge's factor and the second pass is the one that checks the 2% bound. Until now every refusal test ran without off-surface cells and so exercised the first pass only: turning the check off on the second pass, or restoring the old guard that skipped the second pass when no off-surface row joined the frame, left every test green. The new test runs the real rule with off-surface cells present: an unrelated cell that adds no row (a GB caseload of 300 over 200 constituency households is still refused), a UC cell that joins the frame (refused at the same 1.5), and a UC cell that leaves the authorities short of the caseload (refused at authority grain). Each of the two mutations fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bridge's bound reads each lower grain's total under its control: for the GB Universal Credit caseload, the constituencies' and the authorities' sums over Great Britain. It does not bound the nations and English regions inside a grain: the exact-signature group that puts each leg's authorities on its constituencies runs first and has no bound. The texts said "a leg" and "each grain on its own input total", which read as a per-leg bound. No behaviour changes. The shared operator's docstring, the UC bridge's declaration reason, the #1300 changelog line and the authority-tier test's name now say the bound applies to each grain's GB total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir ranges (#1300) The local compile-parity receipt's UC drift rationales still described the all-claims series: a uniform national rescale with fixture over ours in a fixed narrow band across 982 rows, and a children scatter range taken from that series. Since the abf0921 re-pin each area cell counts its own paid claims, so fixture over ours moves with each area's paid share. María has signed off on the rewording. The texts now say so, and each quotes the 5th to 95th percentile of fixture over ours, to three decimals, with the row count, computed from the rows it covers when the tool writes the receipt (the totals rows; the four child bands together), so the range cannot go stale. The gate does not read reasons; the next regeneration picks the texts up. Unit tests check the quoted ranges on synthetic rows and that other rows keep their rationales. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two more readers took the 7.4 GB feed whole. The CGT partition test read the file into one string and parsed every row; it now streams the lines and parses only the CGT rows. Every selector and ratio operand it compiles pins an hmrc.cgt_* source concept and none names a fact key, so no other row can match and the compile is the same. It also accepts CHRONICLE_UK_FACTS, as the other feed-gated helpers do, and skips as before when neither is present. The receipt tool read the file into one string and split it before parsing. It now streams the lines; both compiles read every row, so it still keeps the parsed rows, but no longer holds the text and its lines beside them. A unit test checks it returns the same rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
align_uk_local_registry_parity_fixture looked up each fixture row's contract target through _uk_local_metric_targets, which reads, parses and checks the whole UK population contract. The local fixture has about 23,500 rows, so the local compile-parity gate, the receipt tool and any replay spent three to four minutes here. The table is now built once per fixture and passed to each lookup; the aligned fixture is byte-identical (checked against the previous code on the committed fixture), and alignment takes well under a second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test (#1300) The release-blocking preflight gate uk_ledger_compile_parity_local_incumbent_2025 compares each signed value in the local receipt with the live compile, so a receipt the last regeneration left behind blocks every full or dense build and the release-cut certifier, and no CI test noticed (microcosm#1302 review). The local reference generator writes a reference for every cell that compiles on the pin and records each cell's compiled value in the membership, so the test rebuilds the compile from the committed references and membership (and checks the two agree), then runs the declared gate entry through its UK binding with nothing unsupported. It needs no feed and takes a few seconds. A second test moves one signed value and checks the replay names exactly it. On this branch the generated surfaces are left stale until they are regenerated on abf0921, so the replay fails here until then. Against main's membership and references it passes, and a corrupted receipt value fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unt (#1300) The re-pin procedure named uk/high_value_home_stock.json, which does not exist: the stock is uk/high_value_home_stock_facts.json, with its value knots and uk/high_value_home_stock.provenance.json. The abf0921 history entry said the local-authority energy selection reads gas by gas_year, which implies the selector pairs fuel with period type; it accepts both period types for both fuels, and its expected row count and the per-fuel reads do the pairing, as the changelog already says (1302-18). It said the bridge reads each grain on its own input total; it bounds each grain's GB total. And it and the changelog said the 3,508 UC cells' signed values move in the local receipt: 3,507 change, and one now equals the incumbent and leaves the receipt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The energy module's docstring said consumption is calendar 2024 kWh. Since the abf0921 re-pin the gas rows are DESNZ's gas year 2024, which the sub-national lookups pair with calendar 2024 (SUBNATIONAL_PERIOD_TYPES); electricity stays on calendar years. Docstring only. The other wording the re-review names in this module (the module docstring's account of the Northern Ireland interim, UK_ENERGY_LEVEL_BASIS and the interim text in the household-share receipt) is left as it is: the two stacked changes that retire the interim, #1305 and #1306, rewrite those same lines, and UK_ENERGY_LEVEL_BASIS is stamped into each kWh cell's metadata, so it moves with their regeneration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared operator accepted any finite positive max_factor_shift, and only the UK declarations loader required one below 1. A bound of one or more accepts every factor below one, since a negative factor is refused anyway, so it bounds one side only. _validate_rule now refuses a bound outside (0, 1), as the loader does. Every caller already passes a bound in range: the UK declarations (validated by their loader), the fan-out sum bridges (no bound) and the tests; nothing outside the UK builds a CrossGrainBridge. The refusal test adds bounds of one and above, and a bound just below one is accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock is built from feed facts (the census tenure record sets, the OBR non-owner-occupied share, the HPSSA seat cells), and the re-pin procedure asks for a check that they are unchanged on a new pin, but nothing ran it. Its provenance still names the 735820c feed it was built from. A feed-gated test now streams the pinned feed, runs the builder's own readers over every row that carries one of their selectors' strings, and fingerprints what they return. It compares that with the fingerprint recorded for the feed the provenance names, and asks for a rebuild if they differ. The fingerprint recorded for 735820c was computed on that feed and is the same on abf0921, so the stock stands and, as the procedure says, its provenance keeps naming 735820c. A second test, run without the feed, checks that the feed the provenance names has a recorded fingerprint, so a rebuilt stock records its own. The procedure names the test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Small-cell deferrals always join the reconciliation frame, but they were tested only with the bridges' bounds lifted. Two tests now run them on the real rule: a deferral that brings the authorities' GB total up to the caseload is accepted, the bound authority keeping its value, and one that leaves the total short is refused at authority grain. The committed surface has no UC deferrals, so nothing moves today. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After rebasing onto the #1300 re-pin, the references, memberships, both compile-parity receipts and the census are regenerated once from the final code against the pinned abf0921 feed, in the order the re-pin procedure gives. The rebase had kept main's version of each generated file at every conflict, so nothing in them was merged by hand. National: the two high-value homes region rows signed out below the floor leave the references and the receipt's ledger_only rows and are recorded as signed_excluded, 1,319 active references of 1,341 candidates. Local: the references are byte-identical to the rebased ones; the receipt drops the 2,323 deferred high-value homes cells, which leaves the union the #1302 re-review computed, and restates its 3,507 UC rationales in the new wording; the membership carries the floor rule's new text. No other value or row moves. The census check is current, the gate digests and the US spec-engine attestation are unchanged, and the count pins move to the regenerated files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b10840d to
e119867
Compare
Response to the re-reviewThis PR is rebased onto main after #1302 and regenerated on the abf0921 feed. On top of the earlier commits it carries the fixes below and #1302's remaining review items. The body is rewritten to match. Blocking
María's rulings
Should fix (low)
Nits
From #1302's re-reviewFolded in here, since #1302 merged (48ef616..b0a6914):
Cross-PR
Tests
🤖 Generated with Claude Code |
Third look at this PR, at e119867Run with Claude Code on 17 October 2026, after the rebase onto main (post #1302), the fixes for the second review, #1302's folded-in review items (labelled #1300), the regeneration on abf0921 and the second response.
VerdictEvery blocker from the second review is fixed in code, and so is every should-fix item except the walker's missed shapes (partly fixed). #1302's folded-in items are fixed except streaming (partly).
Two things need settling before build 1: the timeline, and a disclosure ruling on five more region rows. TimelineAt this head the drafter's and guard's usable gate-clock range ends on 22 October, the Unless #1308, which carries the renewal, lands first, build 2, For María's ruling before build 1 (disclosure)
Low
Nits
Still open from earlier rounds
Dispositions
Cross-PR
Disclosure checkWhat changed since the second review (the new commits' messages and the lines they change) carries no survey-derived figures. The forward-looking question is above. Tests and tools run
🤖 Generated with Claude Code |
…process (#1278) Second review of microcosm#1306, items A and B. The OBR leg still raised on a matching row with a missing or non-numeric value, or a period or dimensions field that is not a mapping, and passed NaN or negative counts as available. Such a row is now reported: a count must be finite and non-negative, a field that is not a mapping reads as empty, and an unreadable share leaves the band comparison standing. uk_terminal_only_code_hash is memoised, so an edit to the high-value homes files while a build runs cannot fail the calibrated gates after the solve. Its docstring now says what it buys: an edit to high_value_home_gates.py never re-solves, but high_value_homes.py also feeds the target kernels, so an edit there still does. The hash test covers the preflight kernel #1307 added, which inherits the calibrated kernel's hash, and the memoisation. Two comments are corrected: the regional split withholds regions until the withheld ones rest together on the floor, and its threshold follows the derived stock while the bands follow OBR's. The evidence summary, not the gate details, carries the frame total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) Third review of microcosm#1307. The drafter's free-text check accepted figures dressed as citations (a household and weight beside microcosm references, uk-data references counting rows, references to made-up PolicyEngine repositories, counts after anchors, far-off dates and logins carrying a number), and refused legitimate ones (a lower-case owner, review anchors, file URLs with digits in the path). Digits may now stand only in a citation of a repository on a fixed list, matched in any case: an issue reference, an issue or pull request URL optionally anchored on a comment or review with GitHub's ten-digit id, or a file URL. A citation running into a word, or right next to a noun a figure counts or belongs to, is refused, and an anchor stands only in a URL. ISO dates must lie within a year of the drafting date. Logins with digits must be on a fixed list. The sign-off fragment #1307 added now describes these rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up: the local feed checksThe reply said these would follow. They ran locally against abf0921. They caught a build failure, now fixed (89cc6b9)
Peak memory (1302-03)The build's target load on abf0921 peaked at 21.9 GiB and took about 20 minutes. The compile after it took 30 seconds within the same peak. So target compilation alone needs most of a 32 GB machine. Feed-gated tests
🤖 Generated with Claude Code |
) From the #1305 review: - Both private-rent routes in uk/cross_grain_declarations.json declare their level miss. The rows bind PIPR's level while the node holds each region at its lower FRS level, so with tenure bound, calibration closes the gap only by moving weight toward higher-rent renters (item 03). The BRMA route names its own composer, uk_private_rent_brma_mean_to_total (item 28). - The compile-parity tool routes the support floor's signed deferrals before the per-metric maps, so E06000053 and E09000001 no longer read as Northern Ireland's (item 31). Its UC drift rationales read their fixture/ours bands off the rows, as #1307's code does (item 09). - The parity census names PIPR's July 2026 edition, the record set the selector resolves (item 32). - The Scottish overlap note states the layer's correction counts and where they live: 9 filled, 59 corrected by the postcode lookup, 2 Balloch overrides (item 22). - A pinned digest of the atomic supports' (area, FRS region) pairs lets CI check the committed layer against supports it does not mount; the mounted test re-derives it (item 17). A ladder-free test checks the crosswalk's BRMA level against the committed layer (item 30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On main after #1307 and #1306: 27,267 rows, 23,004 of them compiled. The 18 Scottish BRMA rows are ledger-only by construction, and no signed value moves. The 40 cells of the support floor's two authorities, E06000053 and E09000001, carry the floor's own signed rationale; nothing else changes. The release-blocking preflight gate uk_ledger_compile_parity_local_incumbent_2025 reads this receipt, so whichever of #1305 and #1308 lands second regenerates it on the combined tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #1301. This is the code half of the plan: a K=15, 150,000-household dense (local-area) release that can be published in at most two builds. The rulings are on #1124 (16 October) and below (17 October).
Summary
The plan's nine ordered commits and a tracking commit, then the fixes from the first review. After #1302 merged, the branch was rebased onto main and gained the re-review's fixes and #1302's remaining review items. The replies are in the comments.
--rotated-holdoutreplaces--skip-holdout.--incumbent-diagnosticopts the incumbent steps back in.no_out_of_sample_claim,no_incumbent_comparison,engine_measurement,size_doctrine, and the reviewed exclusions the gates relied on. Exclusion reasons are copied verbatim from the committed registers, so they can carry national figures.max_excessis not one;validate_release_dirreturns them;--verifyprint them;--candidate-dirwalks the three files the assembler ships byte for byte, so a finding shows after each build.calibrate()takes abudget_search_bracket. Callers on the default bracket keep the same bracket and cold path. On the warm path, a start outside the bracket now raises instead of clamping, and the search stops at a bound.uk_local_target_fitreadsuk/local_target_fit_reviewed_exclusions.json.calibration_lines: ["local"]).row: {"dimension": "geography_id", …}).deferred_legs_without_control. A bound row on such a leg is still refused.obr.ni_employeris a diagnostic on both lines. The register has no line scoping, so the national objective drops it too, and a national build's frozen scoring register must be derived from a head that carries this entry. It is tracked on Model the Employment Allowance soni_employeris net of it (split from #1918) policyengine-uk#2459.--gate-review-dategives the calibrated gates their own clock.--require-solve-reusefails before the replay if:--graph-store;--outholds an earlier attempt (build.json,failure.jsonor a Logbook spool row);tools/draft_uk_dense_signoff.py.PR_BODY.mdcarries no local fit bands.microcosm#N,chronicle#N,uk-data#N,PolicyEngine/<repo>#N, with optional#issuecomment-N), PolicyEngine issue and pull request URLs, and ISO dates.--approved-bymust be a GitHub login that starts with a letter and holds at most one run of digits.--outinside any git work tree.--verify --draft-receiptchecks that only the lines changed.bound_despite_support_floorand input-mass entries included.The national target kernel now also hashes
measure_simulation.From #1302's re-review, folded in since it merged:
Rulings (17 October)
Two-build flow
Build 1:
Then the pre-flight with
--candidate-dir. Any national row, local cell or area that still fails is signed gate-only withtools/draft_uk_dense_signoff.py --run-dir <out-1>.PR-B carries register data and pins only. Merge it with a merge commit.
Build 2: run at PR-B's head with build 1's arguments and the same
--review-date,--graph-storeand warm start. Add:--out <out-2>;--gate-review-dateinside the range the draft printed;--require-solve-reuse;--logbook-prev-row-digestset to build 1's row digest.It recomputes only the calibrated gates, the export, the package and certification.
Then:
draft_uk_dense_signoff.py --verify, the pre-flight, assembly,validate_release_dir, and a tag-only publish (--tag-only --no-latest). Read every warning before publishing.Merge order and what is not here
Merge order:
Engine: the policyengine.py certification pins whatever policyengine-uk main locks at build 1.
Not here:
Notes for review
validate_release_dirat the new head, as with everygates.jsonedit.Testing
🤖 Generated with Claude Code