Fix #792: derive band edges from the compiled register, never the pruned roster - #803
Fix #792: derive band edges from the compiled register, never the pruned roster#803juaristi22 wants to merge 3 commits into
Conversation
7ec0c64 to
baef8ac
Compare
baef8ac to
4429487
Compare
|
Automated review pass (Claude Code, high effort, diff only — no execution). Four findings. The first two matter most: between them, the default path reintroduces #792 and the refusal degrades into a silent drop. 1.
|
Banded measures took each band's upper edge from the lower edges of sibling specs in the live registry, so excluding a band silently widened its lower neighbour to the next surviving edge (or to infinity at the top) — the #792 movers: 12 of 361 shared measures shifted by up to +858% when the exclusion register grew 5->47, each by exactly the mass of the absorbed excluded siblings. materialize_target_bindings and resolve_target_measures now accept a band_edge_registry (default: the materialized registry, bit-identical for existing callers), and _band_bounds refuses a spec whose own lower edge is absent from the supplied edge set rather than guessing. Regression fence: survivors must be bit-identical under sibling exclusion, for numeric edges and published range labels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seam prunes measure exclusions before materialization, so the stage only ever saw the pruned registry. run_uk_calibration now takes the compiled pre-exclusion register as band_edge_registry and refuses, before any artifact is written, a non-empty exclusion receipt without it — and a register whose extra names do not exactly reconstitute the receipt (the applier already fails exclusions that match zero specs, so pruned + receipt == compiled is exact). The registry content hash is recorded as run_config.band_edge_register_sha256, flowing into the build record and identity digest. The seam tool passes compilation.registry; census counts are unchanged — only survivors' measured values return to published band widths. The stale prose in test_uk_measure_simulation that read the widening artifact as the resolution mechanism is corrected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…verage fence Findings 1 and 3 (vahid-ahmadi): band_edge_registry is now REQUIRED at UKNationalCalibrationStage and run_uk_calibration - never defaulted. The stage cannot tell a pruned registry from a full one, so the old fallback to self.registry restated #792 for any direct caller holding a pruned roster, and gating the run-level check on receipt truthiness let an empty-but-present receipt skip reconciliation entirely. The reconciliation now always runs: an empty receipt is a claim that nothing was pruned, so the rosters must be name-identical. Receipt keys are spec names by the applier's construction (it raises on zero-match exclusions and builds the receipt from matched spec names), documented at the check (finding 4's key-space concern). Finding 2: the coverage fence in _band_bounds now raises BandEdgeCoverageError, a RuntimeError the per-spec skip catch does not swallow - a register that cannot bound a spec is a wrong-register problem for the whole run, and the target must never quietly drop out of the solve. The refusal test asserts propagation instead of a skip, and new tests pin the TypeError on omission and the empty-receipt reconciliation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4429487 to
2a445b8
Compare
|
All four dispositioned in 2a445b8 (the branch is also now rebased onto main's head, 1225a27 — clean, zero file overlap with #755). 1 — fixed as proposed (required, no default). 2 — fixed (refusal, not skip). The coverage fence now raises 3 — fixed, subsumed by 1. With the parameter required at 4 — documented; the key spaces are aligned by construction. Receipt keys are spec names: Verification: ruff, |
Closes #792 (code side). Based on main's head (#793 merged as 58a09bd; rebased onto it, trees byte-identical to the verified state).
The defect — deterministic, not RNG
Banded measures took each band's upper edge from the lower edges of the sibling specs surviving in the pruned registry (
_band_edges_by_groupiteratesregistry.specs; the seam applies the measure-exclusion register before materialization). Excluding a band therefore silently widened its lower neighbour to the next surviving edge — or to infinity at the top.This is the whole of the 5→47 register movement: every one of the 12 movers' 361-roster initial estimates equals its 403-roster value plus the 403-roster values of exactly the excluded sibling bands its widened band absorbed (max residual 1.1e-5 on values up to 2.6e10). Replaying the edge logic predicts 21 widened bands; the 12 with model mass in the absorbed region moved, the other 9 are bit-stable. The issue's RNG hypothesis is refuted three ways: formula-time randomness is statically banned in policyengine-core; every UK draw is minted spine-side, identity-keyed, and frozen into the H5; and the operator's own
rng_controlreceipts show 0/403 rows differing across code pins. The 4 blockeduk_target_fitcells attributed to this class are artifacts — those bands pass (or nearly) at published widths.The fix
target_materialization.py):materialize_target_bindingsandresolve_target_measuresaccept a keyword-onlyband_edge_registry(defaultNone= derive from the materialized registry, bit-identical for every existing caller). Band edges come from it; the materialization loop stays on the pruned set._band_boundsnow refuses a spec whose own lower edge is absent from the supplied edge set, rather than guessing — the same posture the function already took for unreadable edges.materialize_uk_ledger_targets→UKNationalCalibrationStage→run_uk_calibration; the seam tool passescompilation.registry.run_uk_calibrationrefuses, before any artifact is written, (a) a non-empty exclusion receipt without a band-edge register, and (b) a band-edge register whose extra names do not exactly reconstitute the receipt (the applier already fails exclusions matching zero specs, sopruned + receipt == compiledis exact). Provenance:run_config.band_edge_register_sha256(the registry content hash) flows into the build record and identity digest.band_period_factor), plus refusal and threading tests at every seam layer. The middle-band prune flips[0,20)to[0,40)on pre-fix semantics, so the fence fails on the old code by construction.Deliberately untouched:
certify_uk_release_cut.py's pruned reference registry (aname@periodroster-parity instrument — it never materializes values, which is exactly why its parity arm could not catch this drift), and the scoring path (prepare_uk_target_frame— the frozen scoring register is pinned to the compiled registry before the prune, so its self-derived edges are already the published ones). Register census counts are invariant: compiled/excluded/calibrated do not change, only survivors' measured values return to published band widths.Verification
Ruff,
ci_test_groups.py --verify, the six touched test files, and the fullshared-specandukCI group file lists all pass locally under the locked engine env (run while the PR was stacked on the pre-merge #793 branch; now that the base is main, PR CI runs the same lanes). The data claim — the 12 movers reverting to 403-roster values and the 4 artifact cells reading at published widths — is a licensed seam-run fact, verified on the next seam run outside PR CI, per the PR-CI/certification boundary.🤖 Generated with Claude Code