Enforce the UK local binding fences at execution time (#760) - #801
Conversation
The rowwise doctrine solve now derives bound families structurally from the target surface (census family classifier x area_type), refuses a declared-vs-derived mismatch, and requires an in-force adjudication in the committed schema-2 register local_binding_adjudications.json for every fence of every bound family before any solve runs. The register reuses the reviewed-exclusion machinery verbatim (correct-or-renew windows); the dry-run plan and candidate manifest echo the adjudications the run stood on; the first entry accepts census disclosure-control noise and names constituency the winning grain for the rowwise arm. The other 6 fences stay unadjudicated, so their families remain unbindable until reviewed. Doctrine constants and their pinned test are untouched. Closes #760. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
María's review ruling: the census disclosure-control adjudication renews on a 3-month cadence (2026-08-27..2026-11-27), not 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3b3858c to
602afce
Compare
|
Automated review pass (Claude Code, high effort, diff only — no execution). The fence fires, and I checked that against the branch rather than the diff alone, since a fence that cannot fire is the failure mode this PR exists to prevent: the census's Three findings, none of which undermine that. 1.
|
…th, honest alias Both expiring adjudication registers — the local binding register and the national calibration measure exclusions — now warn when an in-force entry is within one week of expiry, so renewal is signalled before the correct-or-renew failure lands (review finding 1, María's ruling). The candidate runner computes the binding receipt only on the dry-run path; the real path's single authority is the check inside the doctrine solve, so the census and register are no longer parsed twice per build (finding 2). The public census classifier is a thin wrapper instead of an alias that mutated the private function's docstring (finding 3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All three findings addressed in 59e0168 (María's ruling on finding 1: warn one week out, on both expiring registers).
🤖 Generated with Claude Code |
…nion (second application) Both merged UK lanes re-pinned the UK spec digest on main; the union with this train computes a fresh UK value (71054f53), keeps BE at the train's 7062e38f, and leaves US unchanged (835b4d61) — all verified against the live loader on the merged tree. Loader, gate-battery-pin, UK content-identity, and country-bundle suites green on the union. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #760 (#495 increment 4, workstream G of #665).
The 7
review_required_before_bindingfences inuk_local_target_census.jsonwere declared data with no runtime enforcement — a fenced family could be bound by anyone who did not read the census. This PR adds the census's first runtime reader and makes the fence structural, reusing the existing reviewed-record machinery end to end (no new module, no new loader, no new test file).What lands
require_adjudicated_uk_local_bindinginlocal_rowwise.py, wired intosolve_uk_rowwise_weights_under_doctrineimmediately after the uniform-surface refusal — before any solve runs. It derives bound families structurally from the target surface (census family classifier × area_type), refuses a declared-vs-derived mismatch, and requires an in-force adjudication for every fence of every bound family. The solve signature is unchanged (no knobs).local_binding_adjudications.json, a plain schema-2 reviewed register loaded by the existingload_uk_reviewed_exclusion_register— same fields, validation, and correct-or-renew window semantics as the input-mass/QRF-tail/degenerate registers. Keys are census fence ids. Deliberately a separate per-concern file, not entries incalibration_measure_exclusions.json: that register removes measures and fails on any entry matching zero specs, so a fence-id entry there would red every national calibration run.targets_bound, and both the--dry-runplan androwwise_candidate_manifest.jsoncarry abinding_adjudicationsreceipt (register resource, evaluated_on, stood_on per family, dormant entries).census_disclosure_control_noise— accepts the stated basis (published disclosure-controlled counts as target values, never exact controls; measured cross-grain deltas E&W +105 / Scotland −554 / NI +3 accepted) and names constituency the winning grain for the rowwise arm. Window 2026-08-27 → 2027-02-27. The other 6 fences stay unadjudicated, sohmrc_income_by_area,equivalised_income,uc_households, andage_structureremain unbindable until reviewed — that is the point.status_definitionspromise text ("enforcement lands with the solve-doctrine increment") is retired for a description of the real enforcement; census regenerated,--checkgreen.Acceptance
uc_householdsagainst the committed census+register and fail naminguc_unit_vs_household_grain; a runner-level test proves the build fails before the solve and still spools a failed Logbook row.local_doctrine.pyand its pinned test are byte-identical.ci_test_groups.py --verifyok, ruff clean, no US-lane tests touched or needed.🤖 Generated with Claude Code