Skip to content

Sign the draught shares' degenerate exclusions and build on policyengine-uk #2695 - #1411

Merged
juaristi22 merged 10 commits into
mainfrom
uk-pair-release-2162
Oct 28, 2026
Merged

juaristi22 merged 10 commits into
mainfrom
uk-pair-release-2162

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

The next UK national + local (dense) pair was built from this branch. Five commits made it buildable and faster, a merge of main brings it up to date, and four more answer the reviews.

What changes

1. d444eb5 Sign the draught shares' degenerate-surface exclusions

The excise step (#1359) writes HMRC's published draught-relief share of beer and of cider clearances to every household. So household.beer_draught_share and household.cider_draught_share are constant by construction, and uk_degenerate_release_surface refuses them on both lines: it runs in the dense build's calibrated battery and in the national release-cut scope. No release built since the step landed could pass it.

  • María approved both entries on 2026-10-27: adjudication microcosm#1359, a six-month window, every line, worded like the Tax-Free Childcare routed share's.
  • The register is solve-scoped (solve_scope.py keeps it in the solve fingerprint), so the release contract's degenerate evidence digest moves with it.
  • The battery tests' exclusion clock moves to the newest approval date, as the fixture's own rule requires.

2. 5bd7273 Build on policyengine-uk 2.162.10 plus #2695 from its head commit, and 3. 469a44c Move the engine to policyengine-uk 2.162.12 plus #2695

PolicyEngine/policyengine-uk#2695 keeps every property allowance election, but stops each comparison branch from working out every benefit for the whole population. That cost is why Microcosm UK microsimulations have run several times slower since 2.157.0 (PolicyEngine/policyengine-uk#2694). On 2.162.8 this branch's spine took about three times as long as the 2.151.0 pair's, and the dense build's pre-solve stages about twice as long.

Until a release carries the change, the workspace installs policyengine-uk from the pull request's head commit, b71ce793. That commit merges the 2.162.12 release into the pull request, and is set through [tool.uv.sources] in the root pyproject.toml; the second commit pinned its earlier head, ead3444d on 2.162.10. The engine freeze accepts it as policyengine-uk 2.162.12: an exact version, a non-editable install and an intact RECORD.

The move from 2.162.8 also brings 2.162.9 to 2.162.12: the 2010 to 2012 minimum wage history, the ESA permitted-work limit set from the national minimum wage, participation responses that impute non-workers' wages once per step (outputs unchanged), and a CI-only release.

  • The uprating and bonus-sacrifice engine pins are re-taken with every value unchanged.
  • Concept coverage finds no input added or removed.
  • The coverage manifest and the H2 spine fixture are regenerated; only the source-manifest digest and the version mentions move.
  • The US worker identity pins the new lock.
  • The engine pin test reads the lock's direct reference.

4. db50ae8 Sign the dense build's gate-only failures and retire the stale entries

The first dense attempt (at 90ff43f, on policyengine-uk 2.162.8) failed only the degenerate surface, which the first commit fixes, and the two gate-only target-fit gates. tools/draft_uk_dense_signoff.py drafted its sign-off with the degenerate failure set aside, after checking that the failure named only the two draught-share columns the register now excludes. María approved it on 2026-10-27, before the rebuild, so the rebuild can pass without a reuse build.

  • National target fit: three rows added, five kept, one retired.
  • Local target fit: twenty cells added, seventeen kept, six retired.
  • Every entry is scoped to the local line and suppresses only its release fence; the rows stay bound in the solve.

The rebuild solves anew on the new engine. A row that moves there is re-drafted from the rebuild for a reuse build.

Local rows past 0.5. These are the error bands on both builds (d5f72865 and 6528c865); the values stay in the private gate reports.

  • Above 1: council_tax/S12000013/council_tax/band_f (added).
  • 0.5 to 1:
    • added: council_tax/W06000018/council_tax/band_h;
    • kept: age_structure/E14001131/age/20_30, age_structure/E14001319/age/20_30, council_tax/E09000020/council_tax/band_b and council_tax/S12000013/council_tax/band_a.

Every other signed-off row, national and local, is between 0.25 and 0.5. That includes the UC households with three or more children cell (uc_households/E14001098/uc_hh_3plus_children) and the three SPI savings interest bands.

5. daaf543 Pin policyengine-uk 2.162.14 from PyPI

2.162.14 is the first release on PyPI that carries #2695. 2.162.13's release run stopped at its YAML test job's 30-minute limit, so it never published; PolicyEngine/policyengine-uk#2697 raised the limit. Outside the tests, 2.162.14's package code is b71ce793, so the [tool.uv.sources] entry goes and the lock takes the registry release. The pins, coverage manifest and fixture move as in the third commit, with every value unchanged.

Merge of main

The branch was brought up to date with main by a merge, not a rebase. So db50ae8 and daaf543, the commits the two releases record, stay as they are.

Review answers: 8a3bec1, 40a1de5, f6fdc53 and 55d5c9f

  • 8a3bec1 Record a URL install's commit in the UK engine provenance (point 1).
    • A git install of an engine pull request carries the release's version string. So _rules_engine_provenance(), and the engine identity the builds hash from it, couldn't tell b71ce793 from policyengine-uk 2.162.12.
    • The provenance now adds what PEP 610's direct_url.json records: a git install's commit, an archive's hash, or a directory, and never a URL.
    • A release from an index writes no direct_url.json, so its provenance and identity are unchanged.
    • Neither release was built with this change. The national's record is a release install's anyway. The local area's names the version only: see "Which commit builds what".
  • 40a1de5 Describe the battery clock by the current registers (point 3): latest approval 2026-10-27, earliest expiry 2026-11-16.
  • f6fdc53 Name an archive install by every hash it records (Pavel's nice to have 1).
    • The record keeps every hash in archive_info, so an archive hashed only with sha512 no longer records a null.
    • An archive that records no hash is refused.
    • Tests now cover an editable directory and the unrecognised record.
  • 55d5c9f Key the UK country graph on the spine's engine provenance (Pavel's nice to have 3): build_uk_country_graph hashes _rules_engine_provenance() as the spine and full builds do.

Which commit builds what

  • Local area: built at db50ae8 on b71ce793, recording policyengine-uk 2.162.12.
    • It was built before 8a3bec1, so its record names the version only. Its engine identity equals a PyPI 2.162.12 install's.
    • The commit is traceable through the release's git_commit, db50ae8, whose lock names b71ce793, and through policyengine.py#725's recorded limitation.
  • National: built at daaf543 on 2.162.14, from a new spine on that engine. The two spines' tables are compared afterwards, since the package code is the same.
  • policyengine.py: certifies the national on its own build and routes the local area through policyengine.py#699's accepted model mismatch, with the regional input-surface audit.

Before the next cut

Builds

Both releases are built and published to the inspect lane. Neither is promoted.

  • Setup: the 2.151.0 pair's: K=15, 15 engine blocks, 150,000 households, seed 42, the doctrine's target weights, the Orrery off, and staging to populace-uk-private.
  • Local area: microcosm-uk-2024-25-dense-20261027T154955Z-6528c865.
    • The rebuild's solve matched the first attempt's, so the rows signed in the fourth commit held, and no reuse build was needed.
    • Every gate passed except uk_high_value_homes_support, which is diagnostic.
  • National: microcosm-uk-2024-25-national-20261028T061416Z-3c0d11b6.
    • It was built on its own 2.162.14 spine. That spine's household, benefit-unit and person tables are identical to the local area's.
    • The release cut passed 19 of 19 gates.
  • policyengine.py: Certify the Microcosm UK pair on policyengine-uk 2.162.14 (3c0d11b6 national, 6528c865 local area) policyengine.py#725 certifies the pair.

Testing

Run locally:

  • the touched tests: battery bindings, terminal gates, the contract, battery and engine-core pins, country spec, solve scope, source stages, the sign-off drafter, salary sacrifice, the apprentice draw, FRS stochastic draws, the uprating and bonus-sacrifice pins on the engine, policyengine-uk concept mapping, and the DfE spring term;
  • build_uk_release_input_coverage_manifest.py --check and graph_uk_spine_fixture.py --check;
  • ruff on the touched files;
  • the new engine-provenance tests (test_uk_rules_engine_provenance.py), with the spine, assembler, full-gates and full-build tests that read the provenance;
  • after the merge of main, the same tests again, plus the tests Bind HMRC's pensioner taxpayers by area and re-index the SPI State Pension bands (#1069) #1414 touched (uprating, ITL age targets, measurement period, national calibration, population targets, subset relations, target references), Cite US and UK spec sources by symbol or at a commit, never by a drifting line #1407's spec-source citation guard, and the tests that read the sign-off registers.

The full suite runs in CI.

Merge with a merge commit, not squash or rebase: the releases built from this branch record its commits.

🤖 Generated with Claude Code

juaristi22 and others added 4 commits October 27, 2026 14:38
The excise step (microcosm#1359) writes HMRC's published draught-relief
share of beer and of cider clearances to every household, so
household.beer_draught_share and household.cider_draught_share are constant
by construction, and uk_degenerate_release_surface refuses them on both the
local and the national line. María approved both entries on 2026-10-27
(adjudication microcosm#1359, six-month window, every line), like the
Tax-Free Childcare routed share's.

The register is solve-scoped (solve_scope.py keeps it in the solve
fingerprint), so the release contract's degenerate evidence digest moves
with it, and the battery tests' exclusion clock moves to the newest
receipt's approval date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PolicyEngine/policyengine-uk#2695 keeps every property allowance election
and stops working out every benefit for the whole population on each of its
comparison branches, the cost behind Microcosm UK microsimulations running
several times slower since 2.157.0 (policyengine-uk#2694). Until a release
carries it, the workspace installs policyengine-uk from that pull request's
head commit (ead3444d, the 2.162.10 tag plus its three commits), which the
engine freeze accepts as policyengine-uk 2.162.10; the lock moves to the
release once it is out.

The uprating and bonus-sacrifice pins are re-taken on the new engine with
every value unchanged, concept coverage finds no input added or removed,
the coverage manifest and the H2 spine fixture are regenerated, the
version mentions move, the US worker identity pins the new lock, and the
engine pin test reads the lock's direct reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PolicyEngine/policyengine-uk#2695's head now merges the 2.162.12 release
(b71ce793), so the workspace installs that commit, which the engine freeze
accepts as policyengine-uk 2.162.12. 2.162.11 imputes non-workers' wages
once per participation step with unchanged outputs, and 2.162.12 changes
only CI. The uprating and bonus-sacrifice pins keep every value, concept
coverage finds no input added or removed, and the coverage manifest, the H2
spine fixture, the version mentions and the US worker identity's lock
digest move with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drafted by tools/draft_uk_dense_signoff.py from the first dense attempt of
this pair, uk-local-candidate-f100-s42-20261027T022836Z-d5f72865 at
90ff43f, with its degenerate-surface failure set aside: that failure named
only the two draught-share columns, which the previous commits' register
excludes. Approved on 2026-10-27 by juaristi22 before the rebuild, so that
it can pass without a reuse build: three national target-fit rows and twenty
local target-fit cells added, the entries still earned kept, and one
national and six local entries the attempt no longer earns retired. Every
entry is scoped to the local line and suppresses only its release fence;
the rows stay bound in the solve.

The rebuild at this head solves anew on policyengine-uk 2.162.12 plus #2695.
A row that moves there is re-drafted from the rebuild for a reuse build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.162.14 is the first release on PyPI that carries
PolicyEngine/policyengine-uk#2695: 2.162.13's release run stopped at its
YAML test job's 30-minute limit, so it never published, and
PolicyEngine/policyengine-uk#2697 raised the limit. Its package code outside
the tests is the head commit the previous pin installed (b71ce793), so the
interim git source goes and the lock takes the registry release.

This pair's local-area cut stays the build at db50ae8, which records
policyengine-uk 2.162.12; its national is built at this commit on 2.162.14.
The uprating and bonus-sacrifice pins keep every value, concept coverage
finds no input added or removed, and the coverage manifest, the H2 spine
fixture, the version mentions and the US worker identity's lock digest move
with the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge, not rebase: the local-area and national releases record this
branch's db50ae8 and daaf543 as their git commits, so those stay as
they are.

The only conflict was the H2 spine fixture. #1414 moved the
spi_income_band_donors stage's hash through hmrc_itl_taxpayer_counts.json,
and this branch moved the engine. tools/graph_uk_spine_fixture.py
regenerated it on the merged tree, and --check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 marked this pull request as ready for review October 28, 2026 16:17
@vahid-ahmadi

Copy link
Copy Markdown
Contributor

@juaristi22 review of #1411 at 9a6bcce8f. No musts. Two shoulds and a nit below.

Earlier findings. There are no earlier comments or reviews on this PR. The finding it answers is my #1405 verification run (issuecomment-6443298627, summary issuecomment-6443428518): certify refused on uk_degenerate_release_surface for household.beer_draught_share and household.cider_draught_share. Fixed by d444eb5, as far as the code goes (see "What I checked"). I'll re-run #1405's cut from certify once this is in.

What I checked on the draught-share entries

  • uk/degenerate_reviewed_exclusions.json:15-28: both entries have every schema-2 field: reason, approved_by, adjudication: microcosm#1359, approved_on: 2026-10-27, expires_on: 2027-04-27.
  • They carry no release_lines, so applies_on returns True on every line (weighted_integrity.py:380). The same entries therefore cover the national release-cut scope and the dense battery.
  • They load cleanly through UKReviewedExclusion.__post_init__: canonical ISO dates, and the expiry is after the approval.
  • The gate suppresses an entry from approved_on through expires_on (terminal_gates.py:655-668). The battery takes its clock from the exclusions_evaluated_on artifact (battery_bindings.py:1052). The certifier sets that to today's date (tools/certify_uk_release_cut.py:298). Any certify from 27 Oct 2026 to 27 Apr 2027 is inside the window.
  • The register tests pin both entries (test_uk_terminal_gates.py:895, :920). The degenerate evidence digest moved (contract.py:683), and test_contract.py passes with it.
  • Self-approval: UKReviewedExclusion / coerce_reviewed_exclusions (weighted_integrity.py:270-420) have no rule that the approver must differ from the author. So nothing is breached. For the record, all five entries in this register are approved by juaristi22.

Engine

  • policyengine-uk#2695 merged on 27 Oct (e5b57714). 2.162.14 on PyPI contains it: the compare shows 2.162.14 ahead of the merge commit, and 2.162.13 never published. Outside the tests, b71ce793...2.162.14 changes only CI, CHANGELOG/changelog.d, pyproject.toml and uv.lock. So your "same package code" claim holds. git diff db50ae8dd daaf54302 -- uv.lock moves only the policyengine-uk entry, so the dependency tree is the same too.
  • At the head, the [tool.uv.sources] git pin is gone and the lock takes 2.162.14 from the registry (daaf543). The "move the pin to the release" point is already done.

Findings

  1. should The engine freeze and the build's engine identity can't tell a git install from the release. assert_installed_engine_is_pinned (hmrc_uprating.py:479-530) checks the version string, the import origin and __init__.py against RECORD. It never reads direct_url.json. So the b71ce793 install passed as "2.162.12". _rules_engine_provenance() (spine_build.py:728-733) records only {package, version}, and full_build_cli.py:1242-1244 hashes that into engine_identity. As a result, the local-area release ...dense-20261027T154955Z-6528c865 records policyengine-uk 2.162.12, and its engine identity is the same as a genuine 2.162.12 PyPI install's. The sha can be traced through the build's git_commit (db50ae8, whose lock names b71ce793), and policyengine.py#725 states it in prose and in the recorded limitation. So nothing is hidden. It just isn't in Microcosm's own receipt. Suggested fix, here or as a follow-up issue: add direct_url.json (vcs_info.commit_id) to _rules_engine_provenance(). Or have the freeze refuse a non-registry install unless a flag names it. Not blocking, because the head is back on PyPI.
  2. should Pairing, briefly:
    • The two releases were built at db50ae8 and daaf543. Neither has Bind HMRC's pensioner taxpayers by area and re-index the SPI State Pension bands (#1069) #1414's pensioner taxpayer targets, even though the head now does through the merge of main (the merge base is main's tip, 7bf1ed8e7). The body says so. Whoever reads the pair should not assume the merged head built it.
    • The national release meets the dashboard's 2.162.14 target (uk-autumn-budget-dashboard-2026#262). The local area records 2.162.12 + #2695, as in 1.
    • The freeze pins exactly 2.162.14 (test_engine_core_pins.py:36). Issue UK: data fixes for the Budget 2026 household measures #1418 says the Carer's Allowance taper needs 2.162.17+ (policyengine-uk#2699, merged this morning). So that measure needs a re-pin and a rebuild in a later cut, not this pair.
    • 18 target-fit entries expire on 2026-11-16 (target_fit_reviewed_exclusions.json, local_target_fit_reviewed_exclusions.json). Any cut certified after that date needs them renewed.
  3. nit test_uk_battery_bindings.py:57-69: the comment above CLOCK still says "latest approval 2026-09-23" and "The earliest expiry is 2026-10-15". The new line under it says 2026-10-27. Nothing in the registers expires on 2026-10-15 any more; the earliest expiry is 2026-11-16. Worth trimming the old lines so the next person moving the clock isn't misled.

Verdict: ready. Nothing blocks merging. Please merge with a merge commit, as the body asks: squashing would drop db50ae8 and daaf543, which the two releases cite. Point 1 can be a follow-up issue.

What I ran: a shallow clone at 9a6bcce8f, then uv sync --all-packages --locked (no uk extra), then pytest on test_uk_terminal_gates.py, test_uk_battery_bindings.py, test_engine_core_pins.py, microcosm-data/.../test_contract.py, test_uk_solve_scope.py, test_uk_dense_signoff_draft.py and test_uk_full_solve_scope.py. All passed. CI is green on all ten jobs at this head.

Not checked:

  • the engine tests (uprating and bonus-sacrifice pins, concept mapping, DfE spring term), since they need the uk extra;
  • build_uk_release_input_coverage_manifest.py --check and graph_uk_spine_fixture.py --check;
  • ruff;
  • the 185-line local target-fit sign-off, cell by cell;
  • certify on any frame. The gate's pass on both columns is from reading the code and tests. The 19/19 and dense results are from your builds, not mine.

🤖 Generated with Claude Code

juaristi22 and others added 2 commits October 28, 2026 19:08
A git install of an engine pull request carries the release's version
string. So the spine's rules_engine provenance, and the engine identity the
builds hash from it, could not tell b71ce793 from policyengine-uk 2.162.12
(review of #1411, point 1).

The provenance now adds what PEP 610's direct_url.json records: a git
install's commit, an archive's hash, or a directory, and never a URL. A
release from an index writes no direct_url.json, so its provenance and
identity are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment above CLOCK still named the 2026-09-23 approval and a
2026-10-15 expiry. The latest approval is now 2026-10-27, and the earliest
expiry is 2026-11-16 (review of #1411, point 3).

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

Copy link
Copy Markdown
Collaborator Author

@vahid-ahmadi thanks. All three are answered at 40a1de543:

  1. Engine provenance (should): done here, in 8a3bec1.
    • _rules_engine_provenance() now adds what PEP 610's direct_url.json records:
      • a git install's {"kind": "vcs", "vcs": "git", "commit_id": …};
      • an archive's sha256;
      • a directory's kind.
    • It never records a URL, so no local path reaches a build or release record.
    • A release from an index writes no direct_url.json, so its provenance and the engine identity hashed from it stay byte-identical. Both releases' identities stand.
    • The freeze is unchanged: it still accepts a git install. The record now says which commit it was.
    • The tests are in test_uk_rules_engine_provenance.py:
      • a release keeps its provenance and identity;
      • a git install records its commit and takes its own identity;
      • archive and directory installs record their kind and no URL;
      • a missing engine still records "unavailable".
  2. Pairing (should): in the body's new "Before the next cut".
  3. Clock comment (nit): 40a1de5 trims it to the latest approval (2026-10-27) and the earliest expiry (2026-11-16).

Locally, these passed:

  • the new tests;
  • the spine, release-assembler, full-gates, full-build and battery-binding tests;
  • ruff.

CI is running on the new head. It still merges with a merge commit.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of #1411 at 40a1de543. I checked the head after María's answers (8a3bec1, 40a1de5) to Vahid's review at 9a6bcce8f, then the parts his review left out: the 23 new target-fit entries, the engine provenance code and its tests, and the tooling checks.

Vahid's three items at head

  • 1 (provenance): fixed for new builds. _rules_engine_provenance() now adds PEP 610's direct_url.json record. The local-area release 6528c865 was built before the fix, so its receipt still records 2.162.12 with no commit (see Nice to have 2).
  • 2 (pairing): fixed. The body's "Before the next cut" says it.
  • 3 (clock comment): fixed. test_uk_battery_bindings.py:58-64 now names 2026-10-27 and 2026-11-16, and no register at head has an approval later than 2026-10-27.

Must fix

None.

Should fix

  1. The size of the 23 new target-fit misses is not on record anywhere a reviewer can read.
    • Each new entry says only that the row "breached the target-fit release bound (max_abs_relative_error 0.25)" and cites the private gate report a859f46b…. The register test refuses a % in a reason, and the body gives counts only.
    • Some of these rows feed Budget measures. The three national rows are SPI savings interest by income band (£15k, £20k and £30k bands). #1418 item 11 already finds savings interest at about half of the base HMRC costed (row 62). The local cells include UC households with three or more children (E14001098), and council tax bands in Westminster, Kensington and Chelsea, Tower Hamlets and Na h-Eileanan Siar.
    • All entries are scoped to the local line. So the national release and national dashboard figures are not affected. A dashboard view that reads the local-area release is.
    • Fix: post the drafter's table for the 23 added rows (target, estimate, relative error) in the body or on #1301. Add the 22 kept rows' error on the rebuild 6528c865. Then a reviewer can see whether any row is far past 25%.

Nice to have

  1. _direct_install drops the hash of an archive without sha256 (spine_build.py:765-771). PEP 610 allows any hash algorithm in archive_info.hashes. With only, say, sha512, the record is {"kind": "archive", "sha256": null}, so two different archives share one engine identity. Fix: record the first algorithm present, for example {"kind": "archive", "hash": "sha512=…"}, or refuse. Also add tests for "unrecognised" and an editable dir_info. Neither branch has a test now.
  2. The local-area release keeps a version-only receipt. The body says both releases "keep theirs". For 6528c865 this means it still records policyengine-uk 2.162.12, with the same engine identity as a PyPI 2.162.12 install. The fix only covers builds from 8a3bec1 on. Say so in "Which commit builds what", so a reader does not think the fix covers this release. The commit is traceable only through git_commit db50ae8 and its lock (and policyengine.py#725's limitation).
  3. A second engine identity stays version-only. country_adapter.py:160-167 (build_uk_country_graph) builds its default identity from metadata.version alone, not from _rules_engine_provenance(). Today only tests/engine/uk/test_uk_country_adapter.py calls it. Call _rules_engine_provenance() there too, so the two definitions do not drift.
  4. Merge-order note. Open PRs that touch the same pins will need them re-taken after this merges: #1410 (uv.lock, so APPROVED_UV_LOCK_SHA256, and spine_build.py), and #1405, #1391 and #1342 (contract.py, next to the degenerate evidence digest).

Verified as correct

  • Draught-share entries. Writing the published share to each household is right. policyengine-uk 2.162.14's beer_draught_share and cider_draught_share default to 0, so if the column is not written, all beer and cider is charged at the non-draught rate. Excluding the constant column from the degenerate gate is therefore the correct fix. The fields, the six-month window and the absence of release_lines match the Tax-Free Childcare routed-share entry.
  • Register counts match the diff. National: 3 added, 5 kept, 1 retired (8 entries). Local: 20 added, 17 kept, 6 retired (37 entries). The kept entries are byte-identical to main. Every entry has approved_by, adjudication, approved_on, expires_on and release_lines == ["local"]. 18 entries expire on 2026-11-16, 4 on 2026-11-23 and 23 on 2026-11-24. The test sets in test_uk_terminal_gates.py match the files.
  • Engine pin. All three pyproject.toml extras and the three requires-dist lines in uv.lock read >=2.162.14. The lock takes 2.162.14 from PyPI. No [tool.uv.sources] entry for policyengine-uk is left in any pyproject.toml. The installed 2.162.14 wheel writes no direct_url.json, so the head's provenance is {package, version} as before.
  • Provenance code. No URL, path or credential reaches the record: only vcs, commit_id, a sha256, editable, or a kind. A missing package and a missing file both return the old record.
  • Small code changes. frs_take_up.py, hmrc_uprating.py, uc_capital_coherence.py and policyengine_uk_concepts.py change only the version in comments, a docstring or the concept mapping's engine_version. worker_identity.py takes the new lock's sha256. The new _resolved branch in test_engine_core_pins.py makes a future direct reference fail with its URL, which is a clear message.
  • Battery clock. The latest approval in every UK register is 2026-10-27. The earliest expiry is 2026-11-16. CLOCK = 2026-10-27 is inside the window.
  • Head versus the built pair. The body records it clearly (the "Which commit builds what" and "Before the next cut" sections). The difference is #1414's pensioner taxpayer targets. It is not a problem for the merge, because the body says which commit built each release. A re-run of the dashboard on this pair gets the pair, not the head.

Tests run locally

At 40a1de543, in a fresh worktree, with uv sync --all-packages --locked --extra uk (policyengine-uk 2.162.14 from PyPI, Python 3.14):

  • pytest on test_uk_rules_engine_provenance.py, test_uk_terminal_gates.py, test_uk_battery_bindings.py, test_engine_core_pins.py, microcosm-data/.../test_contract.py and test_uk_frs_stochastic.py: 468 passed, 0 failed, 0 skipped.
  • tools/build_uk_release_input_coverage_manifest.py --check: passed (144 required, 1 reviewed exclusion).
  • tools/graph_uk_spine_fixture.py --check: passed, the fixture regenerates byte-identically (about 6 minutes).
  • ruff check and ruff format --check on spine_build.py and the two touched test files: clean.
  • Not run: any build, certify or test that needs the private FRS data or credentials.

🤖 Generated with Claude Code

Comment thread packages/microcosm-build/src/microcosm/build/uk_runtime/spine_build.py Outdated
juaristi22 and others added 2 commits October 28, 2026 20:17
PEP 610 allows any algorithm in archive_info.hashes. With only sha512,
the record was {"kind": "archive", "sha256": null}, so two archives shared
one engine identity (Pavel's review of #1411, nice to have 1).

The record now keeps every hash the archive lists, plus the legacy hash
field. An archive that records no hash is refused. Tests cover the sha512
case, two archives taking two identities, the refusal, an editable
directory and the unrecognised record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_uk_country_graph hashed its default engine identity from the
version alone, so it would drift from the spine and full builds, which
now hash _rules_engine_provenance() (Pavel's review of #1411, nice to
have 3). It now hashes the same provenance. A release install's identity
is unchanged.

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

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk thanks. At 55d5c9fd7:

Locally, these passed:

  • the provenance and country-adapter tests;
  • the spine, release-assembler, full-gates and full-build tests;
  • ruff.

CI is running on the new head.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk on Should fix 1.

The per-row targets, estimates and errors stay in the private gate reports, since the estimates come from the licensed data. Reviewers on the licensed lane can read them there.

Here they are as counts by error band. They are the same on the first attempt (d5f72865) and on the rebuild (6528c865):

Rows 0.25 to 0.5 0.5 to 1 Above 1
National: 3 added, 5 kept 8 0 0
Local: 20 added 18 1 1
Local: 17 kept 13 4 0

Every row is in the same band on both builds.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of #1411 at 55d5c9fd7 (new since 40a1de543: f6fdc53, 55d5c9f, the body edits and the answer on target fit).

Must fix

None.

Should fix

  1. Target-fit misses: mostly answered. The banded counts are the same on d5f72865 and 6528c865. All 8 national rows, including the three SPI savings interest bands, are between 0.25 and 0.5. One question is still open: the local line has 1 added row above 1 and 5 rows (1 added, 4 kept) between 0.5 and 1. The row names are public, so please name these 6 rows. Then a reader can see whether a Budget-relevant cell, such as UC households with three or more children, is among them. This can go in the body; the values can stay private.

Nice to have

None new.

Verified as correct

  • Nice to have 1 (archive hash): fixed in f6fdc53. _direct_install keeps every hash in archive_info.hashes, adds the legacy hash field with setdefault, sorts the keys, and raises a clear ValueError when no hash is recorded. Two archives with different hashes now get different identities. No URL reaches the record.
  • Nice to have 2 (old receipt): fixed in the body. "Which commit builds what" now says that the local-area release was built before 8a3bec1 and keeps a version-only identity.
  • Nice to have 3 (second identity): fixed in 55d5c9f. build_uk_country_graph now hashes _rules_engine_provenance(). The import inside the function avoids a cycle. A release install's identity is unchanged.
  • Nice to have 4 (merge order): fixed in the body. "Before the next cut" lists #1410, #1405, #1391 and #1342.

Tests run locally

At 55d5c9fd7, uv sync --all-packages --locked --extra uk:

  • pytest on test_uk_rules_engine_provenance.py and test_uk_country_adapter.py: 12 passed.
  • ruff check on spine_build.py, country_adapter.py and the provenance test: clean.

🤖 Generated with Claude Code

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of #1411 at 55d5c9fd7 (no new commits; the body now has "Local rows past 0.5").

Must fix

None.

Should fix

None. Should fix 1 is answered. The body names the six local rows past 0.5. Five are council tax band or age-structure cells, and one council tax cell in S12000013 (Na h-Eileanan Siar) is above 1. The UC three-or-more-children cell and the three SPI savings interest bands are between 0.25 and 0.5 on both builds.

Nice to have

None open.

Verified as correct

  • The six rows named in the body are all in the local register with release_lines local only, so the national release is not affected.

I have no open findings on this PR. Note that 18 of the target-fit sign-offs expire on 16 November, as the body says.

🤖 Generated with Claude Code

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 55d5c9fd7. All findings from my reviews and Vahid's are closed. Note that 18 target-fit sign-offs expire on 16 November, so a cut certified after that date needs them renewed.

🤖 Generated with Claude Code

@juaristi22
juaristi22 merged commit fa1bdb1 into main Oct 28, 2026
10 checks passed
juaristi22 added a commit that referenced this pull request Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants