From a8aec5cf0501484f5c47d396f2eb6fc7302f2109 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 10:18:35 -0400 Subject: [PATCH 01/12] =?UTF-8?q?U2=20=C2=A720=20step=204:=20record=20the?= =?UTF-8?q?=20completed=20d1724=20pass=20(value-free)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §20 step 4 now records both step-4 passes in value-free terms: the d1649 pass stopped on 2026-10-19 at 640adccf without output (§16d), and the d1724 pass ran once on 2026-10-21 at 1e739d32 after master's Tests run 39502262845 passed there. The d1597 U1 pin check passed, both scripts completed with no refusal, failure or mapping failure, every income and WEALTH1 identity reconciles in all six support waves, and rows U0 and U1 carry §16d's disclosure keys. The record cites the outputs' MANIFEST.md by SHA-256 (acf5d8a4...e6d) and states no count, value or other statistic from either pass. §19 gains the matching changelog entry, the exposure record gains this lane's reading, and u1_identity.json repins the specification's hash. No line before §16a, no registry and no code changes. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 8 +++++++- docs/design/u2_m2_exposure.md | 13 +++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index d09eed9b2..bc3a92f9f 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-draft-4", - "sha256": "4ad771e296791ce82f5b5cb0cc30c64460f209467fd60504a6cae3bd7581bcb0", + "sha256": "b698815f20d193f70d1dc9751f1c4f3fc69b9ee5425e33234ec69ec744f9a126", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index b3944cab4..f14fd9402 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -1933,6 +1933,12 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - Changed no line before §16a. The specification pin in `data/external/track_u2/u1_identity.json` moves to the new hash; its `version` stays `u2-draft-4`. The d1090 record test now cuts §16c's record at §16d, and a new test pins §16d line by line. - No count, statistic or outcome was computed, no model or estimator was run on real data, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (d1724 section). +**`u2-draft-4`, §20 step 4 recorded (2026-10-21):** + +- §20 step 4 records both passes in value-free terms. The first, under d1649, ran once on 2026-10-19 at `640adccf` and stopped in the birth law without writing any output (§16d). The second, under d1724, ran once on 2026-10-21 at `1e739d32`, after master's Tests run 39502262845 passed there: the d1597 U1 pin check passed, both scripts completed with no refusal, failure or mapping failure, every income and WEALTH1 identity reconciles in all six support waves, and both rows carry §16d's disclosure keys. The record cites the outputs' `MANIFEST.md` by its SHA-256 and states no count, value or other statistic from them. +- The record was written from the run-2 `MANIFEST.md`, which the restricted list allows to be read and cited, and from the coordinating session's value-free brief. No other file in either pass's folder was opened. +- No count, statistic or outcome was computed, no model or estimator was run, no PSID file was read, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (§20 steps 4–6 section). + ## 20. Ratification and execution record **Pending.** @@ -1940,7 +1946,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t 1. Resolve the proposed decisions and source-verification blockers. **Decisions: done (Max, d514, 2026-09-28, adopting every §16 default).** Source-verification blockers remain for steps 2–5. **`u2-draft-4` amendments 1–6 (§16a): ruled by Max on 2026-09-30 (d637; §16b).** B3 is resolved by option 1. B1 and B2 were held until PSID answered; **Max ruled on them on 2026-10-10 (d1090; §16c)**: B1 by option 2 and B2 by option 1, each an exclusion under a named disposition. Max confirmed the reading of amendment 1's parts 1a–1c in §16b on 2026-10-12 (d745), so this step is done. 2. Complete implementation, manifests and parameter capture. **Done (2026-10-16).** Implementation: milestone 2 (#487) and its three review rounds, the last landing as #525 (`f7a84d0b`); and #544, which records with the frame digest the pandas version and string inference setting it was taken under and refuses a frozen load or registered run under any other (`test_u2_loader.py::test_any_other_frame_environment_refuses_the_registered_check`, `::test_a_frozen_load_refuses_another_frame_environment`). Manifests: the documentary source records, 1,766 registry entries and 26 SSI and Census records, are all RESOLVED, none REFUSED or TO VERIFY (`test_adjudication_applied.py::test_status_counts_after_independent_adjudication`), and the loader's source preflight passes (`test_u2_mappings.py::test_source_preflight_passes_at_the_committed_registries`; #524, #540). A RESOLVED record is a documentary finding; reconciliation on real records is step 4's. Parameter capture: the U2 SSI capture, the Track M threshold capture and U1's inherited life tables, each pinned by hash in the code (§17 item 5; `test_u2_parameters.py::test_threshold_capture_is_the_pinned_track_m_file`; `parameters._LIFE_TABLE_PINS`). The final parameter block is step 5's. 3. Complete independent mapping review, invented tests, historical-isolation checks and exact U1 differential evidence. **Done (2026-10-16).** Independent mapping review: the independent adjudication of milestone 1's registries (ERRORS FOUND); milestone 1b applied its corrections in #486, whose independent review requested changes on 11 findings and whose blind verifier then returned U2-M1B: VERIFIED (`test_adjudication_applied.py::test_dispositions_match_the_adjudication`; §17). Every later registry change had an independent review before it merged: the d1090 rounds (§19), ending in APPROVE at round 4 (`e7a558f`) and at #524's merged head (`742590b`; #524's merge audit); the d637 and d745 commit reviews, the last a delta review, APPROVE (§19); and #540's gating review, an Opus 5.5 review with a shell, APPROVE (#540's merge audit). Invented tests and historical isolation: the milestone-2 report's §13 and §14 audit (#487, #525), closed by three review rounds and a fourth-pass mutation run in which a test kills every mutant; §13's optional structure-only U1 load needs Max's approval and is not run. Exact U1 differential evidence: §17 item 7. -4. Complete the authorized pre-registration structural, reconciliation and F17 component pass; resolve mapping failures. The pass reads real PSID records, so it needs Max's authorization, recorded as a chief-of-staff decision. `scripts/track_u2_structure.py` and `scripts/track_u2_component_diagnostics.py` refuse to run without `--authorized-by `, exiting non-zero before any PSID path is resolved or opened, and the loader refuses an unfrozen load without the same record. The flag records the decision id, the git head and the UTC time in the pass's output; it only records the authorization and does not grant it. **First pass (d1649): stopped.** It ran once, on 2026-10-19 at `640adccf`, and stopped in the birth law without writing any output; the component diagnostics were not run (§16d). **Max's ruling of 2026-10-19 (d1724; §16d)** authorizes one more pass, structure and then component diagnostics, under d1649's scope with `--authorized-by d1724`, once the §16d change merges and master's Tests pass at the run head. +4. Complete the authorized pre-registration structural, reconciliation and F17 component pass; resolve mapping failures. The pass reads real PSID records, so it needs Max's authorization, recorded as a chief-of-staff decision. `scripts/track_u2_structure.py` and `scripts/track_u2_component_diagnostics.py` refuse to run without `--authorized-by `, exiting non-zero before any PSID path is resolved or opened, and the loader refuses an unfrozen load without the same record. The flag records the decision id, the git head and the UTC time in the pass's output; it only records the authorization and does not grant it. **First pass (d1649): stopped.** It ran once, on 2026-10-19 at `640adccf`, and stopped in the birth law without writing any output; the component diagnostics were not run (§16d). **Max's ruling of 2026-10-19 (d1724; §16d)** authorizes one more pass, structure and then component diagnostics, under d1649's scope with `--authorized-by d1724`, once the §16d change merges and master's Tests pass at the run head. **Second pass (d1724): completed.** It ran once, on 2026-10-21 at `1e739d32` (`1e739d328b73c99d259a72952b3452546721bfb8`, the merge of #552), after master's Tests run 39502262845 passed at that head. The d1597 structure-only U1 load (§13) ran first and passed: the staged inputs hash to U1's registered input-frame pin. `scripts/track_u2_structure.py` and then `scripts/track_u2_component_diagnostics.py`, each with `--authorized-by d1724`, exited 0, with no refusal, failure or mapping failure. Every family income identity and every WEALTH1 identity reconciles in all six support waves, so no reconciliation failure category is recorded, and the structural counts of rows U0 and U1 both carry §16d's disclosure keys (`n_seed_birth_year_counted_out`, with its four reasons, and `n_seed_birth_year_reseeded`). The outputs stay outside the repository, in `EV/track-u2-step4-pass-20261021-run2/`, restricted except its `MANIFEST.md` (SHA-256 `acf5d8a4252e88b3bbe51581943f1e3b25ba73f205a8240e8e45b9ca538b5e6d`); the first pass's record is `EV/track-u2-step4-pass-20261019/MANIFEST.md` (`1e91407b…a366fa`). This record states no count, value or other statistic from either pass. Under d1724 no further real-data run of either script is authorized. This step is done. 5. Materialize the final parameter block and U2 rulings record; require exact code/specification agreement. 6. Ratify the specification by merge. 7. Obtain and independently check the fresh forecast. diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index ce06615e2..e5e9086c4 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -373,3 +373,16 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses **2026-10-20, the widened rule.** The coordinating session asked that the rule also cover seeds whose age code the law refuses, and later reported that Max approved that widening, re-seeding of code 0 included. The lane read the coordinator's messages, d1724's two notes in the decision ledger (to cite the approval and its correction), the loader's documented age domain and its value check (`loader.py`), and the code and tests it changed. It read nothing else outside the repository, and its runs were as above: an empty PSID directory, invented inputs only, and the mutation check, all listed in the PR. **Review lane.** Subfleet `20261020-030251-d1724-review-552b` reviewed head `8a6a8192` and returned APPROVE. No Codex lane could admit the job, so Subfleet served it on Claude Opus 5.5 (lane `claude-7`), in its own standalone clone with its own virtual environment. The lane reports that its empty PSID directory stayed empty and that it opened nothing under the evidence directory, `REFS` or `PSID`. It loaded master's `cohort.py` from Git and ran invented inputs only. Its scratch scripts were in `/tmp/review552`. Its findings (two test gaps, one record-accuracy point, nits) were addressed in the next commit, which declined one (the header's version line, pinned as d1090's `header_version` clause). The delta review (Subfleet `20261020-045107-d1724-review-552-delta`, GPT-6.1 Sol) approved that commit. Records: `EV/rulings-records-20261019-d1724/review-552-out.md`. + +## §20 steps 4–6 lane (2026-10-21) + +A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator session (`local_6085d52a-…`), recorded §20 step 4, materialized the final parameter block and U2's rulings record (§20 step 5), and takes the ratifying merge through review (§20 step 6), in a worktree of `origin/master` at `1e739d32`. It read no PSID record, computed no PSID statistic and opened no comparator. It may later be asked to forecast U2 or check a U2 forecast, so everything it read is listed here. + +**Read:** + +- `EV/RESTRICTED-FILES.md`, in full and first, through a saved copy of the tool output in this session's own transcript folder. No other session's file in that project folder was opened. The harness also auto-loaded that project's memory index (`MEMORY.md`, one-line pointers); no memory file was opened. +- `EV/track-u2-step4-pass-20261021-run2/MANIFEST.md`, which the restricted list allows to be read and cited, and its SHA-256, which matched `acf5d8a4…e6d`. No other file in that folder and nothing in `EV/track-u2-step4-pass-20261019/` was opened or listed. +- The chief-of-staff decision ledger's entries for d514, d637, d745, d1090, d1242, d1597, d1649 and d1724 (decision, ruling, ruling time and notes), selected by id, and the names in its folder. +- The repository at `1e739d32`: `CLAUDE.md`; the specification in full; the U1 specification's header, §15 block and §20; the U2 package, scripts and tests named in the PR; `data/external/track_u2/u1_identity.json` and the registries' entry ids and fields; the U2 SSI capture's provenance fields and its source manifest's records; `tests/README-tiers.md`; Track M's `specification.py` header; `estimates/cola_age_profile.specification_unratified_fields`. Also `~/PolicyEngine/CLAUDE.md`. + +**Not opened:** any other evidence-folder path; any comparator folder, seal or Boomers Report copy; `runs/track_b_b2_*`, `docs/design/track_b_attempt_ledger.md` and `paper/paper.qmd`; other sessions' transcripts or scratchpads; `~/.subfleet/` other than this session's own review jobs; any PSID file or codebook. From 2cefe6e2e46dd9b7ea17885c3947bbd58f964b5a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 10:42:29 -0400 Subject: [PATCH 02/12] =?UTF-8?q?U2=20=C2=A720=20step=205:=20materialize?= =?UTF-8?q?=20the=20final=20parameter=20block=20and=20rulings=20record?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §15 now holds the final block, u2-ratified-1 (status ratified_frozen): - the three TO_VERIFY keys are resolved: the SSI capture's SHA-256 is pinned (a58d55c1...3762, as parameters.SSI_SHA256), the employer-DC reader names the U2 adapter (uniform_cut_track_u2/sources.py), and later_wave_mapping_status records the RESOLVED pension registry; - previous_routes is amendment 5's ratified fragment, laid out so the registry-cited lines 1173 and 1185 keep their draft-3 number and text; - blocked_by is empty, with each blocker's resolution in §20 step 5; - decisions holds U2's rulings record: d514's eleven entries unchanged, plus d637 (1a-1c confirmed by d745; 2, 3, 4, 5a, 5b, 6; the PSID questions), d1090 (B1, B2, 5c), d1242, d1597, d1649 and d1724. Code: rows.U2_RULINGS builds its registry-applied entries from the registry module's ratified scopes; rows.expected_specification_block() states the whole block from the code; check_block_against_code and check_rulings_against_code refuse any difference, and the registered run's preflight and the dry run apply both. Tests: test_u2_specification_block.py holds the committed block equal to the code and refuses a change to every leaf, every added or dropped key and every JSON type change; a Hypothesis property checks that the check passes exactly the canonical block; the rulings record is held to every decision and the registries; the new TO VERIFY appendix is held complete and its cited tests to existence. Eleven mutants of the checks and the appendix are each killed. The employer-DC, identity, registered-script and specification-revision tests now expect the materialized block; §16 is checked byte-equal to draft 3 at its new position. No registry changes, so the step-4 evidence's registries_sha256 still equals sources.REGISTRY_SHA256. No PSID file was read; tests ran with an empty POPULACE_DYNAMICS_PSID_DIR. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 6 +- docs/design/boomers2004_1946_55_comparison.md | 396 ++++++++- docs/design/u2_m2_exposure.md | 4 + scripts/run_track_u2_registered.py | 21 +- scripts/track_u2_dry_run.py | 17 +- .../uniform_cut_track_u2/__init__.py | 7 +- .../uniform_cut_track_u2/cohort.py | 6 +- .../uniform_cut_track_u2/rows.py | 819 +++++++++++++++++- .../uniform_cut_track_u2/sources.py | 11 +- tests/track_u2/test_adjudication_applied.py | 131 ++- tests/track_u2/test_u2_employer_dc.py | 22 +- tests/track_u2/test_u2_identity.py | 24 +- tests/track_u2/test_u2_scripts.py | 92 +- tests/track_u2/test_u2_specification_block.py | 515 +++++++++++ 14 files changed, 1921 insertions(+), 150 deletions(-) create mode 100644 tests/track_u2/test_u2_specification_block.py diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index bc3a92f9f..ac1d8ada3 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -55,12 +55,12 @@ }, "u2_specification": { "path": "docs/design/boomers2004_1946_55_comparison.md", - "note": "Moved out of the unchanged-byte list by u2-m1b: Part C writes the proposed revision u2-draft-4 into the specification. The draft-3 hash is the milestone-1 pin; the current hash pins u2-draft-4 with Max's 2026-09-30 rulings recorded in §16b (d637) and his 2026-10-10 ruling on blockers B1 and B2 recorded in §16c (d1090), with his 2026-10-19 ruling on the birth law's support recorded in §16d (d1724); every line a registry cites keeps its draft-3 number and text.", + "note": "Moved out of the unchanged-byte list by u2-m1b: Part C writes the proposed revision u2-draft-4 into the specification. The draft-3 hash is the milestone-1 pin; the current hash pins u2-draft-4 with Max's 2026-09-30 rulings recorded in §16b (d637) and his 2026-10-10 ruling on blockers B1 and B2 recorded in §16c (d1090), with his 2026-10-19 ruling on the birth law's support recorded in §16d (d1724). Section 20 step 5 then materialized the final section 15 block and U2's rulings record, and the version became u2-ratified-1; every line a registry cites keeps its draft-3 number and text.", "draft_3_sha256": "7badf89e896ece28abcea1cb2db9bd55d468346b405ed06eb357aaf5c7b51df2", "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", - "version": "u2-draft-4", - "sha256": "b698815f20d193f70d1dc9751f1c4f3fc69b9ee5425e33234ec69ec744f9a126", + "version": "u2-ratified-1", + "sha256": "b000c180e632997e40c440ade5df2d3c7d1a93dd63b00d3185ba598d0125338a", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index f14fd9402..218e67f10 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -1,7 +1,7 @@ # Boomers 2004, 1946–55: specification for held-out target U2 -- **Version:** `u2-draft-4` (milestone 1b, 2026-09-28; revised 2026-09-29 after the independent review; Max's rulings recorded 2026-09-30, 2026-10-10 and 2026-10-12): proposed amendments 1–6 in §16a, ruled by Max on 2026-09-30 (d637) as recorded in §16b, with blockers B1 and B2 ruled on 2026-10-10 (d1090) as recorded in §16c, and amendment 1's parts 1a–1c confirmed on 2026-10-12 (d745) as recorded in §16b. Previous version: `u2-draft-3` (round 2). -- **Status:** decisions ruled for `u2-draft-3`. On 2026-09-28 Max approved the §16 defaults and ratification of that draft (d514, in chat). Per §20, the version becomes `u2-ratified-1` by the merge that completes §20 steps 2–5 and materializes the final parameter block; that merge needs no further ruling unless it changes this document's substance. `u2-draft-4` adds §16a, whose proposed amendments change substance, so each needed Max's ruling first. He ruled on 2026-09-30 (d637; §16b): B3 is resolved by option 1, amendments 2–5 are ratified as written, and blockers B1 and B2 were held until PSID answered. PSID answered on 2026-10-07, and on 2026-10-10 he ruled on B1 and B2 (d1090; §16c): B1 takes option 2 and B2 option 1, each an exclusion under a named disposition, ratified as amendments to draft-3 line 160 (B1) and to draft-3 lines 152, 154 and 158 for 2019–2023 (B2). No blocker hold remains. Amendment 1's parts 1a–1c, which the d637 ruling's list does not name, were read as ratified pending Max's confirmation, and on 2026-10-12 he confirmed that d637 ratified them as written (d745; §16b). Not registered or authorized for execution. +- **Version:** `u2-ratified-1` (§20 step 5, 2026-10-21): §15 holds the final parameter block and U2's rulings record, and the merge of this revision ratifies the specification under d514 (§20 step 6). Previous version: `u2-draft-4` (milestone 1b, 2026-09-28; revised 2026-09-29 after the independent review; Max's rulings recorded 2026-09-30, 2026-10-10, 2026-10-12 and 2026-10-19): proposed amendments 1–6 in §16a, ruled by Max on 2026-09-30 (d637) as recorded in §16b, with blockers B1 and B2 ruled on 2026-10-10 (d1090) as recorded in §16c, amendment 1's parts 1a–1c confirmed on 2026-10-12 (d745) as recorded in §16b, and the birth law's support ruled on 2026-10-19 (d1724) as recorded in §16d. Before it, `u2-draft-3` (round 2). +- **Status:** ratified and frozen by the merge of this revision (§20 step 6). Decisions were ruled for `u2-draft-3`: on 2026-09-28 Max approved the §16 defaults and ratification of that draft (d514, in chat). Per §20, the version becomes `u2-ratified-1` by the merge that completes §20 steps 2–5 and materializes the final parameter block; that merge needs no further ruling unless it changes this document's substance, and §20 step 5 records why this revision changes none. `u2-draft-4` adds §16a, whose proposed amendments change substance, so each needed Max's ruling first. He ruled on 2026-09-30 (d637; §16b): B3 is resolved by option 1, amendments 2–5 are ratified as written, and blockers B1 and B2 were held until PSID answered. PSID answered on 2026-10-07, and on 2026-10-10 he ruled on B1 and B2 (d1090; §16c): B1 takes option 2 and B2 option 1, each an exclusion under a named disposition, ratified as amendments to draft-3 line 160 (B1) and to draft-3 lines 152, 154 and 158 for 2019–2023 (B2). No blocker hold remains. Amendment 1's parts 1a–1c, which the d637 ruling's list does not name, were read as ratified pending Max's confirmation, and on 2026-10-12 he confirmed that d637 ratified them as written (d745; §16b). On 2026-10-19 he ruled on the birth law's support (d1724; §16d), and the §20 step-4 pass completed under that ruling on 2026-10-21. Not registered or authorized for execution. - **Intended file:** `docs/design/boomers2004_1946_55_comparison.md`. - **Specification identity:** `boomers2004_1946_55_uniform_cut`. - **Target identity:** `U2`. Row identifiers are local to this specification: **row U2** means “no SSI response.” @@ -1036,16 +1036,16 @@ Changed source bytes, changed frame digests, duplicate identifiers, failed joins ## 15. Machine-readable parameter block -This is the complete draft parameter contract, with explicit unresolved pins. It uses U1’s key names where the concepts are inherited. Additional keys describe U2-specific identity and amendments. +This is the final parameter contract, materialized under §20 step 5 (2026-10-21): every pin is resolved, the ratified amendments are applied and U2's rulings record is written under `decisions`. It uses U1’s key names where the concepts are inherited. Additional keys describe U2-specific identity and amendments. -Ratification requires code/block equality, completed manifests and replacement of every required `TO_VERIFY`. This block does not authorize execution. +`rows.check_block_against_code` holds this block equal to the code in every key and value, and the registered run's preflight refuses any other block (§20 step 5). The block holds no `TO_VERIFY` value and no blocker. It does not by itself authorize execution: a registered run also needs the issue #42 registration and its binding, and the committed pre-registration evidence (§§14 and 20). ```json { "specification": "boomers2004_1946_55_uniform_cut", "target_id": "U2", - "version": "u2-draft-4", - "status": "draft", + "version": "u2-ratified-1", + "status": "ratified_frozen", "inherits": { "file": "docs/design/boomers2004_uniform_cut_comparison.md", "version": "u1-ratified-1", @@ -1142,7 +1142,7 @@ Ratification requires code/block equality, completed manifests and replacement o "unresolved_marital_status": "relationship_code", "annuitant_age_source": "derived_birth_year", "institution_income_rule": "excluded", - "relationship_rules": "section_3_u2_wave_specific_role_amendment", + "relationship_rules": "section_3_role_amendment_with_section_16a_amendment_1", "relationship_codes_tested": [10, 20, 22, 88, 90, 92], "design": { "stratum": "ER31996", @@ -1165,24 +1165,32 @@ Ratification requires code/block equality, completed manifests and replacement o "financial_assets": "wealth1", "employer_dc": { "row": "U7", - "reader": "TO_VERIFY_u2_employer_dc_adapter", + "reader": "uniform_cut_track_u2/sources.py", "persons": ["head", "wife"], "role_meaning": "reference_person_and_designated_spouse_income_slot", "current_job": "account_amount_when_plan_type_has_an_account", "previous_plans": [1, 2], "previous_routes": { "both_items": ["both"], - "account_items": ["account", "formula", "dk"] + "account_items_by_wave": { + "2013": ["account", "formula", "dk"], + "2015": ["account"], + "2017": ["account"], + "2019": ["account"], + "2021": ["account"], + "2023": ["account"] + }, + "refused_branches": {"2015": "P62ACKPT: P46=1 AND P52=1 AND P62AMT=DK/RF"} }, + "unreported_amount": "zero_counted", "route_source": "later_wave_questionnaire_adjudication", - "later_wave_mapping_status": "TO_VERIFY", + "later_wave_mapping_status": "resolved_in_the_pension_registry_under_amendment_5", "counted_disposition": "left_to_accumulate", "excluded": [ "rolled_over_into_ira", "both_plan_account_items_reasked", "off_route" ], - "unreported_amount": "zero_counted", "top_code": "as_recorded", "brackets": "not_used" }, @@ -1227,7 +1235,7 @@ Ratification requires code/block equality, completed manifests and replacement o "ofum_unit": "single_individual", "parameters": { "file": "data/external/track_u2_ssi_parameters_2012_2022.json", - "sha256": "TO_VERIFY", + "sha256": "a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762", "years": [ 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 @@ -1328,24 +1336,305 @@ Ratification requires code/block equality, completed manifests and replacement o "mechanical incidence" ], "decisions": { - "u2_ratification": "pending_Max", - "prior_u1_rulings": "precedent_not_u2_execution_authorization", - "rulings_record": "separate_u2_record_required", - "row_set_amendment": "omit_u0f_and_eight_f_rows", - "downloads": "no_approval_required_Max_2026_09_28" + "ruled_by": "Max", + "ruled_on": "2026-09-28", + "claim_class": { + "ruling": "track_u_psid_realized_measurement_not_a_projection", + "decision_record": "d514" + }, + "observation_convention": { + "ruling": "u0_exact_age_five_births_u1_both_half_weighted_observations", + "decision_record": "d514" + }, + "common_identification_support": { + "ruling": "earliest_presence_in_common_support_waves", + "support_waves": [2013, 2015, 2017, 2019, 2021, 2023], + "decision_record": "d514" + }, + "rows_and_fallback": { + "ruling": ["U0", "U1", "U2", "U3", "U4", "U5", "U7", "U8", "U9", "U10"], + "omitted": ["U0-F", "eight_-F_rows"], + "headline": "fixed_u0_no_fallback", + "decision_record": "d514" + }, + "income_and_mortality": { + "ruling": "inherited_concept_and_life_tables_with_section_3_role_amendment_and_later_wealth_identities", + "decision_record": "d514" + }, + "ssi_capture": { + "ruling": "separate_verified_2012_2022_capture_exact_2012_overlap", + "decision_record": "d514" + }, + "downloads": { + "ruling": "no_approval_required_Max_2026_09_28", + "decision_record": "d514" + }, + "comparison_publication": { + "ruling": "fifteen_cells_inherited_precision_no_acceptance_threshold_publish_regardless", + "acceptance_rule": null, + "decision_record": "d514" + }, + "process": { + "ruling": "mapping_review_invented_differential_tests_and_preregistration_pass_before_forecast_and_registration", + "decision_record": "d514" + }, + "ratification": { + "ruling": "ratify_by_merge_after_section_20_steps_2_to_5", + "decision_record": "d514" + }, + "rulings_record": { + "ruling": "separate_u2_record", + "u1_rulings": "precedent_not_u2_execution_authorization", + "decision_record": "d514" + }, + "amendment_1a_documented_routes": { + "ruling": "ratified_as_written", + "registration_discloses": "ofum_route_documented_reporting_completeness_for_withheld_answers_not_documented", + "registry_entries": [ + "roles:2015.relationship.90", + "roles:2017.relationship.90", + "roles:2017.relationship.92" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "confirmed_by": "d745", + "confirmed_on": "2026-10-12" + }, + "amendment_1b_2013_inherited_meanings": { + "ruling": "ratified_as_written", + "registry_entries": ["roles:2013.relationship.90"], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "confirmed_by": "d745", + "confirmed_on": "2026-10-12" + }, + "amendment_1c_first_year_cohabitor": { + "ruling": "ratified_as_written", + "convention": "ofum_family_basis_outside_the_spouse_slot_no_legal_spouse_life_no_marital_resolution_inherited_birth_support", + "registry_entries": [ + "roles:2013.relationship.88", + "roles:2015.relationship.88", + "roles:2017.relationship.88", + "roles:2019.relationship.88", + "roles:2021.relationship.88", + "roles:2023.relationship.88" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "confirmed_by": "d745", + "confirmed_on": "2026-10-12" + }, + "amendment_2_annuitant_input_precedence": { + "ruling": "ratified_as_written", + "rule": "recorded_sex_er32000_and_derived_income_year_age_family_file_spouse_age_and_sex_slot_metadata_only", + "registry_entries": [ + "income:income.2015.wife_age", + "income:income.2015.wife_sex", + "income:income.2017.wife_age", + "income:income.2017.wife_sex", + "income:income.2019.wife_age", + "income:income.2019.wife_sex", + "income:income.2021.wife_age", + "income:income.2021.wife_sex", + "income:income.2023.wife_age", + "income:income.2023.wife_sex" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30" + }, + "amendment_3_spouse_retirement_crosswalk": { + "ruling": "ratified_as_written", + "rule": "non_executable_metadata_components_counted_once_in_money_income", + "registry_entries": [ + "income:income.2013.wife_retirement_annuities", + "income:income.2015.wife_retirement_annuities", + "income:income.2017.wife_retirement_annuities", + "income:income.2019.wife_retirement_annuities", + "income:income.2021.wife_retirement_annuities", + "income:income.2023.wife_retirement_annuities" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30" + }, + "amendment_4_wealth_accuracy_flags": { + "ruling": "ratified_as_written", + "rule": "imputation_diagnostics_only_never_amounts_or_sample_inclusion", + "registry_entries": [ + "wealth:wealth.2013.wealth2_acc", + "wealth:wealth.2013.checking_saving_acc", + "wealth:wealth.2015.wealth2_acc", + "wealth:wealth.2015.checking_saving_acc", + "wealth:wealth.2017.wealth2_acc", + "wealth:wealth.2017.checking_saving_acc", + "wealth:wealth.2019.wealth2_acc", + "wealth:wealth.2021.wealth2_acc", + "wealth:wealth.2023.wealth2_acc" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30" + }, + "amendment_5a_later_pension_routes": { + "ruling": "ratified_as_written", + "previous_routes": "income_concept.employer_dc.previous_routes", + "registry_entries": [ + "pension:2017.route.inherited_route_amendment", + "pension:2019.route.inherited_route_amendment", + "pension:2021.route.inherited_route_amendment", + "pension:2023.route.inherited_route_amendment" + ], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "replaces_draft_3_lines": [1173, 1174, 1175, 1176] + }, + "amendment_5b_2015_checkpoint": { + "ruling": "ratified_as_written", + "refused_branch": "P62ACKPT: P46=1 AND P52=1 AND P62AMT=DK/RF", + "off_route_disposition": "off_route", + "registry_entries": ["pension:2015.route.formula_unknown_checkpoint"], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "replaces_draft_3_lines": [1173, 1174, 1175, 1176] + }, + "amendment_6_blocker_b3_2017_weight": { + "ruling": "use_the_released_weight_and_disclose", + "weight": "ER34651", + "registration_discloses": "weights:2017.cross_section_weight.registration_disclosure", + "registry_entries": ["weights:2017.cross_section_weight"], + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + "option": "1", + "amends_draft_3_lines": [134] + }, + "psid_documentary_questions": { + "ruling": "send_all_three_now_as_a_held_draft_for_max", + "sent_on": "2026-09-30", + "answered_on": "2026-10-07", + "research_record": "docs/design/u2_m1b_psid_research.md", + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30" + }, + "blocker_b1_male_code_20_2015": { + "ruling": "exclude_and_disclose", + "supersedes": "d637_hold_for_psid", + "disclosed_count": "persons_recorded_male_with_2015_code_20_in_family_units_that_would_otherwise_supply_an_observation", + "count_decides": "nothing", + "disposition": "male code 20 in 2015 family unit", + "excluded_from": "every_row", + "member_condition": { + "sex": 1 + }, + "registry_entries": ["roles:2015.relationship.20"], + "option": "1d-2", + "amends_draft_3_lines": [160], + "specification_section": "16c", + "decision_record": "d1090", + "ruled_on": "2026-10-10" + }, + "blocker_b2_codes_90_92_2019_2023": { + "ruling": "exclude_and_disclose", + "supersedes": "d637_hold_for_psid", + "disclosed_count": "persons_with_code_90_or_92_in_2019_2021_2023_family_units_that_would_otherwise_supply_an_observation", + "count_decides": "nothing", + "disposition": "uncooperative spouse or partner in family unit", + "excluded_from": "every_row", + "member_condition": null, + "registry_entries": [ + "roles:2019.relationship.90", + "roles:2019.relationship.92", + "roles:2021.relationship.90", + "roles:2021.relationship.92", + "roles:2023.relationship.90", + "roles:2023.relationship.92" + ], + "option": "1e-1", + "amends_draft_3_lines": [152, 154, 158], + "specification_section": "16c", + "decision_record": "d1090", + "ruled_on": "2026-10-10", + "amended_waves": [2019, 2021, 2023] + }, + "amendment_5c_u7_2015_spouse_slot": { + "ruling": "follows_blocker_b1", + "registry_entries": ["pension:2015.route.respondent_slots"], + "option": "1d-2", + "specification_section": "16c", + "decision_record": "d1090", + "ruled_on": "2026-10-10" + }, + "b2_disclosed_count": { + "ruling": "persons_with_code_90_or_92_in_2019_2021_2023_family_units_that_would_otherwise_supply_an_observation", + "person_counted_exclusions": [ + "male code 20 in 2015 family unit", + "uncooperative spouse or partner in family unit" + ], + "disclosed_per_row": ["per_wave", "distinct_persons"], + "count_decides": "nothing", + "specification_section": "19", + "decision_record": "d1242", + "ruled_on": "2026-10-16" + }, + "u1_input_pin_check": { + "ruling": "run_the_optional_structure_only_u1_load_once", + "u1_input_frames_sha256": "43c41f444128ecf72ce9f8c5e64a7cda61a6fccaa2a58ff60ae7ce91356c4271", + "statistic_computed": null, + "authorization_used": { + "on": "2026-10-21", + "head": "1e739d328b73c99d259a72952b3452546721bfb8", + "result": "pin_matched" + }, + "specification_section": "13", + "decision_record": "d1597", + "ruled_on": "2026-10-16" + }, + "preregistration_pass": { + "ruling": "authorize_the_step_4_pass_once", + "scope": [ + "structural_counts", + "dispositions", + "join_and_reconciliation_counts", + "named_f17_component_summaries" + ], + "not_computed": ["income_concept", "poverty_threshold_assignment", "poverty_status"], + "registered_run_frame_environment": "the_preregistration_evidence_pandas_version_and_string_inference_setting", + "authorization_used": { + "on": "2026-10-19", + "head": "640adccf84f9f68c0fb893a834cfdfaca20e7888", + "result": "stopped_in_the_birth_law_without_output" + }, + "specification_section": "20", + "decision_record": "d1649", + "ruled_on": "2026-10-16" + }, + "seed_birth_year_support": { + "ruling": "count_out_or_reseed_before_the_birth_law_then_rerun_once", + "support": [1889, 2016], + "counted_out": ["before_1889", "after_2016", "seed_age_0", "no_valid_seed_age"], + "disclosure_keys": ["n_seed_birth_year_counted_out", "n_seed_birth_year_reseeded"], + "count_decides": "nothing", + "estimates_career": "unchanged", + "widening_approved_on": "2026-10-20", + "authorization_used": { + "on": "2026-10-21", + "head": "1e739d328b73c99d259a72952b3452546721bfb8", + "tests_run": 39502262845, + "result": "completed" + }, + "specification_section": "16d", + "decision_record": "d1724", + "ruled_on": "2026-10-19" + } }, - "blocked_by": [ - "later_wave_component_and_role_adjudication", - "2015_male_code20_family_slot_source_resolution", - "code90_code92_income_routing_source_resolution", - "later_wave_employer_dc_routing_adjudication", - "mapping_manifests_and_independent_review", - "ssi_capture_verification_and_pin", - "cohort_isolation_and_identity_guards", - "invented_dry_run_and_u1_differential_evidence", - "preregistration_structure_reconciliation_and_f17_pass", - "u2_ratification_forecast_and_registration" - ] + "blocked_by": [] } ``` @@ -1695,12 +1984,12 @@ What remains before ratification: 2. Each ruling is applied to the registries in its own reviewed commit. The d1090 commit (2026-10-10) replaced the eight B1 and B2 refusals with §16c's exclusions and released their dependents. The d637 commits, one per ratified amendment, have so far resolved amendment 2's ten later spouse age and sex records as slot metadata and amendment 3's six spouse-retirement crosswalks as non-executable metadata, recorded amendment 4's ruling on the nine corrected wealth accuracy flags, resolved the 2015 P62ACKPT checkpoint under 5b, released the 2017–2023 P64/P65 records under 5a and resolved the 2017 weight ER34651 under amendment 6's option 1; the §15 fragment itself is materialized under item 9. Max confirmed 1a–1c on 2026-10-12 (d745), and the 1a, 1b and 1c commits recorded the ruling on the documented 2015 code-90 and 2017 code-90 and code-92 routes and on 2013's inherited code-90 meaning, and resolved the six code-88 records under the first-year cohabitor convention. No TO VERIFY record remains, and the loader's source preflight passes; the structural pass still needs Max's authorization (item 8; §20 step 4). 3. The recommendations asked Max to send PSID the questions in `docs/design/u2_m1b_psid_research.md` §§1–3 now. He ruled to send all three as a held draft for him (d637), and they were sent on 2026-09-30 (§16b). PSID answered all three on 2026-10-07 (research record §§1–3), and Max ruled on B1 and B2 on 2026-10-10 (d1090; §16c). 4. Verify identification support, observation plans and refreshed design domains; weight documentation follows the amendment 6 ruling (option 1, ratified 2026-09-30; §16b). **Done on the documents and invented data (2026-10-16):** milestone 2 (#487) holds the observation plans to the milestone-1 support registry (`tests/track_u2/test_u2_plans.py::test_plan_equals_the_milestone_1_support_registry`, `::test_u0_has_five_exact_age_pairs`, `::test_u1_has_fifteen_pairs_over_ten_births_summing_to_one`), seeds identification from the earliest positive-weight presence in the support waves (`::test_seed_is_the_earliest_positive_weight_presence`), and holds the refreshed design domains to the design registry, refresher strata 88–94 included, with support-only waves kept out of U0's observations and design frame (`test_u2_design_floor.py::test_design_domains_equal_the_design_registry`, `::test_refresher_strata_88_to_94_are_valid`, `test_u2_plans.py::test_support_waves_never_enter_u0_observations_or_design`). The d637 registry commits (#540) applied amendment 6's 2017 weight with its registration disclosure (`test_adjudication_applied.py::test_d637_amendment_6_uses_er34651_as_released`). Their counts on real records come from the structural pass (item 8), which resolves any failure it finds (§20 step 4). -5. Pin the SSI parameter file in §15. The capture exists and the adjudication reconfirmed all 106 source hashes. **Code pin checked (2026-10-16); §15's pin is open:** the code pins the capture by its SHA-256, `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762` (`parameters.SSI_SHA256`; `diagnostics.SSI_SHA256` equals it). Its loader takes no caller-supplied hash, both readers refuse any other bytes (`test_u2_parameters.py::test_loaders_take_no_caller_supplied_hash`, `::test_a_changed_ssi_capture_refuses`), and a registered check refuses another recorded hash (`::test_registered_check_needs_every_u2_pin`). §15's `ssi.parameters.sha256` still reads `TO_VERIFY`: §15 precedes §16a, so no revision of this draft edits it, and §20 step 5 writes the pin when it materializes the final parameter block (item 9). +5. Pin the SSI parameter file in §15. The capture exists and the adjudication reconfirmed all 106 source hashes. **Code pin checked (2026-10-16); §15's pin is open:** the code pins the capture by its SHA-256, `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762` (`parameters.SSI_SHA256`; `diagnostics.SSI_SHA256` equals it). Its loader takes no caller-supplied hash, both readers refuse any other bytes (`test_u2_parameters.py::test_loaders_take_no_caller_supplied_hash`, `::test_a_changed_ssi_capture_refuses`), and a registered check refuses another recorded hash (`::test_registered_check_needs_every_u2_pin`). §15's `ssi.parameters.sha256` still reads `TO_VERIFY`: §15 precedes §16a, so no revision of this draft edits it, and §20 step 5 writes the pin when it materializes the final parameter block (item 9). **Pinned (2026-10-21; §20 step 5):** §15's `ssi.parameters.sha256` is `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762`, and `rows.check_block_against_code` holds it equal to `parameters.SSI_SHA256` with the rest of the block (`test_u2_specification_block.py::test_the_committed_block_equals_the_code`). 6. Complete historical-isolation and cross-cohort refusal tests (milestone 2). **Done (milestone 2, #487; kept through #525):** every U2 module is an exact historical-source exclusion, unreachable from the birth evidence (`test_u2_isolation.py::test_every_u2_module_is_an_exact_historical_exclusion`; `tests/estimates/test_birth_evidence_artifact.py`), and §14's cross-cohort refusals hold both ways (`test_u2_parameters.py::test_u1_runner_refuses_the_track_m_and_u2_captures`, `::test_u1_captures_are_refused_as_u2_sources`, `test_u2_identity.py::test_u1_guards_refuse_u2_seed_and_rows`). `docs/design/u2_m2_report.md`'s §13 and §14 audit lists each test. 7. Produce U2 invented dry-run and exact U1 differential evidence (milestone 2). **Done (2026-10-16):** the invented dry run (`scripts/track_u2_dry_run.py`; `test_u2_scripts.py::test_dry_run_records_every_branch_and_refusal`) and the exact U1 differential at `f7a84d0b` against `9cee2423`, equal in every check, the dry run included (`docs/design/u2_m2_u1_differential_f7a84d0.json`). This is evidence at `f7a84d0b`; the differential runs again at the merge that ratifies the specification (§20 step 6). -8. Complete the pre-registration structural, reconciliation and F17 component pass, including only the counts that the ratified B1 and B2 options name (under the 2026-10-10 ruling, the counts specified by §16a options 1d-2 and 1e-1, each disclosed and deciding nothing; §16c). -9. Check the specification, literal named deltas, parameter block (with the amendment 5 fragment, ratified 2026-09-30), manifests and ten-row implementation for equality. **Partly checked (2026-10-16):** the named deltas equal §12 literally and the rows equal the §15 block (`test_u2_identity.py::test_named_deltas_equal_section_12_literally`, `::test_rows_equal_the_section_15_block`). The equality checks of the specification, the manifests and the final parameter block wait for §20 step 5, which materializes the block and empties §15's `blocked_by`. -10. Obtain ratification, then a fresh forecast and an independent forecast check. +8. Complete the pre-registration structural, reconciliation and F17 component pass, including only the counts that the ratified B1 and B2 options name (under the 2026-10-10 ruling, the counts specified by §16a options 1d-2 and 1e-1, each disclosed and deciding nothing; §16c). **Done (2026-10-21; §20 step 4):** the second pass, under d1724, completed with no refusal, failure or mapping failure, and the structural counts of rows U0 and U1 carry the per-exclusion person counts (`n_family_unit_exclusion_persons` and `n_family_unit_exclusion_persons_distinct`); this record states none of them. +9. Check the specification, literal named deltas, parameter block (with the amendment 5 fragment, ratified 2026-09-30), manifests and ten-row implementation for equality. **Partly checked (2026-10-16):** the named deltas equal §12 literally and the rows equal the §15 block (`test_u2_identity.py::test_named_deltas_equal_section_12_literally`, `::test_rows_equal_the_section_15_block`). The equality checks of the specification, the manifests and the final parameter block wait for §20 step 5, which materializes the block and empties §15's `blocked_by`. **Done (2026-10-21; §20 step 5):** §15 holds the final block, `u2-ratified-1`, with the amendment 5 fragment, an empty `blocked_by` and U2's rulings record, and `rows.check_block_against_code` holds the whole block equal to the code, every key and value; the manifests it is checked against are the committed registries, all RESOLVED (§20 step 2), which give each wave's weight, family-unit and WEALTH1 variables and every registry entry the rulings record names (`test_u2_specification_block.py`). +10. Obtain ratification, then a fresh forecast and an independent forecast check. **Ratification:** the merge of the revision that completes §20 step 5 (§20 step 6). The fresh forecast and its independent check are §20 step 7. Required fields remain **TO VERIFY** until source adjudication is complete. Neither empty empirical categories nor observed outputs may substitute for documentary resolution. @@ -1939,6 +2228,15 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - The record was written from the run-2 `MANIFEST.md`, which the restricted list allows to be read and cited, and from the coordinating session's value-free brief. No other file in either pass's folder was opened. - No count, statistic or outcome was computed, no model or estimator was run, no PSID file was read, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (§20 steps 4–6 section). +**`u2-ratified-1`, §20 step 5: the final parameter block and U2's rulings record (2026-10-21):** + +- §15 holds the final block, version `u2-ratified-1` with status `ratified_frozen`. The three `TO_VERIFY` keys are resolved: the SSI capture's SHA-256 is pinned, the employer-DC `reader` names the U2 adapter (`uniform_cut_track_u2/sources.py`), and `later_wave_mapping_status` records the RESOLVED pension registry. `previous_routes` is amendment 5's ratified fragment, laid out so that the registry-cited lines 1173 and 1185 keep their draft-3 number and text. `population.relationship_rules` names amendment 1. `blocked_by` is empty, and §20 step 5 gives each blocker's resolution. +- `decisions` holds U2's rulings record: d514's eleven entries, unchanged, and an entry for every later ruling: d637 (amendments 1a–1c, confirmed by d745; amendments 2, 3, 4, 5a, 5b and 6; the PSID questions), d1090 (B1, B2 and amendment 5c), d1242, d1597, d1649 and d1724. +- Code: `rows.U2_RULINGS` builds the registry-applied entries from the registry module's ratified scopes; `rows.expected_specification_block()` states the whole block from the code; `rows.check_block_against_code` and `rows.check_rulings_against_code` refuse any difference, and the registered run's preflight and the dry run apply both. The registered-run docstring, the package docstring and the cohort's decision notes no longer describe a draft block. +- Tests: the new `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code, mutates every leaf and refuses each change, adds and drops keys, checks JSON types (`true` is not `1`, `1` is not `1.0`), holds the rulings record to every decision, and holds the TO VERIFY appendix complete. The employer-DC, identity and registered-script tests now expect the materialized block, and the specification-revision test holds lines before §15 to draft 3 (except the version and status lines), §15's lines through 1185 to draft 3 except where the block was materialized, and §16 to draft 3's text at its new position. +- §17 items 5, 8, 9 and 10 and §20 steps 5 and 6 record the work; the header gives the new version and status; the new appendix maps each prose TO VERIFY item to its resolution. +- No registry changed, so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed, no model or estimator was run on real data, no PSID file was read, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (§20 steps 4–6 section). + ## 20. Ratification and execution record **Pending.** @@ -1947,8 +2245,14 @@ Revision verification comprised the two touched test files and `tests/track_u2/t 2. Complete implementation, manifests and parameter capture. **Done (2026-10-16).** Implementation: milestone 2 (#487) and its three review rounds, the last landing as #525 (`f7a84d0b`); and #544, which records with the frame digest the pandas version and string inference setting it was taken under and refuses a frozen load or registered run under any other (`test_u2_loader.py::test_any_other_frame_environment_refuses_the_registered_check`, `::test_a_frozen_load_refuses_another_frame_environment`). Manifests: the documentary source records, 1,766 registry entries and 26 SSI and Census records, are all RESOLVED, none REFUSED or TO VERIFY (`test_adjudication_applied.py::test_status_counts_after_independent_adjudication`), and the loader's source preflight passes (`test_u2_mappings.py::test_source_preflight_passes_at_the_committed_registries`; #524, #540). A RESOLVED record is a documentary finding; reconciliation on real records is step 4's. Parameter capture: the U2 SSI capture, the Track M threshold capture and U1's inherited life tables, each pinned by hash in the code (§17 item 5; `test_u2_parameters.py::test_threshold_capture_is_the_pinned_track_m_file`; `parameters._LIFE_TABLE_PINS`). The final parameter block is step 5's. 3. Complete independent mapping review, invented tests, historical-isolation checks and exact U1 differential evidence. **Done (2026-10-16).** Independent mapping review: the independent adjudication of milestone 1's registries (ERRORS FOUND); milestone 1b applied its corrections in #486, whose independent review requested changes on 11 findings and whose blind verifier then returned U2-M1B: VERIFIED (`test_adjudication_applied.py::test_dispositions_match_the_adjudication`; §17). Every later registry change had an independent review before it merged: the d1090 rounds (§19), ending in APPROVE at round 4 (`e7a558f`) and at #524's merged head (`742590b`; #524's merge audit); the d637 and d745 commit reviews, the last a delta review, APPROVE (§19); and #540's gating review, an Opus 5.5 review with a shell, APPROVE (#540's merge audit). Invented tests and historical isolation: the milestone-2 report's §13 and §14 audit (#487, #525), closed by three review rounds and a fourth-pass mutation run in which a test kills every mutant; §13's optional structure-only U1 load needs Max's approval and is not run. Exact U1 differential evidence: §17 item 7. 4. Complete the authorized pre-registration structural, reconciliation and F17 component pass; resolve mapping failures. The pass reads real PSID records, so it needs Max's authorization, recorded as a chief-of-staff decision. `scripts/track_u2_structure.py` and `scripts/track_u2_component_diagnostics.py` refuse to run without `--authorized-by `, exiting non-zero before any PSID path is resolved or opened, and the loader refuses an unfrozen load without the same record. The flag records the decision id, the git head and the UTC time in the pass's output; it only records the authorization and does not grant it. **First pass (d1649): stopped.** It ran once, on 2026-10-19 at `640adccf`, and stopped in the birth law without writing any output; the component diagnostics were not run (§16d). **Max's ruling of 2026-10-19 (d1724; §16d)** authorizes one more pass, structure and then component diagnostics, under d1649's scope with `--authorized-by d1724`, once the §16d change merges and master's Tests pass at the run head. **Second pass (d1724): completed.** It ran once, on 2026-10-21 at `1e739d32` (`1e739d328b73c99d259a72952b3452546721bfb8`, the merge of #552), after master's Tests run 39502262845 passed at that head. The d1597 structure-only U1 load (§13) ran first and passed: the staged inputs hash to U1's registered input-frame pin. `scripts/track_u2_structure.py` and then `scripts/track_u2_component_diagnostics.py`, each with `--authorized-by d1724`, exited 0, with no refusal, failure or mapping failure. Every family income identity and every WEALTH1 identity reconciles in all six support waves, so no reconciliation failure category is recorded, and the structural counts of rows U0 and U1 both carry §16d's disclosure keys (`n_seed_birth_year_counted_out`, with its four reasons, and `n_seed_birth_year_reseeded`). The outputs stay outside the repository, in `EV/track-u2-step4-pass-20261021-run2/`, restricted except its `MANIFEST.md` (SHA-256 `acf5d8a4252e88b3bbe51581943f1e3b25ba73f205a8240e8e45b9ca538b5e6d`); the first pass's record is `EV/track-u2-step4-pass-20261019/MANIFEST.md` (`1e91407b…a366fa`). This record states no count, value or other statistic from either pass. Under d1724 no further real-data run of either script is authorized. This step is done. -5. Materialize the final parameter block and U2 rulings record; require exact code/specification agreement. -6. Ratify the specification by merge. +5. Materialize the final parameter block and U2 rulings record; require exact code/specification agreement. **Done (2026-10-21).** §15's block is the final contract, version `u2-ratified-1` with status `ratified_frozen`, and `rows.expected_specification_block()` states the same block from the code: the identity, plans and support waves; each wave's weight, family-unit and WEALTH1 variables, read from the committed registries; the income-concept, annuity, threshold, cut and SSI parameters and their capture pins; the cells, floor, design and memo settings; the rows, entry points and labels; and the rulings record. `rows.check_block_against_code` refuses a block that differs at any key, list length, JSON type or value and names every differing path. The registered run's preflight and the dry run apply it, and `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code and refuses a change to each of its leaves. In detail: + - **The three `TO_VERIFY` keys.** `ssi.parameters.sha256` pins the U2 SSI capture, `a58d55c1…3762`, as the code already did (§17 item 5). `income_concept.employer_dc.reader` names the U2 adapter, `uniform_cut_track_u2/sources.py`, whose `employer_dc_balances` applies each wave's registry route (`dc_route`); U1's key names its own reader the same way. `later_wave_mapping_status` records that every pension-registry entry is RESOLVED under amendment 5, which the code checks before it states the block. + - **Amendment 5's fragment.** `previous_routes` is the fragment §16a printed and d637 ratified, equal to `sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS` and to the 2015 refused branch the pension registry records. The registries cite §15's lines 1173 and 1185, so both keep their draft-3 number and text: the fragment fills lines 1174–1184 in place of draft 3's generic route, and the four keys that stood there follow line 1185 unchanged except `later_wave_mapping_status`. `population.relationship_rules` now names amendment 1 with §3's role amendment. + - **`blocked_by` is empty.** Each draft-3 blocker is resolved: later-wave component and role adjudication, and the mapping manifests and independent review, by the RESOLVED registries and their reviews (steps 2 and 3); 2015 male code 20 by d1090's B1 exclusion (§16c); code-90 and code-92 routing by amendment 1a and 1b (d637, confirmed by d745) and d1090's B2 exclusion; later-wave employer-DC routing by amendment 5 (d637); the SSI capture by its verification and this pin (§17 item 5); cohort isolation and identity guards, and the invented dry run and U1 differential, by milestone 2 (§17 items 6 and 7); the pre-registration pass by step 4; and ratification by this revision's merge (step 6). The fresh forecast and the registration, which that last blocker also named, are steps 7 and 8: the registered run's preflight refuses without the issue #42 registration and its binding, and without the committed pre-registration evidence, so an empty `blocked_by` authorizes no run. + - **U2's rulings record.** `decisions` holds `rows.U2_RULINGS` with `ruled_by` Max and `ruled_on` 2026-09-28: d514's eleven entries, unchanged, which adopt every §16 ruling; d637's entries for amendments 1a–1c (confirmed by d745), 2, 3, 4, 5a, 5b and 6 and for the three PSID questions; d1090's entries for B1, B2 and amendment 5c; and one entry each for d1242 (B2's counting unit), d1597 (the structure-only U1 load), d1649 (the step-4 pass) and d1724 (the birth law's support). Each registry-applied entry names the registry entries it governs and copies their ruling, built from the registry module's own scopes, and `rows.check_rulings_against_code` holds the record to the committed registries, the role rules and the cohort code. The d1597, d1649 and d1724 entries record that each authorization was used once, with the head, date and value-free result. + - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. + - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. +6. Ratify the specification by merge. **This revision's merge (2026-10-21).** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential runs again at the revision's last code change (§17 item 7). The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). 7. Obtain and independently check the fresh forecast. 8. Post the issue #42 registration with exact commit, specification hash, input and parameter pins, rows, headline, plans and exposure disclosure. 9. Execute the one-shot under the required launchd process. @@ -2032,3 +2336,25 @@ Registration must state that U0 observes five birth years at exact age 67; row U | Check §4: mapping checks before the one-shot | §§4, 9, 14, 15, 16 and 20 preserve the pre-registration pass, blocker and frozen-identity recheck | | Check §5: U1-compatible parameter keys | §15 preserves inherited keys and explicit U2 departures; R7 supplies the missing reader key | | Check §6: bounded verification and blindness | §§2, 17–19 distinguish inherited verification, current documentary checks, outstanding routing/parameter work and unexecuted model tests; no held-out value or direction is supplied | + +## Appendix: TO VERIFY item → where resolved + +§§2–14 keep their draft-3 text and line numbers, because the registries cite those lines and the tests pin them. These are the lines there that still say **TO VERIFY**, with §17's closing line, and where each item was resolved (§20 step 5). Line numbers are this file's, which equal draft 3's below §15. + +| Line | Item left open in draft 3 | Resolution | Evidence | +|---:|---|---|---| +| 59 | Later SSI capture: not yet created | Created and pinned: `data/external/track_u2_ssi_parameters_2012_2022.json`, SHA-256 `a58d55c1…3762`, in `parameters.SSI_SHA256` and §15 `ssi.parameters.sha256` (§17 item 5) | `test_ssi_capture.py::test_capture_manifest_hashes_and_bytes`, `::test_capture_reproduces_exactly_offline` | +| 60 | COLA notices: downloaded bytes and parameter cross-checks | 106 sources staged with URL, retrieval time, SHA-256 and path in `tests/data/track_u2/ssi_sources/manifest.json`; each year's federal benefit rates checked against its notice, and the CFR provisions against their historical editions | `test_ssi_capture.py::test_source_manifest_schema_and_hashes`, `::test_fbr_independently_matches_official_notice`, `::test_2017_actual_notice_and_2018_corrected_republication`, `::test_historical_cfr_verification_and_parameter_crosscheck` | +| 64 | Unverified items block registration where required | All 1,766 registry entries are RESOLVED and the loader's source preflight passes (§20 step 2) | `test_adjudication_applied.py::test_status_counts_after_independent_adjudication`; `test_u2_mappings.py::test_source_preflight_passes_at_the_committed_registries` | +| 152 | Code 92's OFUM income routing | 2017: documented route, amendment 1a (d637, confirmed by d745). 2019–2023: d1090's B2 exclusion (§16c). 2013 and 2015: the code is absent and refused as absent | `roles:2017.relationship.92`, `roles:2019.relationship.92` and the 2021 and 2023 entries; §15 `decisions.amendment_1a_documented_routes`, `.blocker_b2_codes_90_92_2019_2023` | +| 154 | Code 90's OFUM income routing | 2013: inherited meaning, amendment 1b. 2015 and 2017: documented routes, amendment 1a (d637, confirmed by d745). 2019–2023: d1090's B2 exclusion | `roles:.relationship.90`; §15 `decisions.amendment_1b_2013_inherited_meanings`, `.amendment_1a_documented_routes`, `.blocker_b2_codes_90_92_2019_2023` | +| 160 | Family-file routing of a male code-20 person in 2015 (blocker B1) | d1090's B1 exclusion, `male code 20 in 2015 family unit`, ratified as an amendment to this line (§16c) | `roles:2015.relationship.20`; §15 `decisions.blocker_b1_male_code_20_2015` | +| 190 | Code 88's routing and use beyond administrative birth support | Amendment 1c's first-year cohabitor convention (d637, confirmed by d745) | `roles:.relationship.88`; §15 `decisions.amendment_1c_first_year_cohabitor` | +| 227 | Sentinel domains, persistent-identifier semantics, weight construction and further history anchors | The individual, weights and design registries are RESOLVED; the 2017 weight under amendment 6, option 1 (d637), with its registration disclosure | `test_adjudication_applied.py::test_d637_amendment_6_uses_er34651_as_released`; §15 `decisions.amendment_6_blocker_b3_2017_weight` | +| 265 | Income component definitions and code domains | The income registry's 764 entries are RESOLVED | `test_adjudication_applied.py::test_status_counts_after_independent_adjudication` | +| 281 | Later income component IDs not verified here | Each later ID is a RESOLVED income-registry entry cited to its own wave's documentation | `test_adjudication_applied.py::test_status_counts_after_independent_adjudication` | +| 303 | Wealth component definitions, accuracy flags, sentinels, loss permissions and top codes | The wealth registry's 234 entries are RESOLVED; the nine corrected accuracy flags under amendment 4 (d637) | §15 `decisions.amendment_4_wealth_accuracy_flags` | +| 311 | Later-wave employer-DC IDs, widths, codes and routes | The pension registry's 648 entries are RESOLVED; routes under amendment 5 (5a and 5b, d637; 5c follows B1, d1090) | §15 `income_concept.employer_dc.previous_routes`; `decisions.amendment_5a_later_pension_routes`, `.amendment_5b_2015_checkpoint`, `.amendment_5c_u7_2015_spouse_slot` | +| 464 | The SSI capture's source revision, source hashes and final SHA-256 | The capture records its policyengine-us revision and the seven source files' SHA-256; its own SHA-256 is pinned | `test_ssi_capture.py::test_capture_manifest_hashes_and_bytes`, `::test_capture_reproduces_exactly_offline` | +| 521 | Retrieval records of the downloaded notices | The source manifest records URL, retrieval time, SHA-256 and path for each | `test_ssi_capture.py::test_source_manifest_schema_and_hashes` | +| §17's closing line | Required fields remain TO VERIFY until source adjudication is complete | Source adjudication is complete: no registry entry and no §15 value is TO VERIFY. The line stays, because §16a quotes it as draft-3 line 1391 and says it closes §17 | `test_adjudication_applied.py::test_section_16a_quotes_draft_3_verbatim`; `test_u2_specification_block.py::test_the_block_holds_no_open_value` | diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index e5e9086c4..ae99be993 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -386,3 +386,7 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - The repository at `1e739d32`: `CLAUDE.md`; the specification in full; the U1 specification's header, §15 block and §20; the U2 package, scripts and tests named in the PR; `data/external/track_u2/u1_identity.json` and the registries' entry ids and fields; the U2 SSI capture's provenance fields and its source manifest's records; `tests/README-tiers.md`; Track M's `specification.py` header; `estimates/cola_age_profile.specification_unratified_fields`. Also `~/PolicyEngine/CLAUDE.md`. **Not opened:** any other evidence-folder path; any comparator folder, seal or Boomers Report copy; `runs/track_b_b2_*`, `docs/design/track_b_attempt_ledger.md` and `paper/paper.qmd`; other sessions' transcripts or scratchpads; `~/.subfleet/` other than this session's own review jobs; any PSID file or codebook. + +**Ran:** the U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to an empty directory, which stayed empty, so the checks that need PSID documentation skipped; Black and Ruff; the tier recount by collection; and the mutation checks listed in the PR. Every block, rulings and appendix check reads only the specification, the committed registries and the code. + +**Values:** it stated and inferred no 1946–55 value or direction. Every number it wrote is a hash, a commit, a CI run id, a date, a line number, a registry entry count, a test count or a constant of the code or the specification. diff --git a/scripts/run_track_u2_registered.py b/scripts/run_track_u2_registered.py index 171049002..82427640b 100644 --- a/scripts/run_track_u2_registered.py +++ b/scripts/run_track_u2_registered.py @@ -2,11 +2,11 @@ Specification sections 14 and 20. This is the only entry point that would compute the U2 statistic on real data. It is built but not -authorized: the specification is ``u2-draft-4`` (not ratified; Max has -ruled on section 16a's amendments, d637 and d1090) and the -pre-registration evidence record does not exist, so its preflight -refuses (items 4 and 8) before any PSID file is opened. :func:`preflight` -verifies, in order: +authorized: the specification is ``u2-ratified-1`` (section 20 steps 5 +and 6), but the pre-registration evidence record is not committed and no +issue #42 registration binds a run, so its preflight refuses (items 8 +and 9) before any PSID file is opened. :func:`preflight` verifies, in +order: 1. the registration pointer is an issue #42 comment URL; 2. ``--registered-commit`` is a full SHA equal to ``HEAD`` and the @@ -22,7 +22,12 @@ value anywhere; 5. its rows equal :data:`~populace_dynamics.uniform_cut_track_u2.rows. REGISTERED_ROWS` with the fixed U0 headline, its ``decisions`` equal - U2's own rulings record (U1's are refused), and section 12's named + U2's own rulings record (U1's are refused), the rulings record agrees + with the code and the committed registries + (:func:`~populace_dynamics.uniform_cut_track_u2.rows. + check_rulings_against_code`), the whole block equals the code in every + key and value (:func:`~populace_dynamics.uniform_cut_track_u2.rows. + check_block_against_code`; section 20 step 5), and section 12's named deltas equal the code's literal strings; 6. row U1's observation plan (and so U0's) equals milestone 1's committed support registry, cell by cell; @@ -288,6 +293,8 @@ def preflight( check_specification_ratified(block) row_check = rows.check_rows_against_block(block) ruling_check = rows.check_u2_rulings_against_block(block) + rulings_code_check = rows.check_rulings_against_code() + block_check = rows.check_block_against_code(block) delta_check = rows.check_named_deltas_against_specification() plan_check = cohort.check_plan_against_support_registry( sources.RegistrySet.committed() @@ -317,6 +324,8 @@ def preflight( "binding_sha256": expected, "rows": row_check, "rulings": ruling_check, + "rulings_against_code": rulings_code_check, + "block_against_code": block_check, "named_deltas": delta_check, "plan": plan_check, "mapping_review": review, diff --git a/scripts/track_u2_dry_run.py b/scripts/track_u2_dry_run.py index e53a9626a..aac9a8e2b 100644 --- a/scripts/track_u2_dry_run.py +++ b/scripts/track_u2_dry_run.py @@ -920,8 +920,10 @@ def checks( return { "specification_rows": rows.check_rows_against_block(block), "named_deltas": rows.check_named_deltas_against_specification(), - "u2_rulings_against_block": _refusal( - lambda: rows.check_u2_rulings_against_block(block) + "u2_rulings_against_block": rows.check_u2_rulings_against_block(block), + "u2_rulings_against_code": rows.check_rulings_against_code(registries), + "specification_block_against_code": rows.check_block_against_code( + block, registries ), "plans": { "u0_pairs": len(cohort.observation_plan(cohort.U2CohortSpec())), @@ -1197,6 +1199,7 @@ def results_markdown(result: dict[str, Any]) -> str: round_2 = check["round_2_refusals"] round_3 = check["round_3_refusals"] u1_branch = check["row_branches"]["U1"] + against_code = check["specification_block_against_code"] lines += [ "", "## Checks", @@ -1206,9 +1209,13 @@ def results_markdown(result: dict[str, Any]) -> str: f"{check['specification_rows']['rows_equal_the_block']}; named " f"deltas equal section 12: " f"{check['named_deltas']['equal_to_section_12']}.", - "- U2 rulings record against the draft block: refused " - f"{check['u2_rulings_against_block']['refused']} (section 20 step 5 " - "materializes it).", + "- U2 rulings record equals the block's decisions: " + f"{check['u2_rulings_against_block']['rulings_equal']}; agrees with " + "the code and the committed registries: " + f"{check['u2_rulings_against_code']['rulings_equal_the_code']}; " + "the whole section 15 block equals the code " + f"({against_code['specification_version']}): " + f"{against_code['block_equals_code']}.", f"- Plans: U0 {check['plans']['u0_pairs']} pairs; U1 " f"{check['plans']['u1_pairs']} pairs over " f"{check['plans']['u1_birth_years']} birth years; equal to the " diff --git a/src/populace_dynamics/uniform_cut_track_u2/__init__.py b/src/populace_dynamics/uniform_cut_track_u2/__init__.py index 54857360f..27760ee51 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/__init__.py +++ b/src/populace_dynamics/uniform_cut_track_u2/__init__.py @@ -1,10 +1,13 @@ """Held-out target U2: Boomers 2004, 1946-55 column, uniform 13% cut. Specification ``docs/design/boomers2004_1946_55_comparison.md`` -(``u2-draft-4``), sections 3-15, with section 16a's amendments as Max +(``u2-ratified-1``), sections 3-15, with section 16a's amendments as Max ruled on them: d637 (section 16b) ratified amendments 2-5, amendment 6's option 1 and, as Max confirmed on 2026-10-12 (d745), amendment 1's parts -1a-1c; d1090 (section 16c) ruled on blockers B1 and B2. Each ruling +1a-1c; d1090 (section 16c) ruled on blockers B1 and B2; and d1724 +(section 16d) ruled on the birth law's support. Section 20 step 5 +materialized the final section 15 block and U2's rulings record, which +:mod:`.rows` holds equal to the code. Each ruling reaches this package through the source registries, one reviewed commit per amendment, and through the declared rules where the declared interpretation changes. U2 extends U1's static measurement diff --git a/src/populace_dynamics/uniform_cut_track_u2/cohort.py b/src/populace_dynamics/uniform_cut_track_u2/cohort.py index bd98e5d6d..f25d5e4ef 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/cohort.py +++ b/src/populace_dynamics/uniform_cut_track_u2/cohort.py @@ -393,9 +393,9 @@ def pending_decisions() -> tuple[psid2010.PendingDecision, ...]: spec = U2CohortSpec() ruled = ( - "section 16 default adopted by Max, d514 (2026-09-28); the U2 " - "rulings record must be materialized in the section 15 block " - "before a registered run (section 20 step 5)" + "section 16 default adopted by Max, d514 (2026-09-28); recorded in " + "U2's rulings record, the section 15 block's decisions (section 20 " + "step 5)" ) return ( psid2010.PendingDecision( diff --git a/src/populace_dynamics/uniform_cut_track_u2/rows.py b/src/populace_dynamics/uniform_cut_track_u2/rows.py index 6d7bce878..003d26b02 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/rows.py +++ b/src/populace_dynamics/uniform_cut_track_u2/rows.py @@ -17,17 +17,26 @@ (``uniform_cut_track_u.runner.NAMED_DELTAS``) is untouched. * :data:`U2_RULINGS` -- U2's own rulings record: Max's decision d514 (2026-09-28) adopting every section 16 default and ratification of the - specification, as the specification's header and section 20 step 1 - record it. :func:`check_u2_rulings_against_block` refuses a block - whose ``decisions`` differ, and refuses U1's ``MAX_RULINGS`` outright: - copying U1's authorization is insufficient (section 14). The current - ``u2-draft-4`` block still records ``u2_ratification: pending_Max``; - section 20 step 5 materializes the final block, so a registered run - refuses. + specification, and every later U2 ruling: d637 and its confirmation + d745 (section 16b), d1090 (section 16c), d1242 (section 19), d1597 + (section 13), d1649 (section 20 step 4) and d1724 (section 16d). The + entries for the registry-applied rulings are built from the registry + module's own scopes (:data:`~populace_dynamics.data.u2_source_registry. + RATIFIED_AMENDMENTS` and ``RATIFIED_EXCLUSIONS``), and + :func:`check_rulings_against_code` holds the rest to the code and the + committed registries. :func:`check_u2_rulings_against_block` refuses a + block whose ``decisions`` differ, and refuses U1's ``MAX_RULINGS`` + outright: copying U1's authorization is insufficient (section 14). +* :func:`expected_specification_block` -- the complete section 15 block + as the code states it, and :func:`check_block_against_code`, which + refuses a block that differs in any key or leaf (section 20 step 5: + exact code/specification agreement). Section 20 step 5 materialized + the final block, ``u2-ratified-1``, so the committed block passes it. """ from __future__ import annotations +import inspect import json import re from collections.abc import Mapping @@ -35,7 +44,19 @@ from pathlib import Path from typing import Any -from populace_dynamics.uniform_cut_track_u2 import identity +from populace_dynamics.data import family_income +from populace_dynamics.data import u2_source_registry as _registry +from populace_dynamics.estimates import adjusted_poverty as ap +from populace_dynamics.estimates import career +from populace_dynamics.estimates import uniform_cut_tabulation as ut +from populace_dynamics.uniform_cut_track_u2 import ( + cohort, + identity, + memo, + parameters, + sources, + tabulation, +) from populace_dynamics.uniform_cut_track_u2.cohort import U2CohortSpec from populace_dynamics.uniform_cut_track_u2.estimator import U2IncomeSpec @@ -46,11 +67,16 @@ "REGISTERED_ROWS", "ROW_IDS", "SENSITIVITIES_UNSCORED", + "SPECIFICATION_STATUS", + "SPECIFICATION_VERSION", "U2_RULINGS", "U2Row", + "check_block_against_code", "check_named_deltas_against_specification", "check_rows_against_block", + "check_rulings_against_code", "check_u2_rulings_against_block", + "expected_specification_block", "named_deltas_in_specification", "specification_block", ] @@ -271,9 +297,90 @@ def as_dict(self) -> dict[str, Any]: "identity is preserved", ) -#: U2's own rulings record (d514; specification header and section 20 -#: step 1: Max approved the section 16 defaults and ratification). The -#: keys follow section 16's decision table and section 15's identity. +#: The specification's version and status once section 20 step 5 +#: materialized the final block (ratified by merge, section 20 step 6). +SPECIFICATION_VERSION = "u2-ratified-1" +SPECIFICATION_STATUS = "ratified_frozen" +#: U1's ratified specification version, which U2 inherits (section 15 +#: ``inherits``; the U1 block's ``version``). +_U1_VERSION = "u1-ratified-1" +#: The structure-only U1 load's pin (section 13; d1597). +_U1_INPUT_FRAMES_SHA256 = ( + "43c41f444128ecf72ce9f8c5e64a7cda61a6fccaa2a58ff60ae7ce91356c4271" +) +#: The two section 20 step-4 passes (section 20 step 4; section 16d). +_STEP_4_FIRST_HEAD = "640adccf84f9f68c0fb893a834cfdfaca20e7888" +_STEP_4_SECOND_HEAD = "1e739d328b73c99d259a72952b3452546721bfb8" +_STEP_4_SECOND_TESTS_RUN = 39502262845 + + +def _ruled(scope: Mapping[str, Any], **record: Any) -> dict[str, Any]: + """A rulings-record entry for one registry scope of a ruling. + + ``scope`` is a :data:`~populace_dynamics.data.u2_source_registry. + RATIFIED_AMENDMENTS` value: the entry names the registry entries the + ruling governs and copies the ruling's decision, date, section and + any confirmation or draft-3 lines exactly as the registry validator + holds them. + """ + + ruling = scope["ruling"] + entry = { + **record, + "registry_entries": [ + f"{scope['registry']}:{entry}" for entry in scope["entries"] + ], + "specification_section": ruling["specification_section"], + "decision_record": ruling["decision"], + "ruled_on": ruling["date"], + } + for key in ("confirmed_by", "confirmed_on", "option"): + if key in ruling: + entry[key] = ruling[key] + for key in ("amends_draft_3_lines", "replaces_draft_3_lines"): + if key in ruling: + entry[key] = list(ruling[key]) + return entry + + +def _excluded(disposition: str, **record: Any) -> dict[str, Any]: + """A rulings-record entry for one family-unit exclusion (d1090). + + Built from :data:`~populace_dynamics.data.u2_source_registry. + RATIFIED_EXCLUSIONS`, the scope the registry validator holds each role + entry to. + """ + + scope = _registry.RATIFIED_EXCLUSIONS[disposition] + ruling = scope["ruling"] + entry = { + **record, + "disposition": disposition, + "excluded_from": "every_row", + "member_condition": ( + None if scope["condition"] is None else dict(scope["condition"]) + ), + "registry_entries": [f"roles:{entry}" for entry in scope["entries"]], + "option": ruling["option"], + "amends_draft_3_lines": list(ruling["amends_draft_3_lines"]), + "specification_section": ruling["specification_section"], + "decision_record": ruling["decision"], + "ruled_on": ruling["date"], + } + if "amended_waves" in ruling: + entry["amended_waves"] = list(ruling["amended_waves"]) + return entry + + +_AMENDMENTS = _registry.RATIFIED_AMENDMENTS + +#: U2's own rulings record. The first eleven entries are d514 +#: (specification header and section 20 step 1: Max approved the section +#: 16 defaults and ratification); their keys follow section 16's decision +#: table and section 15's identity. The later entries record d637 and its +#: confirmation d745 (section 16b), d1090 (section 16c), d1242, d1597, +#: d1649 and d1724 (section 16d); section 20 step 5 materialized them in +#: the section 15 block's ``decisions``. U2_RULINGS: dict[str, dict[str, Any]] = { "claim_class": { "ruling": "track_u_psid_realized_measurement_not_a_projection", @@ -335,8 +442,192 @@ def as_dict(self) -> dict[str, Any]: "u1_rulings": "precedent_not_u2_execution_authorization", "decision_record": "d514", }, + # d637 (2026-09-30) and d745 (2026-10-12): section 16b. + "amendment_1a_documented_routes": _ruled( + _AMENDMENTS["1a"], + ruling="ratified_as_written", + registration_discloses=( + "ofum_route_documented_reporting_completeness_for_withheld_" + "answers_not_documented" + ), + ), + "amendment_1b_2013_inherited_meanings": _ruled( + _AMENDMENTS["1b"], ruling="ratified_as_written" + ), + "amendment_1c_first_year_cohabitor": _ruled( + _AMENDMENTS["1c"], + ruling="ratified_as_written", + convention=( + "ofum_family_basis_outside_the_spouse_slot_no_legal_spouse_" + "life_no_marital_resolution_inherited_birth_support" + ), + ), + "amendment_2_annuitant_input_precedence": _ruled( + _AMENDMENTS["2"], + ruling="ratified_as_written", + rule=( + "recorded_sex_er32000_and_derived_income_year_age_family_file_" + "spouse_age_and_sex_slot_metadata_only" + ), + ), + "amendment_3_spouse_retirement_crosswalk": _ruled( + _AMENDMENTS["3"], + ruling="ratified_as_written", + rule="non_executable_metadata_components_counted_once_in_money_income", + ), + "amendment_4_wealth_accuracy_flags": _ruled( + _AMENDMENTS["4"], + ruling="ratified_as_written", + rule="imputation_diagnostics_only_never_amounts_or_sample_inclusion", + ), + "amendment_5a_later_pension_routes": _ruled( + _AMENDMENTS["5a"], + ruling="ratified_as_written", + previous_routes="income_concept.employer_dc.previous_routes", + ), + "amendment_5b_2015_checkpoint": _ruled( + _AMENDMENTS["5b"], + ruling="ratified_as_written", + refused_branch=_AMENDMENTS["5b"]["fields"]["refused_branch"], + off_route_disposition=_AMENDMENTS["5b"]["fields"][ + "off_route_disposition" + ], + ), + "amendment_6_blocker_b3_2017_weight": _ruled( + _AMENDMENTS["6"], + ruling="use_the_released_weight_and_disclose", + weight=_AMENDMENTS["6"]["fields"]["variable"], + registration_discloses=( + "weights:2017.cross_section_weight.registration_disclosure" + ), + ), + "psid_documentary_questions": { + "ruling": "send_all_three_now_as_a_held_draft_for_max", + "sent_on": "2026-09-30", + "answered_on": "2026-10-07", + "research_record": "docs/design/u2_m1b_psid_research.md", + "specification_section": "16b", + "decision_record": "d637", + "ruled_on": "2026-09-30", + }, + # d1090 (2026-10-10): section 16c. + "blocker_b1_male_code_20_2015": _excluded( + sources.MALE_CODE_20_2015, + ruling="exclude_and_disclose", + supersedes="d637_hold_for_psid", + disclosed_count=( + "persons_recorded_male_with_2015_code_20_in_family_units_that_" + "would_otherwise_supply_an_observation" + ), + count_decides="nothing", + ), + "blocker_b2_codes_90_92_2019_2023": _excluded( + sources.UNCOOPERATIVE_SPOUSE_OR_PARTNER, + ruling="exclude_and_disclose", + supersedes="d637_hold_for_psid", + disclosed_count=( + "persons_with_code_90_or_92_in_2019_2021_2023_family_units_" + "that_would_otherwise_supply_an_observation" + ), + count_decides="nothing", + ), + "amendment_5c_u7_2015_spouse_slot": { + "ruling": "follows_blocker_b1", + "registry_entries": ["pension:2015.route.respondent_slots"], + "option": "1d-2", + "specification_section": "16c", + "decision_record": "d1090", + "ruled_on": "2026-10-10", + }, + # d1242 (2026-10-16): recorded in section 19. + "b2_disclosed_count": { + "ruling": ( + "persons_with_code_90_or_92_in_2019_2021_2023_family_units_that_" + "would_otherwise_supply_an_observation" + ), + "person_counted_exclusions": list(sources.PERSON_COUNTED_EXCLUSIONS), + "disclosed_per_row": ["per_wave", "distinct_persons"], + "count_decides": "nothing", + "specification_section": "19", + "decision_record": "d1242", + "ruled_on": "2026-10-16", + }, + # d1597 (2026-10-16): section 13's optional structure-only U1 load. + "u1_input_pin_check": { + "ruling": "run_the_optional_structure_only_u1_load_once", + "u1_input_frames_sha256": _U1_INPUT_FRAMES_SHA256, + "statistic_computed": None, + "authorization_used": { + "on": "2026-10-21", + "head": _STEP_4_SECOND_HEAD, + "result": "pin_matched", + }, + "specification_section": "13", + "decision_record": "d1597", + "ruled_on": "2026-10-16", + }, + # d1649 (2026-10-16): the section 20 step-4 pass. + "preregistration_pass": { + "ruling": "authorize_the_step_4_pass_once", + "scope": [ + "structural_counts", + "dispositions", + "join_and_reconciliation_counts", + "named_f17_component_summaries", + ], + "not_computed": [ + "income_concept", + "poverty_threshold_assignment", + "poverty_status", + ], + "registered_run_frame_environment": ( + "the_preregistration_evidence_pandas_version_and_string_" + "inference_setting" + ), + "authorization_used": { + "on": "2026-10-19", + "head": _STEP_4_FIRST_HEAD, + "result": "stopped_in_the_birth_law_without_output", + }, + "specification_section": "20", + "decision_record": "d1649", + "ruled_on": "2026-10-16", + }, + # d1724 (2026-10-19; widening approved 2026-10-20): section 16d. + "seed_birth_year_support": { + "ruling": "count_out_or_reseed_before_the_birth_law_then_rerun_once", + "support": [career.DERIVED_BIRTH_MIN, career.DERIVED_BIRTH_MAX], + "counted_out": list(cohort.SEED_BIRTH_COUNTED_OUT), + "disclosure_keys": [ + "n_seed_birth_year_counted_out", + "n_seed_birth_year_reseeded", + ], + "count_decides": "nothing", + "estimates_career": "unchanged", + "widening_approved_on": "2026-10-20", + "authorization_used": { + "on": "2026-10-21", + "head": _STEP_4_SECOND_HEAD, + "tests_run": _STEP_4_SECOND_TESTS_RUN, + "result": "completed", + }, + "specification_section": "16d", + "decision_record": "d1724", + "ruled_on": "2026-10-19", + }, } _RULED_ON = "2026-09-28" +#: Every decision the rulings record must cover (section 20 step 5). +RULING_DECISIONS: tuple[str, ...] = ( + "d514", + "d637", + "d745", + "d1090", + "d1242", + "d1597", + "d1649", + "d1724", +) _BLOCK_SECTION = "\n## 15. Machine-readable parameter block" @@ -477,6 +768,512 @@ def check_u2_rulings_against_block( return {"rulings_checked": sorted(U2_RULINGS), "rulings_equal": True} +def check_rulings_against_code( + registries: sources.RegistrySet | None = None, +) -> dict[str, Any]: + """Hold :data:`U2_RULINGS` to the code and the committed registries. + + Every decision in :data:`RULING_DECISIONS` has an entry and no entry + names another; each registry entry a ruling names exists, is RESOLVED + and records that ruling's decision (and option, for d1090); the d637 + entries are exactly the registry module's ratified scopes, and each + d1090 exclusion names exactly the role entries that carry it in the + committed roles registry; B2's disclosed count is a person count the + structural pass makes (d1242); and the d1724 support and reasons are + the cohort code's. Refuses on the first difference. + """ + + registries = ( + sources.RegistrySet.committed() if registries is None else registries + ) + named = sorted( + {entry["decision_record"] for entry in U2_RULINGS.values()} + | { + entry["confirmed_by"] + for entry in U2_RULINGS.values() + if "confirmed_by" in entry + }, + key=lambda d: int(d[1:]), + ) + if named != list(RULING_DECISIONS): + raise ValueError( + f"the rulings record names {named}, not {list(RULING_DECISIONS)}" + ) + by_id = { + name: {entry["id"]: entry for entry in registries.entries(name)} + for name in _registry.REGISTRY_NAMES + } + for key, entry in U2_RULINGS.items(): + for reference in entry.get("registry_entries", ()): + name, entry_id = reference.split(":", 1) + found = by_id.get(name, {}).get(entry_id) + if found is None or found.get("status") != "RESOLVED": + raise ValueError( + f"{key}: {reference} is not a RESOLVED registry entry" + ) + recorded = found.get("ruling") or {} + if recorded.get("decision") != entry["decision_record"]: + raise ValueError( + f"{key}: {reference} records " + f"{recorded.get('decision')!r}, not " + f"{entry['decision_record']!r}" + ) + if "option" in entry and recorded.get("option") != entry["option"]: + raise ValueError(f"{key}: {reference} records another option") + applied = sorted( + reference + for entry in U2_RULINGS.values() + if entry["decision_record"] == "d637" + for reference in entry.get("registry_entries", ()) + ) + scoped = sorted( + f"{scope['registry']}:{entry}" + for scope in _AMENDMENTS.values() + for entry in scope["entries"] + ) + if applied != scoped: + raise ValueError( + "the d637 entries differ from the registry's ratified scopes: " + f"{sorted(set(applied) ^ set(scoped))}" + ) + for disposition in sources.FAMILY_UNIT_EXCLUSIONS: + entry = next( + e + for e in U2_RULINGS.values() + if e.get("disposition") == disposition + ) + carrying = sorted( + f"roles:{role['id']}" + for role in registries.entries("roles") + if (role.get("family_unit_exclusion") or {}).get("disposition") + == disposition + ) + if carrying != sorted(entry["registry_entries"]): + raise ValueError( + f"{disposition!r}: the roles registry carries it on " + f"{carrying}, the rulings record on {entry['registry_entries']}" + ) + if set(sources.PERSON_COUNTED_EXCLUSIONS) != set( + sources.FAMILY_UNIT_EXCLUSIONS + ) or U2_RULINGS["b2_disclosed_count"]["person_counted_exclusions"] != ( + list(sources.PERSON_COUNTED_EXCLUSIONS) + ): + raise ValueError("B2's disclosed person count is not made (d1242)") + support = U2_RULINGS["seed_birth_year_support"] + if support["support"] != [ + career.DERIVED_BIRTH_MIN, + career.DERIVED_BIRTH_MAX, + ] or support["counted_out"] != list(cohort.SEED_BIRTH_COUNTED_OUT): + raise ValueError("the d1724 support or reasons differ from the code") + return { + "decisions": list(RULING_DECISIONS), + "entries": len(U2_RULINGS), + "rulings_equal_the_code": True, + } + + +def _relative(path: Path) -> str: + return str(Path(path).resolve().relative_to(identity.ROOT)) + + +def _registry_variable( + entries: Mapping[str, Mapping[str, Any]], entry_id: str +) -> str: + entry = entries[entry_id] + if entry.get("status") != "RESOLVED": + raise ValueError(f"{entry_id} is not RESOLVED") + return str(entry["variable"]) + + +def _block_row(row: U2Row) -> dict[str, Any]: + """A row as the section 15 block writes it (the inverse of + :func:`_parse_block_row`).""" + + inverse = { + (target, name, override): (key, value) + for (key, value), (target, name, override) in ( + _BLOCK_KEY_OVERRIDES.items() + ) + } + entry: dict[str, Any] = {} + for target, overrides in (("cohort", row.cohort), ("income", row.income)): + for name, value in overrides.items(): + key, written = inverse.get((target, name, value), (name, value)) + entry[key] = written + return entry + + +def expected_specification_block( + registries: sources.RegistrySet | None = None, +) -> dict[str, Any]: + """The complete section 15 block as the code states it. + + Every value the code holds comes from the code: the identity, plans, + support waves and their registry variables, the income-concept, + annuity, threshold, cut and SSI parameters and their capture pins, + the cells, floor and design settings, the memo rules, the rows, the + entry points, the labels and U2's rulings record. The descriptive + values the code does not otherwise hold (the Report's citation, the + unit and financial-asset definitions, the comparator boundary, the + cleared extracts' hashes and the wording keys) are written here once, + so that :func:`check_block_against_code` holds the whole block, not + only its executable part. + """ + + registries = ( + sources.RegistrySet.committed() if registries is None else registries + ) + individual = {e["id"]: e for e in registries.entries("individual")} + wealth = {e["id"]: e for e in registries.entries("wealth")} + design = {e["id"]: e for e in registries.entries("design")} + pension = registries.entries("pension") + if any(entry.get("status") != "RESOLVED" for entry in pension): + raise ValueError("a pension registry entry is not RESOLVED") + spec = U2IncomeSpec() + population = U2CohortSpec() + tabulated = tabulation.U2TabulationConfig().as_dict() + refused = _AMENDMENTS["5b"] + (refused_wave,) = { + entry["wave"] for entry in pension if entry["id"] in refused["entries"] + } + reader = inspect.getsourcefile(sources.employer_dc_balances) + return { + "specification": identity.SPECIFICATION_ID, + "target_id": identity.TARGET_ID, + "version": SPECIFICATION_VERSION, + "status": SPECIFICATION_STATUS, + "inherits": { + "file": _relative(identity.U1_SPECIFICATION_PATH), + "version": _U1_VERSION, + "sha256": identity.U1_SPECIFICATION_SHA256, + }, + "claim_class": {"class": U2_RULINGS["claim_class"]["ruling"]}, + "target": { + "report": ( + "Butrica and Uccello (2004), How Will Boomers Fare at " + "Retirement?, AARP PPI #2004-05" + ), + "tables": ["19", "21"], + "column": identity.COMPARATOR_COLUMN, + "birth_years": [identity.BIRTH_YEARS[0], identity.BIRTH_YEARS[-1]], + "age": identity.TARGET_AGE, + "cut_rate": spec.cut_rate, + "report_rows": len(tabulation.REPORT_ROWS) + + sum( + len(entry["rows"]) + for entry in tabulation.NOT_COMPUTED_REPORT_ROWS.values() + ), + "unit": "individual_with_household_income_and_wealth_including_" + "spouse", + "financial_assets": "non_pension_wealth_plus_ira_keogh_401k_" + "balances", + "comparator_values": "sealed_comparator_side_not_opened_by_" + "builder", + "printed_precision": "whole_numbers", + "definitions_extract": [ + { + "file": "EVID/exercise2-definitions-cleared-20260924.md", + "sha256": "a3978b683b4275424b6d12e9fe45f021fae277ccf9" + "731a7883952564b6ed0384", + }, + { + "file": "EVID/u2-target-availability-cleared-20260928.md", + "sha256": "2ffa3092d297418c58868f7e5326c3e4c8047b1291" + "71b40b66fde5ee07702211", + }, + ], + }, + "population": { + "primary_row": HEADLINE_ROW, + "headline": {"rule": HEADLINE_RULE, "fallback_row": None}, + "support_waves": list(cohort.SUPPORT_WAVES), + "primary_waves": list(cohort.PRIMARY_WAVES), + "primary_birth_years": list(cohort.PRIMARY_BIRTH_YEARS), + "u1_birth_years": [ + cohort.ALL_BIRTH_YEARS[0], + cohort.ALL_BIRTH_YEARS[-1], + ], + "u1_even_birth_year_weight": cohort.U1_EVEN_BIRTH_YEAR_WEIGHT, + "u1_single_observation_weight": ( + cohort.U1_SINGLE_OBSERVATION_WEIGHT + ), + "u1_even_birth_ages": list(cohort.U1_EVEN_BIRTH_AGES), + "missing_observation_reweighting": ( + population.missing_observation_reweighting + ), + "waves": { + str(wave): { + "income_year": wave - 1, + "weight": _registry_variable(individual, f"{wave}.weight"), + "family_unit_id": _registry_variable( + individual, f"{wave}.interview" + ), + "wealth1": _registry_variable( + wealth, f"wealth.{wave}.wealth1" + ), + "wealth_source": "family_file", + } + for wave in cohort.SUPPORT_WAVES + }, + "presence": population.presence, + "birth_year_law": ( + f"estimates.career.{career.derive_birth_years.__name__}" + ), + "seed_wave_rule": population.seed_wave_rule, + "separated_is_married": population.separated_is_married, + "unresolved_marital_status": population.unresolved_marital_status, + "annuitant_age_source": population.annuitant_age_source, + "institution_income_rule": population.institution_income_rule, + "relationship_rules": ( + "section_3_role_amendment_with_section_16a_amendment_1" + ), + "relationship_codes_tested": list(sources.RELATIONSHIP_CODES), + "design": { + "stratum": _registry_variable(design, "common.stratum"), + "cluster": _registry_variable(design, "common.cluster"), + }, + }, + "income_concept": { + "income_unit": spec.income_unit, + "money_income": "TOTAL FAMILY INCOME", + "asset_income_rule": spec.asset_income_rule, + "asset_income_items": list(family_income.ASSET_INCOME_CONCEPTS), + "retirement_account_income_rule": ( + spec.retirement_account_income_rule + ), + "retirement_account_income_items": list( + ap.RETIREMENT_ACCOUNT_INCOME_CONCEPTS + ), + "farm_asset_share": spec.farm_asset_share, + "farm_loss": ap.FARM_LOSS_RULE, + "financial_assets": spec.financial_assets, + "employer_dc": { + "row": "U7", + "reader": str( + Path(reader).relative_to( + identity.ROOT / "src" / "populace_dynamics" + ) + ), + "persons": ["head", "wife"], + "role_meaning": ( + "reference_person_and_designated_spouse_income_slot" + ), + "current_job": "account_amount_when_plan_type_has_an_account", + "previous_plans": [1, 2], + "previous_routes": { + "both_items": ["both"], + "account_items_by_wave": { + str(wave): list(items) + for wave, items in ( + sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS.items() + ) + }, + "refused_branches": { + str(refused_wave): refused["fields"]["refused_branch"] + }, + }, + "unreported_amount": "zero_counted", + "route_source": "later_wave_questionnaire_adjudication", + "later_wave_mapping_status": ( + "resolved_in_the_pension_registry_under_amendment_5" + ), + "counted_disposition": "left_to_accumulate", + "excluded": [ + "rolled_over_into_ira", + "both_plan_account_items_reasked", + refused["fields"]["off_route_disposition"], + ], + "top_code": "as_recorded", + "brackets": "not_used", + }, + "annuitized_share": spec.annuitized_share, + "annuity": { + "real_interest_rate": spec.real_interest_rate, + "timing": spec.annuity_timing, + "load": spec.annuity_load, + "survivor_share": spec.survivor_share, + "mortality_basis": spec.mortality_basis, + "terminal_closure": spec.terminal_closure, + "lives": spec.annuity_lives, + "negative_wealth": spec.negative_wealth_rule, + }, + "sensitivities_unscored": { + key: list(value) + for key, value in SENSITIVITIES_UNSCORED.items() + }, + }, + "threshold": { + "rule": spec.threshold_rule, + "years": [ + parameters.REQUIRED_INCOME_YEARS[0], + parameters.REQUIRED_INCOME_YEARS[-1], + ], + "required_income_years": list(parameters.REQUIRED_INCOME_YEARS), + "values": "weighted_averages_as_printed_in_thresh_workbooks", + "capture_status": "captured", + "capture": { + "file": _relative(parameters.THRESHOLDS_PATH), + "sha256": parameters.THRESHOLDS_SHA256, + }, + "overlap_2012": "exact_equality_with_u1_capture", + "poor_if": "income_below_threshold", + }, + "cut": { + "rate": spec.cut_rate, + "base": "all_social_security_of_the_unit", + "behavior": "none", + "start_year": spec.cut_start_year, + "start_year_rule": ap.CUT_START_YEAR_RULE, + }, + "ssi": { + "rule": spec.ssi_rule, + "deeming": spec.ssi_deeming, + "ofum_unit": spec.ofum_ssi_unit, + "parameters": { + "file": _relative(parameters.SSI_PATH), + "sha256": parameters.SSI_SHA256, + "years": list(parameters.SSI_YEARS), + "checked_against": "section_8_federal_register_cola_notices", + "revision_rule": "refuse_missing_empty_or_unknown", + "overlap_2012": "all_seven_parameters_equal_u1", + }, + }, + "statistic": { + "headline": "delta", + "secondary": ["baseline_rate", "reform_rate"], + "unit": "percentage_points", + }, + "cells": { + "column": identity.COMPARATOR_COLUMN, + "scored": list(tabulation.DEFAULT_CELLS), + "secondary": list(tabulation.OPTIONAL_CELLS), + "diagnostic": "birth_year", + "report_labels": "tables_19_21_stub_labels_cleared_extract", + "unclassified_marital_cells": ( + tabulated["unclassified_marital_cells"] + ), + "not_computed": list(tabulation.NOT_COMPUTED_REPORT_ROWS), + }, + "uncertainty": { + "draws": tabulated["draws"], + "floor": { + "seeds": tabulated["floor_seeds"], + "fraction": ut.FLOOR_FRACTION, + "split_unit": ut.FLOOR_SPLIT_UNIT, + "summary": ["mean", "sample_sd"], + "min_usable_seeds": ut.MIN_FLOOR_SEEDS, + }, + "design_se": { + "method": "taylor_linearization", + "domain": tabulated["design_se_domain"], + "outside_cell": "zero", + "singleton_strata": "excluded_counted_and_listed", + }, + }, + "comparison": { + "gap": "model_minus_report", + "comparator_interval": memo.INTERVAL_ID, + "acceptance": { + "rule": U2_RULINGS["comparison_publication"]["acceptance_rule"] + }, + "memo_small_cells": { + "flag_unweighted_n_below": memo.SMALL_CELL_THRESHOLD, + "unweighted_n": "n_observations", + "no_switcher_cell": memo.NO_SWITCHER_WORDING.replace(" ", "_"), + }, + }, + "rows": { + row_id: _block_row(row) for row_id, row in REGISTERED_ROWS.items() + }, + "diagnostics_f17": { + "row": "headline", + "official_concept_poverty_rate": "registered_run_only", + "components": [ + "social_security", + "ssi", + "wealth1", + "fu_size_record_counts", + ], + "component_timing": "permitted_before_registration", + "social_security_published": ( + "ssa_supplement_2025_table_5a4_december_retired_workers" + ), + "ssi_published": "federal_benefit_rate_capture_only", + "wealth1_published": None, + }, + "entry_points": { + "dry_run": "scripts/track_u2_dry_run.py", + "registered_run": "scripts/run_track_u2_registered.py", + "registered_artifact": _relative(identity.ARTIFACT_PATH), + "environment": _relative(identity.SIDECAR_PATH), + "structure": "scripts/track_u2_structure.py", + "component_diagnostics": ( + "scripts/track_u2_component_diagnostics.py" + ), + }, + "acceptance_rule": None, + "labels": list(ap.OUTPUT_LABELS), + "decisions": {"ruled_by": "Max", "ruled_on": _RULED_ON, **U2_RULINGS}, + "blocked_by": [], + } + + +def _differing_paths( + observed: Any, expected: Any, path: str = "" +) -> list[str]: + """The paths at which ``observed`` and ``expected`` differ, comparing + keys, list lengths, types and values, each leaf as canonical JSON + (``True`` is not ``1``, ``1`` is not ``1.0`` and ``-0.0`` is not + ``0.0``).""" + + if isinstance(expected, Mapping) and isinstance(observed, Mapping): + found: list[str] = [] + for key in sorted(set(observed) | set(expected), key=str): + where = f"{path}.{key}" if path else str(key) + if key not in observed or key not in expected: + found.append(where) + else: + found += _differing_paths(observed[key], expected[key], where) + return found + if isinstance(expected, list) and isinstance(observed, list): + if len(observed) != len(expected): + return [path] + found = [] + for index, (a, b) in enumerate(zip(observed, expected, strict=True)): + found += _differing_paths(a, b, f"{path}[{index}]") + return found + if type(observed) is not type(expected) or observed != expected: + return [path] + if json.dumps(observed) != json.dumps(expected): + return [path] + return [] + + +def check_block_against_code( + block: Mapping[str, Any], + registries: sources.RegistrySet | None = None, +) -> dict[str, Any]: + """Refuse a section 15 block that differs from the code in any way. + + The block must equal :func:`expected_specification_block` exactly: + the same keys at every level, the same list lengths and the same + values of the same JSON types (equal under ``==`` and as canonical + JSON). The refusal names every differing path. + """ + + expected = expected_specification_block(registries) + differing = _differing_paths(block, expected) + if differing or _canonical(block) != _canonical(expected): + raise ValueError( + "the section 15 block differs from the code at " + f"{differing or ['(canonical encoding)']}" + ) + return { + "specification_version": block["version"], + "block_equals_code": True, + } + + _DELTA = re.compile(r"^- \*\*D(\d{2}):\*\* (.+)$", re.MULTILINE) diff --git a/src/populace_dynamics/uniform_cut_track_u2/sources.py b/src/populace_dynamics/uniform_cut_track_u2/sources.py index 83c7e7e05..b6f4fc01a 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/sources.py +++ b/src/populace_dynamics/uniform_cut_track_u2/sources.py @@ -374,11 +374,12 @@ #: the route inherited from U1; 2015-2023 admit account plans (P46 = 5) #: only, so a formula, combined, DK, refused or NA plan's account items #: are off-route, and an amount reached only through 2015's refused -#: P62ACKPT branch is too (5b). Where a ratified amendment and sections -#: 3-15 differ the amendment governs (section 16b); section 15's own text -#: keeps draft 3's wording until section 20 step 5 materializes the final -#: parameter block. ``tests/track_u2/test_u2_employer_dc.py`` holds this -#: table equal to the fragment in section 16a. +#: P62ACKPT branch is too (5b). Section 20 step 5 materialized the +#: fragment in section 15's block, which +#: :func:`~populace_dynamics.uniform_cut_track_u2.rows. +#: check_block_against_code` holds equal to this table; +#: ``tests/track_u2/test_u2_employer_dc.py`` also holds it equal to the +#: fragment in section 16a. DECLARED_PREVIOUS_ACCOUNT_ITEMS: dict[int, tuple[str, ...]] = { 2013: ("account", "formula", "dk"), 2015: ("account",), diff --git a/tests/track_u2/test_adjudication_applied.py b/tests/track_u2/test_adjudication_applied.py index e3df6e62a..4abfc6ae7 100644 --- a/tests/track_u2/test_adjudication_applied.py +++ b/tests/track_u2/test_adjudication_applied.py @@ -1004,7 +1004,7 @@ def test_specification_revision_keeps_cited_lines(documents): ) lines = raw.split(b"\n") draft_3 = draft_3_bytes(record).split(b"\n") - assert lines[2].startswith(b"- **Version:** `u2-draft-4`") + assert lines[2].startswith(b"- **Version:** `u2-ratified-1`") assert draft_3[2].startswith(b"- **Version:** `u2-draft-3`") cited = { citation["line"] @@ -1020,19 +1020,38 @@ def test_specification_revision_keeps_cited_lines(documents): assert lines[line - 1].strip(), line digest = hashlib.sha256(lines[line - 1]).hexdigest() assert digest == record["cited_lines_sha256"][str(line)], line - # §16a is inserted where draft 3 had §17, so every earlier line keeps its - # draft-3 number and text except the version, status and §15 version. + # Every line before §15 keeps its draft-3 number and text except the + # version and status lines. + section_15 = lines.index(b"## 15. Machine-readable parameter block") + assert draft_3[section_15] == lines[section_15] + assert { + number + for number in range(1, section_15 + 1) + if lines[number - 1] != draft_3[number - 1] + } == {3, 4} + # §20 step 5 materialized §15's block. Through line 1185, the last line + # a registry cites, every line keeps its draft-3 number, and only the + # lines the materialization wrote differ: the two lines of prose, the + # version and status, the role rules' name, the employer-DC reader and + # amendment 5's fragment between the cited lines 1173 and 1185. + assert max(cited) == 1185 + assert { + number + for number in range(section_15 + 1, max(cited) + 1) + if lines[number - 1] != draft_3[number - 1] + } == {1039, 1041, 1047, 1048, 1145, 1168, *range(1175, 1185)} + # §16, unchanged since draft 3, follows the longer block, and §16a stands + # where draft 3's §17 would stand after it. + heading_16 = b"## 16. Decisions and explicit departures from U1" + section_16 = lines.index(heading_16) section_16a = lines.index( b"## 16a. Proposed amendments for `u2-draft-4` (pending Max's ruling)" ) - assert draft_3[section_16a].startswith(b"## 17. ") - changed = { - number - for number in range(1, section_16a + 1) - if lines[number - 1] != draft_3[number - 1] - } - assert changed == {3, 4, 1047} - assert cited < set(range(1, section_16a + 1)) + draft_16 = draft_3.index(heading_16) + draft_17 = draft_16 + (section_16a - section_16) + assert draft_3[draft_17].startswith(b"## 17. ") + assert lines[section_16:section_16a] == draft_3[draft_16:draft_17] + assert cited <= set(range(1, section_16 + 1)) def test_section_16a_quotes_draft_3_verbatim(): @@ -1319,38 +1338,47 @@ def test_section_16a_recommendations_and_options(): #: db735e5). D1090_CLAUSES = { "header_version": [ - "- **Version:** `u2-draft-4` (milestone 1b, 2026-09-28; revised " - "2026-09-29 after the independent review; Max's rulings recorded " - "2026-09-30, 2026-10-10 and 2026-10-12): proposed amendments " + "- **Version:** `u2-ratified-1` (\N{SECTION SIGN}20 step 5, " + "2026-10-21): \N{SECTION SIGN}15 holds the final parameter block and " + "U2's rulings record, and the merge of this revision ratifies the " + "specification under d514 (\N{SECTION SIGN}20 step 6). Previous " + "version: `u2-draft-4` (milestone 1b, 2026-09-28; revised 2026-09-29 " + "after the independent review; Max's rulings recorded 2026-09-30, " + "2026-10-10, 2026-10-12 and 2026-10-19): proposed amendments " "1\N{EN DASH}6 in \N{SECTION SIGN}16a, ruled by Max on 2026-09-30 " "(d637) as recorded in \N{SECTION SIGN}16b, with blockers B1 and B2 " "ruled on 2026-10-10 (d1090) as recorded in \N{SECTION SIGN}16c, " - "and amendment 1's parts 1a\N{EN DASH}1c confirmed on 2026-10-12 " - "(d745) as recorded in \N{SECTION SIGN}16b. Previous version: " - "`u2-draft-3` (round 2).", + "amendment 1's parts 1a\N{EN DASH}1c confirmed on 2026-10-12 (d745) " + "as recorded in \N{SECTION SIGN}16b, and the birth law's support " + "ruled on 2026-10-19 (d1724) as recorded in \N{SECTION SIGN}16d. " + "Before it, `u2-draft-3` (round 2).", ], "header_status": [ - "- **Status:** decisions ruled for `u2-draft-3`. On 2026-09-28 Max " - "approved the \N{SECTION SIGN}16 defaults and ratification of that " - "draft (d514, in chat). Per \N{SECTION SIGN}20, the version becomes " - "`u2-ratified-1` by the merge that completes \N{SECTION SIGN}20 " - "steps 2\N{EN DASH}5 and materializes the final parameter block; " - "that merge needs no further ruling unless it changes this " - "document's substance. `u2-draft-4` adds \N{SECTION SIGN}16a, whose " - "proposed amendments change substance, so each needed Max's ruling " - "first. He ruled on 2026-09-30 (d637; \N{SECTION SIGN}16b): B3 is " - "resolved by option 1, amendments 2\N{EN DASH}5 are ratified as " - "written, and blockers B1 and B2 were held until PSID answered. " - "PSID answered on 2026-10-07, and on 2026-10-10 he ruled on B1 and " - "B2 (d1090; \N{SECTION SIGN}16c): B1 takes option 2 and B2 option " - "1, each an exclusion under a named disposition, ratified as " - "amendments to draft-3 line 160 (B1) and to draft-3 lines 152, 154 " - "and 158 for 2019\N{EN DASH}2023 (B2). No blocker hold remains. " - "Amendment 1's parts 1a\N{EN DASH}1c, which the d637 ruling's list " - "does not name, were read as ratified pending Max's confirmation, " - "and on 2026-10-12 he confirmed that d637 ratified them as written " - "(d745; \N{SECTION SIGN}16b). Not registered or authorized for " - "execution.", + "- **Status:** ratified and frozen by the merge of this revision " + "(\N{SECTION SIGN}20 step 6). Decisions were ruled for `u2-draft-3`: " + "on 2026-09-28 Max approved the \N{SECTION SIGN}16 defaults and " + "ratification of that draft (d514, in chat). Per \N{SECTION SIGN}20, " + "the version becomes `u2-ratified-1` by the merge that completes " + "\N{SECTION SIGN}20 steps 2\N{EN DASH}5 and materializes the final " + "parameter block; that merge needs no further ruling unless it " + "changes this document's substance, and \N{SECTION SIGN}20 step 5 " + "records why this revision changes none. `u2-draft-4` adds " + "\N{SECTION SIGN}16a, whose proposed amendments change substance, so " + "each needed Max's ruling first. He ruled on 2026-09-30 (d637; " + "\N{SECTION SIGN}16b): B3 is resolved by option 1, amendments " + "2\N{EN DASH}5 are ratified as written, and blockers B1 and B2 were " + "held until PSID answered. PSID answered on 2026-10-07, and on " + "2026-10-10 he ruled on B1 and B2 (d1090; \N{SECTION SIGN}16c): B1 " + "takes option 2 and B2 option 1, each an exclusion under a named " + "disposition, ratified as amendments to draft-3 line 160 (B1) and to " + "draft-3 lines 152, 154 and 158 for 2019\N{EN DASH}2023 (B2). No " + "blocker hold remains. Amendment 1's parts 1a\N{EN DASH}1c, which the " + "d637 ruling's list does not name, were read as ratified pending " + "Max's confirmation, and on 2026-10-12 he confirmed that d637 " + "ratified them as written (d745; \N{SECTION SIGN}16b). On 2026-10-19 " + "he ruled on the birth law's support (d1724; \N{SECTION SIGN}16d), " + "and the \N{SECTION SIGN}20 step-4 pass completed under that ruling " + "on 2026-10-21. Not registered or authorized for execution.", ], "record": [ "## 16c. Max's ruling on blockers B1 and B2 (2026-10-10, d1090)", @@ -1514,11 +1542,17 @@ def test_section_16a_recommendations_and_options(): "record remains, and the loader's source preflight passes; the " "structural pass still needs Max's authorization (item 8; " "\N{SECTION SIGN}20 step 4).", - "8. Complete the pre-registration structural, reconciliation and " - "F17 component pass, including only the counts that the ratified B1 " - "and B2 options name (under the 2026-10-10 ruling, the counts " - "specified by \N{SECTION SIGN}16a options 1d-2 and 1e-1, each " - "disclosed and deciding nothing; \N{SECTION SIGN}16c).", + "8. Complete the pre-registration structural, reconciliation and F17 " + "component pass, including only the counts that the ratified B1 and " + "B2 options name (under the 2026-10-10 ruling, the counts specified " + "by \N{SECTION SIGN}16a options 1d-2 and 1e-1, each disclosed and " + "deciding nothing; \N{SECTION SIGN}16c). **Done (2026-10-21; " + "\N{SECTION SIGN}20 step 4):** the second pass, under d1724, " + "completed with no refusal, failure or mapping failure, and the " + "structural counts of rows U0 and U1 carry the per-exclusion person " + "counts (`n_family_unit_exclusion_persons` and " + "`n_family_unit_exclusion_persons_distinct`); this record states none " + "of them.", ], "execution": [ "**Pending.**", @@ -1562,7 +1596,16 @@ def test_section_16a_recommendations_and_options(): "`::test_rows_equal_the_section_15_block`). The equality checks " "of the specification, the manifests and the final parameter " "block wait for \N{SECTION SIGN}20 step 5, which materializes the " - "block and empties \N{SECTION SIGN}15's `blocked_by`." + "block and empties \N{SECTION SIGN}15's `blocked_by`. **Done " + "(2026-10-21; \N{SECTION SIGN}20 step 5):** \N{SECTION SIGN}15 " + "holds the final block, `u2-ratified-1`, with the amendment 5 " + "fragment, an empty `blocked_by` and U2's rulings record, and " + "`rows.check_block_against_code` holds the whole block equal to " + "the code, every key and value; the manifests it is checked " + "against are the committed registries, all RESOLVED " + "(\N{SECTION SIGN}20 step 2), which give each wave's weight, " + "family-unit and WEALTH1 variables and every registry entry the " + "rulings record names (`test_u2_specification_block.py`)." ), }, "execution": { diff --git a/tests/track_u2/test_u2_employer_dc.py b/tests/track_u2/test_u2_employer_dc.py index 70a7611c7..8ef0d6f8a 100644 --- a/tests/track_u2/test_u2_employer_dc.py +++ b/tests/track_u2/test_u2_employer_dc.py @@ -148,10 +148,9 @@ def _section_16a_fragment() -> dict: def test_declared_route_follows_the_ratified_amendment_5_fragment(): """Max's d637 ruling ratified amendment 5 as written (section 16b), so the declared route is section 16a's ``previous_routes`` fragment: - the same items per wave, with 2013 equal to section 15's generic - route. Section 15's block keeps draft 3's generic route until section - 20 step 5 materializes the final parameter block; this test then has - to change with it.""" + the same items per wave, with 2013 equal to draft 3's generic route. + Section 20 step 5 materialized the fragment in section 15's block, so + the block's ``previous_routes`` now equals it too.""" from populace_dynamics.uniform_cut_track_u2 import rows fragment = _section_16a_fragment() @@ -166,14 +165,15 @@ def test_declared_route_follows_the_ratified_amendment_5_fragment(): assert fragment["refused_branches"] == { "2015": "P62ACKPT: P46=1 AND P52=1 AND P62AMT=DK/RF" } + # Section 20 step 5 materialized the fragment in section 15's block. block = rows.specification_block()["income_concept"]["employer_dc"] - assert block["previous_routes"] == { - "both_items": ["both"], - "account_items": ["account", "formula", "dk"], - } - assert tuple(block["previous_routes"]["account_items"]) == ( - sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS[2013] - ) + assert block["previous_routes"] == fragment + assert block["previous_routes"]["account_items_by_wave"]["2013"] == [ + "account", + "formula", + "dk", + ] + assert "account_items" not in block["previous_routes"] for wave in sources.SUPPORT_WAVES: route = sources.declared_dc_route(wave) assert route.previous_account_items == tuple( diff --git a/tests/track_u2/test_u2_identity.py b/tests/track_u2/test_u2_identity.py index 179db9bea..930de98ee 100644 --- a/tests/track_u2/test_u2_identity.py +++ b/tests/track_u2/test_u2_identity.py @@ -104,18 +104,20 @@ def test_u2_refuses_u1_rulings_and_max_rulings(): rows.check_u2_rulings_against_block({"decisions": decisions}) -def test_draft_block_rulings_are_refused_until_materialized(): - with pytest.raises(ValueError, match="no ruling by Max"): - rows.check_u2_rulings_against_block(rows.specification_block()) - ruled = { - "decisions": { - "ruled_by": "Max", - "ruled_on": "2026-09-28", - **rows.U2_RULINGS, - } +def test_the_materialized_block_records_the_rulings(): + """Section 20 step 5 wrote U2's rulings record into the block; the + draft block's placeholder is refused, and so is any altered entry.""" + block = rows.specification_block() + assert block["decisions"] == { + "ruled_by": "Max", + "ruled_on": "2026-09-28", + **rows.U2_RULINGS, } - assert rows.check_u2_rulings_against_block(ruled)["rulings_equal"] - altered = {"decisions": {**ruled["decisions"], "downloads": {"ruling": 1}}} + assert rows.check_u2_rulings_against_block(block)["rulings_equal"] + draft = {"decisions": {"u2_ratification": "pending_Max"}} + with pytest.raises(ValueError, match="no ruling by Max"): + rows.check_u2_rulings_against_block(draft) + altered = {"decisions": {**block["decisions"], "downloads": {"ruling": 1}}} with pytest.raises(ValueError, match="differ"): rows.check_u2_rulings_against_block(altered) diff --git a/tests/track_u2/test_u2_scripts.py b/tests/track_u2/test_u2_scripts.py index c1f906845..d4a834166 100644 --- a/tests/track_u2/test_u2_scripts.py +++ b/tests/track_u2/test_u2_scripts.py @@ -86,7 +86,12 @@ def test_dry_run_records_every_branch_and_refusal(dry_run): checks = result["checks"] assert checks["specification_rows"]["rows_equal_the_block"] assert checks["named_deltas"]["equal_to_section_12"] - assert checks["u2_rulings_against_block"]["refused"] + assert checks["u2_rulings_against_block"]["rulings_equal"] + assert checks["u2_rulings_against_code"]["rulings_equal_the_code"] + assert checks["specification_block_against_code"] == { + "specification_version": "u2-ratified-1", + "block_equals_code": True, + } assert checks["plans"]["u0_pairs"] == 5 assert checks["plans"]["u1_pairs"] == 15 assert checks["plans"]["u1_birth_years"] == 10 @@ -659,25 +664,33 @@ def registered(): def _ratified_block(): + """The committed block, which section 20 step 5 materialized.""" block = copy.deepcopy(rows.specification_block()) - block["status"] = "ratified_frozen" - block["version"] = "u2-ratified-1" - block["blocked_by"] = [] - block["ssi"]["parameters"][ - "sha256" - ] = "a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762" - block["income_concept"]["employer_dc"]["reader"] = "u2_employer_dc_adapter" - block["income_concept"]["employer_dc"][ - "later_wave_mapping_status" - ] = "RESOLVED" - block["decisions"] = { + assert block["version"] == "u2-ratified-1" + assert block["status"] == "ratified_frozen" + assert block["blocked_by"] == [] + assert block["ssi"]["parameters"]["sha256"] == ( + "a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762" + ) + assert block["decisions"] == { "ruled_by": "Max", "ruled_on": "2026-09-28", - **copy.deepcopy(rows.U2_RULINGS), + **rows.U2_RULINGS, } return block +def _draft_block(): + """The committed block as it stood before section 20 step 5.""" + block = _ratified_block() + block["version"] = "u2-draft-4" + block["status"] = "draft" + block["ssi"]["parameters"]["sha256"] = "TO_VERIFY" + block["decisions"] = {"u2_ratification": "pending_Max"} + block["blocked_by"] = ["preregistration_structure_reconciliation"] + return block + + #: INVENTED file digests standing in for the frozen PSID identities the #: section 20 step-4 pass would record (no real file is hashed here). _INVENTED_FILES = { @@ -743,7 +756,58 @@ def test_preflight_refuses_before_the_specification( def test_preflight_refuses_the_draft_specification(registered): with pytest.raises(ValueError, match="draft|ratified"): - _preflight(registered, specification=rows.specification_block()) + _preflight(registered, specification=_draft_block()) + + +def test_preflight_passes_the_committed_specification(registered): + """Section 20 step 5: the committed block, which the preflight reads + when no specification is passed, passes items 4 and 5.""" + binding = registered.binding_sha256("b" * 64, _invented_evidence()) + state = _preflight(registered, specification=None, binding=binding) + assert state["rows"]["rows_equal_the_block"] + assert state["rulings"]["rulings_equal"] + assert state["rulings_against_code"]["rulings_equal_the_code"] + assert state["block_against_code"] == { + "specification_version": "u2-ratified-1", + "block_equals_code": True, + } + + +@pytest.mark.parametrize( + "change, path", + [ + ( + lambda b: b["income_concept"]["employer_dc"][ + "previous_routes" + ].update(account_items=["account", "formula", "dk"]), + "income_concept.employer_dc.previous_routes.account_items", + ), + ( + lambda b: b["income_concept"]["employer_dc"].update( + reader="data/employer_dc.py" + ), + "income_concept.employer_dc.reader", + ), + ( + lambda b: b["population"]["waves"]["2017"].update( + weight="ER34650" + ), + "population.waves.2017.weight", + ), + ( + lambda b: b["threshold"].update(poor_if="income_at_or_below"), + "threshold.poor_if", + ), + ], +) +def test_preflight_refuses_a_block_that_differs_from_the_code( + registered, change, path +): + block = _ratified_block() + change(block) + with pytest.raises(ValueError, match="differs from the code") as error: + _preflight(registered, specification=block) + assert path in str(error.value) @pytest.mark.parametrize( diff --git a/tests/track_u2/test_u2_specification_block.py b/tests/track_u2/test_u2_specification_block.py new file mode 100644 index 000000000..1f5e2af75 --- /dev/null +++ b/tests/track_u2/test_u2_specification_block.py @@ -0,0 +1,515 @@ +"""Section 20 step 5: the final section 15 block equals the code exactly. + +The block, U2's rulings record and the TO VERIFY appendix are documentary: +these tests read the specification, the committed registries and the code, +and never a PSID file. Invariants held here: + +* the committed block equals :func:`rows.expected_specification_block` + under ``==`` and as canonical JSON; +* :func:`rows.check_block_against_code` passes a block exactly when it is + canonically equal to the code's, and otherwise names every differing + path (every leaf, every added or dropped key, every JSON type change); +* the rulings record covers every U2 decision and agrees with the + committed registries and the code; +* the block holds no open value, and every prose TO VERIFY line the + specification keeps is mapped to its resolution. +""" + +from __future__ import annotations + +import ast +import copy +import importlib.util +import json +import re +from collections.abc import Iterator +from pathlib import Path +from typing import Any + +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from populace_dynamics.data import u2_source_registry as registry +from populace_dynamics.uniform_cut_track_u import rows as u1_rows +from populace_dynamics.uniform_cut_track_u2 import identity, rows, sources + +ROOT = Path(__file__).resolve().parents[2] +TESTS = ROOT / "tests" / "track_u2" + + +@pytest.fixture(scope="module") +def registries(): + return sources.RegistrySet.committed() + + +@pytest.fixture(scope="module") +def block(): + return rows.specification_block() + + +@pytest.fixture(scope="module") +def spec_lines(): + return identity.SPECIFICATION_PATH.read_text(encoding="utf-8").split("\n") + + +def _canonical(value: Any) -> str: + return json.dumps(value, sort_keys=True, allow_nan=False) + + +def _leaves(value: Any, path: tuple = ()) -> Iterator[tuple[tuple, Any]]: + if isinstance(value, dict) and value: + for key, item in value.items(): + yield from _leaves(item, (*path, key)) + elif isinstance(value, list) and value: + for index, item in enumerate(value): + yield from _leaves(item, (*path, index)) + else: + yield path, value + + +def _mappings(value: Any, path: tuple = ()) -> Iterator[tuple[tuple, dict]]: + if isinstance(value, dict): + yield path, value + for key, item in value.items(): + yield from _mappings(item, (*path, key)) + elif isinstance(value, list): + for index, item in enumerate(value): + yield from _mappings(item, (*path, index)) + + +def _at(value: Any, path: tuple) -> Any: + for step in path: + value = value[step] + return value + + +def _dotted(path: tuple) -> str: + text = "" + for step in path: + if isinstance(step, int): + text += f"[{step}]" + else: + text += f".{step}" if text else str(step) + return text + + +def _changed(block: dict, path: tuple, value: Any) -> dict: + changed = copy.deepcopy(block) + _at(changed, path[:-1])[path[-1]] = value + return changed + + +def _other(value: Any) -> Any: + """A value whose canonical JSON differs from ``value``'s.""" + if value is None: + return "null" + if isinstance(value, bool): + return not value + if isinstance(value, int): + return value + 1 + if isinstance(value, float): + return value + 0.25 + if isinstance(value, str): + return value + "_changed" + if value == []: + return [None] + if value == {}: + return {"added": None} + raise AssertionError(value) + + +def _refused(block: dict, registries, path: tuple) -> None: + """The check refuses ``block`` and names ``path`` (or, where an empty + mapping or list gained an element, a path inside it).""" + with pytest.raises(ValueError, match="differs from the code") as error: + rows.check_block_against_code(block, registries) + named = ast.literal_eval(str(error.value).split(" at ", 1)[1]) + where = _dotted(path) + assert any( + name == where or name.startswith((f"{where}.", f"{where}[")) + for name in named + ), (where, named) + + +# --------------------------------------------------------------------------- +# The block equals the code +# --------------------------------------------------------------------------- +def test_the_committed_block_equals_the_code(block, registries): + expected = rows.expected_specification_block(registries) + assert block == expected + assert _canonical(block) == _canonical(expected) + assert rows.check_block_against_code(block, registries) == { + "specification_version": "u2-ratified-1", + "block_equals_code": True, + } + assert block["version"] == rows.SPECIFICATION_VERSION + assert block["status"] == rows.SPECIFICATION_STATUS + + +def test_every_leaf_change_is_refused_and_named(block, registries): + leaves = list(_leaves(block)) + assert len(leaves) > 400 + for path, value in leaves: + _refused(_changed(block, path, _other(value)), registries, path) + + +def test_every_json_type_change_is_refused(block, registries): + """``True`` is not ``1`` and ``1`` is not ``1.0``: equal under ``==``, + different as canonical JSON, so the check compares types too.""" + swaps = 0 + for path, value in _leaves(block): + if isinstance(value, bool): + other: Any = int(value) + elif isinstance(value, int): + other = float(value) + elif isinstance(value, float) and value.is_integer(): + other = int(value) + else: + continue + assert other == value + _refused(_changed(block, path, other), registries, path) + swaps += 1 + assert swaps > 50 + + +def test_every_added_or_dropped_key_is_refused(block, registries): + mappings = list(_mappings(block)) + assert len(mappings) > 60 + for path, mapping in mappings: + added = copy.deepcopy(block) + _at(added, path)["unregistered_key"] = None + _refused(added, registries, (*path, "unregistered_key")) + for key in mapping: + dropped = copy.deepcopy(block) + del _at(dropped, path)[key] + _refused(dropped, registries, (*path, key)) + + +def test_a_reordered_list_is_refused(block, registries): + path = ("cells", "scored") + reordered = list(reversed(_at(block, path))) + _refused(_changed(block, path, reordered), registries, (*path, 0)) + + +_SCALARS = st.one_of( + st.none(), + st.booleans(), + st.integers(min_value=-(10**6), max_value=10**12), + st.floats(allow_nan=False, allow_infinity=False, width=64), + st.text(max_size=12), +) + + +@settings(max_examples=300, deadline=None) +@given(index=st.integers(min_value=0), value=_SCALARS) +def test_property_the_check_passes_exactly_the_canonical_block(index, value): + """For any leaf and any JSON scalar, the check passes the changed block + exactly when it is canonically the committed one.""" + block = rows.specification_block() + registries = sources.RegistrySet.committed() + leaves = list(_leaves(block)) + path, old = leaves[index % len(leaves)] + changed = _changed(block, path, value) + if _canonical(value) == _canonical(old): + assert rows.check_block_against_code(changed, registries)[ + "block_equals_code" + ] + else: + _refused(changed, registries, path) + + +# --------------------------------------------------------------------------- +# The materialized values +# --------------------------------------------------------------------------- +def _find(value: Any, needle: str) -> list[str]: + return [ + _dotted(path) + for path, leaf in _leaves(value) + if isinstance(leaf, str) and needle in leaf.lower() + ] + + +def test_the_block_holds_no_open_value(block): + assert block["blocked_by"] == [] + assert _find(block, "to_verify") == [] + assert _find(block, "to verify") == [] + assert _find(block, "pending") == [] + spec = importlib.util.spec_from_file_location( + "u2_registered_for_block_test", + ROOT / "scripts" / "run_track_u2_registered.py", + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + module.check_specification_ratified(block) + + +def test_the_resolved_keys_name_the_code(block, registries): + dc = block["income_concept"]["employer_dc"] + reader = ROOT / "src" / "populace_dynamics" / dc["reader"] + assert reader.resolve() == Path(sources.__file__).resolve() + assert "def employer_dc_balances(" in reader.read_text(encoding="utf-8") + assert all( + entry["status"] == "RESOLVED" + for entry in registries.entries("pension") + ) + assert dc["later_wave_mapping_status"].startswith("resolved_") + routes = dc["previous_routes"] + assert { + int(wave): tuple(items) + for wave, items in routes["account_items_by_wave"].items() + } == sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS + assert routes["refused_branches"] == { + "2015": registry.RATIFIED_AMENDMENTS["5b"]["fields"]["refused_branch"] + } + assert block["ssi"]["parameters"]["sha256"] == ( + "a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762" + ) + + +def test_the_inherited_and_recorded_identities(spec_lines): + assert rows._U1_VERSION == u1_rows.specification_block()["version"] + text = "\n".join(spec_lines) + section_13 = text[text.index("## 13. ") : text.index("## 14. ")] + assert rows._U1_INPUT_FRAMES_SHA256 in section_13 + section_20 = text[text.index("## 20. ") : text.index("## Appendix")] + assert rows._STEP_4_SECOND_HEAD in section_20 + assert str(rows._STEP_4_SECOND_TESTS_RUN) in section_20 + assert f"`{rows._STEP_4_FIRST_HEAD[:8]}`" in section_20 + + +# --------------------------------------------------------------------------- +# U2's rulings record +# --------------------------------------------------------------------------- +def test_the_rulings_record_covers_every_ruling(block, registries): + assert block["decisions"] == { + "ruled_by": "Max", + "ruled_on": "2026-09-28", + **rows.U2_RULINGS, + } + assert rows.check_rulings_against_code(registries) == { + "decisions": list(rows.RULING_DECISIONS), + "entries": len(rows.U2_RULINGS), + "rulings_equal_the_code": True, + } + assert rows.RULING_DECISIONS == ( + "d514", + "d637", + "d745", + "d1090", + "d1242", + "d1597", + "d1649", + "d1724", + ) + for decision in ("d637", "d1090", "d1724"): + assert any( + entry["decision_record"] == decision + for entry in rows.U2_RULINGS.values() + ) + assert { + entry["confirmed_by"] + for entry in rows.U2_RULINGS.values() + if "confirmed_by" in entry + } == {"d745"} + + +def test_every_section_16_ruling_is_a_d514_entry(spec_lines): + """Section 16's decision table, ruled by d514: each row is an entry.""" + text = "\n".join(spec_lines) + section = text[text.index("## 16. ") : text.index("## 16a. ")] + names = re.findall(r"^\| ([A-Z][^|]+?) \| ", section, re.M) + assert names[0] == "Decision" + keys = { + re.sub(r"[^a-z]+", "_", name.lower()).strip("_") for name in names[1:] + } + assert len(keys) == 8 + for key in keys: + assert rows.U2_RULINGS[key]["decision_record"] == "d514", key + + +def test_registry_rulings_are_the_registry_scopes(): + for code, scope in registry.RATIFIED_AMENDMENTS.items(): + entries = [ + entry + for entry in rows.U2_RULINGS.values() + if entry.get("registry_entries") + == [f"{scope['registry']}:{e}" for e in scope["entries"]] + ] + assert len(entries) == 1, code + assert entries[0]["decision_record"] == scope["ruling"]["decision"] + assert entries[0]["ruled_on"] == scope["ruling"]["date"] + for disposition, scope in registry.RATIFIED_EXCLUSIONS.items(): + (entry,) = ( + e + for e in rows.U2_RULINGS.values() + if e.get("disposition") == disposition + ) + assert entry["registry_entries"] == [ + f"roles:{e}" for e in scope["entries"] + ] + assert entry["amends_draft_3_lines"] == list( + scope["ruling"]["amends_draft_3_lines"] + ) + + +def _without_reference(key: str, reference: str) -> dict: + record = copy.deepcopy(rows.U2_RULINGS) + record[key]["registry_entries"].remove(reference) + return record + + +@pytest.mark.parametrize( + "record, message", + [ + ( + _without_reference( + "amendment_1a_documented_routes", "roles:2017.relationship.92" + ), + "ratified scopes", + ), + ( + _without_reference( + "blocker_b2_codes_90_92_2019_2023", + "roles:2023.relationship.92", + ), + "roles registry carries it", + ), + ( + { + **rows.U2_RULINGS, + "unrecorded": {"ruling": "x", "decision_record": "d9999"}, + }, + "names", + ), + ( + { + key: value + for key, value in rows.U2_RULINGS.items() + if value["decision_record"] != "d1597" + }, + "names", + ), + ( + { + **rows.U2_RULINGS, + "amendment_5c_u7_2015_spouse_slot": { + **rows.U2_RULINGS["amendment_5c_u7_2015_spouse_slot"], + "decision_record": "d637", + }, + }, + "records 'd1090'", + ), + ( + { + **rows.U2_RULINGS, + "amendment_5c_u7_2015_spouse_slot": { + **rows.U2_RULINGS["amendment_5c_u7_2015_spouse_slot"], + "registry_entries": ["pension:2015.route.no_such_entry"], + }, + }, + "not a RESOLVED registry entry", + ), + ( + { + **rows.U2_RULINGS, + "blocker_b1_male_code_20_2015": { + **rows.U2_RULINGS["blocker_b1_male_code_20_2015"], + "option": "1d-1", + }, + }, + "another option", + ), + ( + { + **rows.U2_RULINGS, + "seed_birth_year_support": { + **rows.U2_RULINGS["seed_birth_year_support"], + "support": [1890, 2016], + }, + }, + "d1724", + ), + ], +) +def test_a_changed_rulings_record_is_refused( + monkeypatch, registries, record, message +): + monkeypatch.setattr(rows, "U2_RULINGS", record) + with pytest.raises(ValueError, match=message): + rows.check_rulings_against_code(registries) + + +# --------------------------------------------------------------------------- +# The TO VERIFY appendix +# --------------------------------------------------------------------------- +_APPENDIX = "## Appendix: TO VERIFY item → where resolved" + + +def _appendix_rows(spec_lines: list[str]) -> list[list[str]]: + start = spec_lines.index(_APPENDIX) + table = [ + line + for line in spec_lines[start:] + if line.startswith("| ") and not line.startswith("|---") + ] + assert ( + table[0] + == "| Line | Item left open in draft 3 | Resolution | Evidence |" + ) + return [ + [cell.strip() for cell in line.strip("|").split(" | ")] + for line in table[1:] + ] + + +def test_the_to_verify_appendix_is_complete(spec_lines): + """Every line before section 15 that still says TO VERIFY, and section + 17's closing line, has a row; every row names such a line.""" + section_15 = spec_lines.index("## 15. Machine-readable parameter block") + open_lines = { + number + for number, line in enumerate(spec_lines[:section_15], 1) + if "TO VERIFY" in line or "TO_VERIFY" in line + } + table = _appendix_rows(spec_lines) + listed = [row[0] for row in table] + assert listed[-1] == "§17's closing line" + assert {int(line) for line in listed[:-1]} == open_lines + assert listed[:-1] == sorted(listed[:-1], key=int) + section_17 = spec_lines.index( + next(x for x in spec_lines if x.startswith("## 17. ")) + ) + section_18 = spec_lines.index( + next(x for x in spec_lines if x.startswith("## 18. ")) + ) + closing = [x for x in spec_lines[section_17:section_18] if x.strip()][-1] + assert "TO VERIFY" in closing + assert table[-1][1].replace("TO VERIFY", "**TO VERIFY**") in closing + + +def test_the_appendix_evidence_exists(spec_lines): + """Each test the appendix cites exists, and each ``decisions`` or + ``income_concept`` key it names is in the block.""" + block = rows.specification_block() + tests = { + path.name: path.read_text(encoding="utf-8") + for path in TESTS.glob("test_*.py") + } + cited_tests = 0 + for row in _appendix_rows(spec_lines): + evidence = row[3] + module = None + for name, test in re.findall( + r"`(?:(test_[a-z0-9_]+\.py))?::(test_[a-z0-9_]+)`", evidence + ): + module = name or module + assert module in tests, module + assert f"def {test}(" in tests[module], (module, test) + cited_tests += 1 + for key in re.findall( + r"`decisions\.([a-z0-9_]+)`|`\.([a-z0-9_]+)`", evidence + ): + name = key[0] or key[1] + assert name in block["decisions"], name + assert cited_tests >= 14 From 690a12764fbf0e8cfa7298273d055217be3617fc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 10:49:35 -0400 Subject: [PATCH 03/12] =?UTF-8?q?U2=20=C2=A720=20step=205:=20pin=20the=20s?= =?UTF-8?q?tep-4=20evidence=20record's=20SHA-256?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the coordinating session's request, the pre-registration evidence record the d1724 step-4 pass wrote is pinned by the SHA-256 its MANIFEST.md records, 7bc5c3c2...b845: - loader.PREREGISTRATION_EVIDENCE_SHA256 holds it, and read_preregistration_evidence, which the registered preflight and every frozen load call, refuses any file whose bytes differ before parsing it (a well-formed invented record included); - §15 gains preregistration_evidence (file, sha256, schema, writer, authorizing decision and where the pin comes from), which rows.expected_specification_block states from the loader's constants, so check_block_against_code holds the two equal. The record itself is restricted and is neither opened nor committed here; a separate lane copies it to docs/design/ before §20 step 8. Also: export rows.RULING_DECISIONS and rename two comprehension variables that shadowed `entry`. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 10 +++++++ .../uniform_cut_track_u2/loader.py | 22 +++++++++++++-- .../uniform_cut_track_u2/rows.py | 19 ++++++++++--- tests/track_u2/test_u2_loader.py | 27 ++++++++++++++++++- 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index ac1d8ada3..81351a6de 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "b000c180e632997e40c440ade5df2d3c7d1a93dd63b00d3185ba598d0125338a", + "sha256": "ad55588091a1af2677114699567de28abceb56ec943323f74391c5b58900c03a", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index 218e67f10..8be13fe4b 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -1329,6 +1329,14 @@ This is the final parameter contract, materialized under §20 step 5 (2026-10-21 "structure": "scripts/track_u2_structure.py", "component_diagnostics": "scripts/track_u2_component_diagnostics.py" }, + "preregistration_evidence": { + "file": "docs/design/u2_preregistration_evidence.json", + "sha256": "7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845", + "schema_version": "populace_dynamics.track_u2_preregistration_evidence.v2", + "written_by": "scripts/track_u2_structure.py", + "authorized_by": "d1724", + "pinned_from": "EV/track-u2-step4-pass-20261021-run2/MANIFEST.md" + }, "acceptance_rule": null, "labels": [ "PSID-realized outcomes (not a projection)", @@ -2231,6 +2239,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t **`u2-ratified-1`, §20 step 5: the final parameter block and U2's rulings record (2026-10-21):** - §15 holds the final block, version `u2-ratified-1` with status `ratified_frozen`. The three `TO_VERIFY` keys are resolved: the SSI capture's SHA-256 is pinned, the employer-DC `reader` names the U2 adapter (`uniform_cut_track_u2/sources.py`), and `later_wave_mapping_status` records the RESOLVED pension registry. `previous_routes` is amendment 5's ratified fragment, laid out so that the registry-cited lines 1173 and 1185 keep their draft-3 number and text. `population.relationship_rules` names amendment 1. `blocked_by` is empty, and §20 step 5 gives each blocker's resolution. +- §15's new `preregistration_evidence` pins the step-4 evidence record's SHA-256 (`7bc5c3c2…b845`, from the run-2 `MANIFEST.md`), and `loader.read_preregistration_evidence` refuses any other bytes at `docs/design/u2_preregistration_evidence.json` before parsing them. The record itself is not opened or committed here. - `decisions` holds U2's rulings record: d514's eleven entries, unchanged, and an entry for every later ruling: d637 (amendments 1a–1c, confirmed by d745; amendments 2, 3, 4, 5a, 5b and 6; the PSID questions), d1090 (B1, B2 and amendment 5c), d1242, d1597, d1649 and d1724. - Code: `rows.U2_RULINGS` builds the registry-applied entries from the registry module's ratified scopes; `rows.expected_specification_block()` states the whole block from the code; `rows.check_block_against_code` and `rows.check_rulings_against_code` refuse any difference, and the registered run's preflight and the dry run apply both. The registered-run docstring, the package docstring and the cohort's decision notes no longer describe a draft block. - Tests: the new `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code, mutates every leaf and refuses each change, adds and drops keys, checks JSON types (`true` is not `1`, `1` is not `1.0`), holds the rulings record to every decision, and holds the TO VERIFY appendix complete. The employer-DC, identity and registered-script tests now expect the materialized block, and the specification-revision test holds lines before §15 to draft 3 (except the version and status lines), §15's lines through 1185 to draft 3 except where the block was materialized, and §16 to draft 3's text at its new position. @@ -2250,6 +2259,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Amendment 5's fragment.** `previous_routes` is the fragment §16a printed and d637 ratified, equal to `sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS` and to the 2015 refused branch the pension registry records. The registries cite §15's lines 1173 and 1185, so both keep their draft-3 number and text: the fragment fills lines 1174–1184 in place of draft 3's generic route, and the four keys that stood there follow line 1185 unchanged except `later_wave_mapping_status`. `population.relationship_rules` now names amendment 1 with §3's role amendment. - **`blocked_by` is empty.** Each draft-3 blocker is resolved: later-wave component and role adjudication, and the mapping manifests and independent review, by the RESOLVED registries and their reviews (steps 2 and 3); 2015 male code 20 by d1090's B1 exclusion (§16c); code-90 and code-92 routing by amendment 1a and 1b (d637, confirmed by d745) and d1090's B2 exclusion; later-wave employer-DC routing by amendment 5 (d637); the SSI capture by its verification and this pin (§17 item 5); cohort isolation and identity guards, and the invented dry run and U1 differential, by milestone 2 (§17 items 6 and 7); the pre-registration pass by step 4; and ratification by this revision's merge (step 6). The fresh forecast and the registration, which that last blocker also named, are steps 7 and 8: the registered run's preflight refuses without the issue #42 registration and its binding, and without the committed pre-registration evidence, so an empty `blocked_by` authorizes no run. - **U2's rulings record.** `decisions` holds `rows.U2_RULINGS` with `ruled_by` Max and `ruled_on` 2026-09-28: d514's eleven entries, unchanged, which adopt every §16 ruling; d637's entries for amendments 1a–1c (confirmed by d745), 2, 3, 4, 5a, 5b and 6 and for the three PSID questions; d1090's entries for B1, B2 and amendment 5c; and one entry each for d1242 (B2's counting unit), d1597 (the structure-only U1 load), d1649 (the step-4 pass) and d1724 (the birth law's support). Each registry-applied entry names the registry entries it governs and copies their ruling, built from the registry module's own scopes, and `rows.check_rulings_against_code` holds the record to the committed registries, the role rules and the cohort code. The d1597, d1649 and d1724 entries record that each authorization was used once, with the head, date and value-free result. + - **The pre-registration evidence pin.** The step-4 pass wrote `track-u2-preregistration-evidence.json`, the record of PSID file and frame hashes that the registered run rechecks; the restricted list keeps it unopened here. The pass's `MANIFEST.md` pins its SHA-256, `7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845`. §15's `preregistration_evidence` and `loader.PREREGISTRATION_EVIDENCE_SHA256` now pin the same value, so `loader.read_preregistration_evidence`, which the registered preflight and every frozen load call, refuses any file at `docs/design/u2_preregistration_evidence.json` whose bytes differ, before it parses it. A separate lane commits the file there, byte for byte, before step 8; this revision does not touch it. - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. 6. Ratify the specification by merge. **This revision's merge (2026-10-21).** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential runs again at the revision's last code change (§17 item 7). The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). diff --git a/src/populace_dynamics/uniform_cut_track_u2/loader.py b/src/populace_dynamics/uniform_cut_track_u2/loader.py index b1aadc034..e8afac1f4 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/loader.py +++ b/src/populace_dynamics/uniform_cut_track_u2/loader.py @@ -107,6 +107,7 @@ __all__ = [ "ANCHOR_CONCEPTS", "PREREGISTRATION_EVIDENCE_PATH", + "PREREGISTRATION_EVIDENCE_SHA256", "PREREGISTRATION_SCHEMA", "U2AuthorizationRefusal", "U2FrozenIdentityMismatch", @@ -189,6 +190,13 @@ PREREGISTRATION_EVIDENCE_PATH = ( identity.ROOT / "docs" / "design" / "u2_preregistration_evidence.json" ) +#: The SHA-256 of the record the section 20 step-4 pass wrote under d1724 +#: (2026-10-21), as that pass's MANIFEST.md pins it. The committed record +#: must be that file byte for byte: any other bytes refuse before they are +#: parsed (section 20 step 5). +PREREGISTRATION_EVIDENCE_SHA256 = ( + "7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845" +) _EVIDENCE_KEYS: tuple[str, ...] = ( "schema_version", "target_id", @@ -923,7 +931,9 @@ def preregistration_evidence(inputs: cohort.U2Inputs) -> dict[str, Any]: def read_preregistration_evidence( path: Path = PREREGISTRATION_EVIDENCE_PATH, ) -> dict[str, Any]: - """The committed evidence record; refuses while it is absent.""" + """The committed evidence record; refuses while it is absent, and + refuses any file whose bytes are not the pinned record's + (:data:`PREREGISTRATION_EVIDENCE_SHA256`).""" if not Path(path).is_file(): raise U2FrozenIdentityMismatch( @@ -931,8 +941,16 @@ def read_preregistration_evidence( "20 step-4 pass has not frozen the PSID file and frame " "identities, so a registered run refuses (section 14)" ) + raw = Path(path).read_bytes() + digest = hashlib.sha256(raw).hexdigest() + if digest != PREREGISTRATION_EVIDENCE_SHA256: + raise U2FrozenIdentityMismatch( + f"the evidence record at {path} has SHA-256 {digest}, not the " + f"pinned {PREREGISTRATION_EVIDENCE_SHA256} the step-4 pass wrote " + "(section 20 step 5): it is not the committed record" + ) try: - record = json.loads(Path(path).read_text(encoding="utf-8")) + record = json.loads(raw.decode("utf-8")) except ValueError as error: raise U2FrozenIdentityMismatch( f"the evidence record at {path} is not JSON: {error}" diff --git a/src/populace_dynamics/uniform_cut_track_u2/rows.py b/src/populace_dynamics/uniform_cut_track_u2/rows.py index 003d26b02..16795ca3a 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/rows.py +++ b/src/populace_dynamics/uniform_cut_track_u2/rows.py @@ -52,6 +52,7 @@ from populace_dynamics.uniform_cut_track_u2 import ( cohort, identity, + loader, memo, parameters, sources, @@ -66,6 +67,7 @@ "NAMED_DELTAS", "REGISTERED_ROWS", "ROW_IDS", + "RULING_DECISIONS", "SENSITIVITIES_UNSCORED", "SPECIFICATION_STATUS", "SPECIFICATION_VERSION", @@ -328,7 +330,7 @@ def _ruled(scope: Mapping[str, Any], **record: Any) -> dict[str, Any]: entry = { **record, "registry_entries": [ - f"{scope['registry']}:{entry}" for entry in scope["entries"] + f"{scope['registry']}:{name}" for name in scope["entries"] ], "specification_section": ruling["specification_section"], "decision_record": ruling["decision"], @@ -360,7 +362,7 @@ def _excluded(disposition: str, **record: Any) -> dict[str, Any]: "member_condition": ( None if scope["condition"] is None else dict(scope["condition"]) ), - "registry_entries": [f"roles:{entry}" for entry in scope["entries"]], + "registry_entries": [f"roles:{name}" for name in scope["entries"]], "option": ruling["option"], "amends_draft_3_lines": list(ruling["amends_draft_3_lines"]), "specification_section": ruling["specification_section"], @@ -912,7 +914,8 @@ def expected_specification_block( support waves and their registry variables, the income-concept, annuity, threshold, cut and SSI parameters and their capture pins, the cells, floor and design settings, the memo rules, the rows, the - entry points, the labels and U2's rulings record. The descriptive + entry points, the pre-registration evidence pin, the labels and U2's + rulings record. The descriptive values the code does not otherwise hold (the Report's citation, the unit and financial-asset definitions, the comparator boundary, the cleared extracts' hashes and the wording keys) are written here once, @@ -1211,6 +1214,16 @@ def expected_specification_block( "scripts/track_u2_component_diagnostics.py" ), }, + "preregistration_evidence": { + "file": _relative(loader.PREREGISTRATION_EVIDENCE_PATH), + "sha256": loader.PREREGISTRATION_EVIDENCE_SHA256, + "schema_version": loader.PREREGISTRATION_SCHEMA, + "written_by": "scripts/track_u2_structure.py", + "authorized_by": U2_RULINGS["seed_birth_year_support"][ + "decision_record" + ], + "pinned_from": "EV/track-u2-step4-pass-20261021-run2/MANIFEST.md", + }, "acceptance_rule": None, "labels": list(ap.OUTPUT_LABELS), "decisions": {"ruled_by": "Max", "ruled_on": _RULED_ON, **U2_RULINGS}, diff --git a/tests/track_u2/test_u2_loader.py b/tests/track_u2/test_u2_loader.py index eed72642f..d3c56ebfa 100644 --- a/tests/track_u2/test_u2_loader.py +++ b/tests/track_u2/test_u2_loader.py @@ -37,6 +37,7 @@ import copy import hashlib +import json import math import os import pwd @@ -1172,11 +1173,35 @@ def test_a_malformed_evidence_record_refuses(evidence, change, message): loader.check_preregistration_evidence(change(dict(evidence))) -def test_an_absent_evidence_record_refuses(tmp_path): +def test_an_absent_or_unpinned_evidence_record_refuses( + tmp_path, evidence, monkeypatch +): + """Section 20 step 5 pins the record the step-4 pass wrote: a file + with any other bytes refuses before it is parsed, a well-formed + invented record included.""" + assert loader.PREREGISTRATION_EVIDENCE_SHA256 == ( + "7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845" + ) with pytest.raises(loader.U2LoaderRefusal, match="no pre-registration"): loader.read_preregistration_evidence(tmp_path / "absent.json") path = tmp_path / "evidence.json" + path.write_text(json.dumps(evidence)) + assert loader.check_preregistration_evidence(evidence) + with pytest.raises( + loader.U2FrozenIdentityMismatch, match="not the pinned" + ): + loader.read_preregistration_evidence(path) path.write_text("not json") + with pytest.raises( + loader.U2FrozenIdentityMismatch, match="not the pinned" + ): + loader.read_preregistration_evidence(path) + # Only bytes equal to the pin are parsed; these are not JSON. + monkeypatch.setattr( + loader, + "PREREGISTRATION_EVIDENCE_SHA256", + hashlib.sha256(b"not json").hexdigest(), + ) with pytest.raises(loader.U2LoaderRefusal, match="not JSON"): loader.read_preregistration_evidence(path) From 1742efb1633de323643a6bc3068e0765b90d2d0b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 21:44:08 -0400 Subject: [PATCH 04/12] =?UTF-8?q?U2=20=C2=A720=20steps=204=E2=80=935:=20an?= =?UTF-8?q?swer=20review=20r1b=20(d1597's=20two=20uses;=20block=20check=20?= =?UTF-8?q?gaps)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of 690a1276 (Opus 5.5, Subfleet 20261021-105010-u2-step5- review-r1b, REQUEST CHANGES): 1 (blocking): the d1597 rulings entry recorded one use of the structure- only U1 load; it ran before each step-4 pass (2026-10-19 at 640adccf and 2026-10-21 at 1e739d32), each matching the pin, as d1724's decision record states. The entry records both, every authorization's uses are now a list, and §20 steps 4 and 5 say so. 2: specification_block refuses a JSON key repeated at any level. 3: employer-DC persons and previous plans come from the reader (sources.DC_PERSONS/DC_PLANS, made public), the overlap_2012 values from the parameter loaders, and named scripts must exist. 4: the reader's path resolves through a symlinked checkout. 5, 7: tests for the preflight's rulings check, the dry run's specification checks (now one helper, specification_checks), d1242's person count, unresolved registry entries and -0.0. 8: the appendix names the 2013/2015 code-92 entries; its test checks every registry entry and income_concept path it cites. 9: the exposure record names the coordinating session's messages and the review lane's disclosures. 10: §15 says what decisions.ruled_on dates; §20's status line names the steps done and pending (the d1090 checks pin it as STATUS_20). Mutation recheck: 14 mutants; 12 killed, 2 equivalent (the type check the canonical-JSON leaf check covers, and one of d1242's two independent clauses; removing the whole d1242 check is killed). Tests: 1,718 passed, 254 skipped (empty PSID root). Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 71 +++++--- docs/design/u2_m2_exposure.md | 14 ++ scripts/track_u2_dry_run.py | 30 +++- .../uniform_cut_track_u2/rows.py | 102 +++++++---- .../uniform_cut_track_u2/sources.py | 16 +- tests/track_u2/test_adjudication_applied.py | 14 +- tests/track_u2/test_u2_scripts.py | 39 ++++ tests/track_u2/test_u2_specification_block.py | 168 +++++++++++++++++- 9 files changed, 377 insertions(+), 79 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 81351a6de..132f281e2 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "ad55588091a1af2677114699567de28abceb56ec943323f74391c5b58900c03a", + "sha256": "3c6539800900e6cba7963111d0960cc40261f014feb0cea88eec7c9629c5e9a6", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index 8be13fe4b..8e4aeb33a 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -1038,7 +1038,7 @@ Changed source bytes, changed frame digests, duplicate identifiers, failed joins This is the final parameter contract, materialized under §20 step 5 (2026-10-21): every pin is resolved, the ratified amendments are applied and U2's rulings record is written under `decisions`. It uses U1’s key names where the concepts are inherited. Additional keys describe U2-specific identity and amendments. -`rows.check_block_against_code` holds this block equal to the code in every key and value, and the registered run's preflight refuses any other block (§20 step 5). The block holds no `TO_VERIFY` value and no blocker. It does not by itself authorize execution: a registered run also needs the issue #42 registration and its binding, and the committed pre-registration evidence (§§14 and 20). +`rows.check_block_against_code` holds this block equal to the code in every key and value, and the registered run's preflight refuses any other block (§20 step 5). The block holds no `TO_VERIFY` value and no blocker. It does not by itself authorize execution: a registered run also needs the issue #42 registration and its binding, and the committed pre-registration evidence (§§14 and 20). Its `decisions.ruled_on` is the date of d514, which adopted §16's rulings and ratification; every later entry carries its own `ruled_on`. ```json { @@ -1592,14 +1592,23 @@ This is the final parameter contract, materialized under §20 step 5 (2026-10-21 "ruled_on": "2026-10-16" }, "u1_input_pin_check": { - "ruling": "run_the_optional_structure_only_u1_load_once", + "ruling": "run_the_optional_structure_only_u1_load_before_step_4", "u1_input_frames_sha256": "43c41f444128ecf72ce9f8c5e64a7cda61a6fccaa2a58ff60ae7ce91356c4271", "statistic_computed": null, - "authorization_used": { - "on": "2026-10-21", - "head": "1e739d328b73c99d259a72952b3452546721bfb8", - "result": "pin_matched" - }, + "authorization_used": [ + { + "on": "2026-10-19", + "head": "640adccf84f9f68c0fb893a834cfdfaca20e7888", + "before": "the_d1649_pass", + "result": "pin_matched" + }, + { + "on": "2026-10-21", + "head": "1e739d328b73c99d259a72952b3452546721bfb8", + "before": "the_d1724_pass", + "result": "pin_matched" + } + ], "specification_section": "13", "decision_record": "d1597", "ruled_on": "2026-10-16" @@ -1614,11 +1623,13 @@ This is the final parameter contract, materialized under §20 step 5 (2026-10-21 ], "not_computed": ["income_concept", "poverty_threshold_assignment", "poverty_status"], "registered_run_frame_environment": "the_preregistration_evidence_pandas_version_and_string_inference_setting", - "authorization_used": { - "on": "2026-10-19", - "head": "640adccf84f9f68c0fb893a834cfdfaca20e7888", - "result": "stopped_in_the_birth_law_without_output" - }, + "authorization_used": [ + { + "on": "2026-10-19", + "head": "640adccf84f9f68c0fb893a834cfdfaca20e7888", + "result": "stopped_in_the_birth_law_without_output" + } + ], "specification_section": "20", "decision_record": "d1649", "ruled_on": "2026-10-16" @@ -1631,12 +1642,14 @@ This is the final parameter contract, materialized under §20 step 5 (2026-10-21 "count_decides": "nothing", "estimates_career": "unchanged", "widening_approved_on": "2026-10-20", - "authorization_used": { - "on": "2026-10-21", - "head": "1e739d328b73c99d259a72952b3452546721bfb8", - "tests_run": 39502262845, - "result": "completed" - }, + "authorization_used": [ + { + "on": "2026-10-21", + "head": "1e739d328b73c99d259a72952b3452546721bfb8", + "tests_run": 39502262845, + "result": "completed" + } + ], "specification_section": "16d", "decision_record": "d1724", "ruled_on": "2026-10-19" @@ -2232,7 +2245,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t **`u2-draft-4`, §20 step 4 recorded (2026-10-21):** -- §20 step 4 records both passes in value-free terms. The first, under d1649, ran once on 2026-10-19 at `640adccf` and stopped in the birth law without writing any output (§16d). The second, under d1724, ran once on 2026-10-21 at `1e739d32`, after master's Tests run 39502262845 passed there: the d1597 U1 pin check passed, both scripts completed with no refusal, failure or mapping failure, every income and WEALTH1 identity reconciles in all six support waves, and both rows carry §16d's disclosure keys. The record cites the outputs' `MANIFEST.md` by its SHA-256 and states no count, value or other statistic from them. +- §20 step 4 records both passes in value-free terms. The first, under d1649, ran once on 2026-10-19 at `640adccf` and stopped in the birth law without writing any output (§16d). The second, under d1724, ran once on 2026-10-21 at `1e739d32`, after master's Tests run 39502262845 passed there: the d1597 U1 pin check passed, as it had before the first pass, both scripts completed with no refusal, failure or mapping failure, every income and WEALTH1 identity reconciles in all six support waves, and both rows carry §16d's disclosure keys. The record cites the outputs' `MANIFEST.md` by its SHA-256 and states no count, value or other statistic from them. - The record was written from the run-2 `MANIFEST.md`, which the restricted list allows to be read and cited, and from the coordinating session's value-free brief. No other file in either pass's folder was opened. - No count, statistic or outcome was computed, no model or estimator was run, no PSID file was read, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (§20 steps 4–6 section). @@ -2245,20 +2258,30 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - Tests: the new `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code, mutates every leaf and refuses each change, adds and drops keys, checks JSON types (`true` is not `1`, `1` is not `1.0`), holds the rulings record to every decision, and holds the TO VERIFY appendix complete. The employer-DC, identity and registered-script tests now expect the materialized block, and the specification-revision test holds lines before §15 to draft 3 (except the version and status lines), §15's lines through 1185 to draft 3 except where the block was materialized, and §16 to draft 3's text at its new position. - §17 items 5, 8, 9 and 10 and §20 steps 5 and 6 record the work; the header gives the new version and status; the new appendix maps each prose TO VERIFY item to its resolution. - No registry changed, so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed, no model or estimator was run on real data, no PSID file was read, and no file on the restricted list was opened for reading. Reading and exposure are recorded in `docs/design/u2_m2_exposure.md` (§20 steps 4–6 section). +- After the independent review of `690a1276` (Claude Opus 5.5 on a review lane, Subfleet job `20261021-105010-u2-step5-review-r1b`, REQUEST CHANGES; records in `EV/rulings-records-20261021-u2-step5/`): + - **Finding 1 (blocking).** The d1597 entry now records both uses of the structure-only U1 load: before the first step-4 pass, on 2026-10-19 at `640adccf`, and before the second, on 2026-10-21 at `1e739d32`. Each matched the pin, as d1724's decision record states. §20 steps 4 and 5 say so, and every authorization's uses are now a list. + - **Finding 2.** The block reader refuses a JSON key repeated at any level, which `json.loads` would otherwise resolve silently to the last value. + - **Finding 3.** The block takes the employer-DC persons and previous plans from the reader (`sources.DC_PERSONS` and `DC_PLANS`), the two `overlap_2012` values from the parameter loaders, and refuses to name a script that does not exist. + - **Finding 4.** The reader's path resolves through a symlinked checkout. + - **Findings 5 and 7.** New tests cover the preflight's rulings check and the dry run's specification checks (now one helper, `specification_checks`), B2's person count (d1242), unresolved registry entries and `-0.0`. + - **Finding 6.** The U1 differential report is committed at this revision's last code change (§20 step 6). + - **Finding 8.** The appendix names the 2013 and 2015 code-92 entries, and its test checks every registry entry and `income_concept` path the appendix cites. + - **Finding 9.** The exposure record names the coordinating session's messages. + - **Finding 10.** §15 says which ruling `decisions.ruled_on` dates, and §20's status line names the steps done and pending. ## 20. Ratification and execution record -**Pending.** +**Steps 1–5 done; step 6 is this revision's merge; steps 7–12 pending.** 1. Resolve the proposed decisions and source-verification blockers. **Decisions: done (Max, d514, 2026-09-28, adopting every §16 default).** Source-verification blockers remain for steps 2–5. **`u2-draft-4` amendments 1–6 (§16a): ruled by Max on 2026-09-30 (d637; §16b).** B3 is resolved by option 1. B1 and B2 were held until PSID answered; **Max ruled on them on 2026-10-10 (d1090; §16c)**: B1 by option 2 and B2 by option 1, each an exclusion under a named disposition. Max confirmed the reading of amendment 1's parts 1a–1c in §16b on 2026-10-12 (d745), so this step is done. 2. Complete implementation, manifests and parameter capture. **Done (2026-10-16).** Implementation: milestone 2 (#487) and its three review rounds, the last landing as #525 (`f7a84d0b`); and #544, which records with the frame digest the pandas version and string inference setting it was taken under and refuses a frozen load or registered run under any other (`test_u2_loader.py::test_any_other_frame_environment_refuses_the_registered_check`, `::test_a_frozen_load_refuses_another_frame_environment`). Manifests: the documentary source records, 1,766 registry entries and 26 SSI and Census records, are all RESOLVED, none REFUSED or TO VERIFY (`test_adjudication_applied.py::test_status_counts_after_independent_adjudication`), and the loader's source preflight passes (`test_u2_mappings.py::test_source_preflight_passes_at_the_committed_registries`; #524, #540). A RESOLVED record is a documentary finding; reconciliation on real records is step 4's. Parameter capture: the U2 SSI capture, the Track M threshold capture and U1's inherited life tables, each pinned by hash in the code (§17 item 5; `test_u2_parameters.py::test_threshold_capture_is_the_pinned_track_m_file`; `parameters._LIFE_TABLE_PINS`). The final parameter block is step 5's. 3. Complete independent mapping review, invented tests, historical-isolation checks and exact U1 differential evidence. **Done (2026-10-16).** Independent mapping review: the independent adjudication of milestone 1's registries (ERRORS FOUND); milestone 1b applied its corrections in #486, whose independent review requested changes on 11 findings and whose blind verifier then returned U2-M1B: VERIFIED (`test_adjudication_applied.py::test_dispositions_match_the_adjudication`; §17). Every later registry change had an independent review before it merged: the d1090 rounds (§19), ending in APPROVE at round 4 (`e7a558f`) and at #524's merged head (`742590b`; #524's merge audit); the d637 and d745 commit reviews, the last a delta review, APPROVE (§19); and #540's gating review, an Opus 5.5 review with a shell, APPROVE (#540's merge audit). Invented tests and historical isolation: the milestone-2 report's §13 and §14 audit (#487, #525), closed by three review rounds and a fourth-pass mutation run in which a test kills every mutant; §13's optional structure-only U1 load needs Max's approval and is not run. Exact U1 differential evidence: §17 item 7. -4. Complete the authorized pre-registration structural, reconciliation and F17 component pass; resolve mapping failures. The pass reads real PSID records, so it needs Max's authorization, recorded as a chief-of-staff decision. `scripts/track_u2_structure.py` and `scripts/track_u2_component_diagnostics.py` refuse to run without `--authorized-by `, exiting non-zero before any PSID path is resolved or opened, and the loader refuses an unfrozen load without the same record. The flag records the decision id, the git head and the UTC time in the pass's output; it only records the authorization and does not grant it. **First pass (d1649): stopped.** It ran once, on 2026-10-19 at `640adccf`, and stopped in the birth law without writing any output; the component diagnostics were not run (§16d). **Max's ruling of 2026-10-19 (d1724; §16d)** authorizes one more pass, structure and then component diagnostics, under d1649's scope with `--authorized-by d1724`, once the §16d change merges and master's Tests pass at the run head. **Second pass (d1724): completed.** It ran once, on 2026-10-21 at `1e739d32` (`1e739d328b73c99d259a72952b3452546721bfb8`, the merge of #552), after master's Tests run 39502262845 passed at that head. The d1597 structure-only U1 load (§13) ran first and passed: the staged inputs hash to U1's registered input-frame pin. `scripts/track_u2_structure.py` and then `scripts/track_u2_component_diagnostics.py`, each with `--authorized-by d1724`, exited 0, with no refusal, failure or mapping failure. Every family income identity and every WEALTH1 identity reconciles in all six support waves, so no reconciliation failure category is recorded, and the structural counts of rows U0 and U1 both carry §16d's disclosure keys (`n_seed_birth_year_counted_out`, with its four reasons, and `n_seed_birth_year_reseeded`). The outputs stay outside the repository, in `EV/track-u2-step4-pass-20261021-run2/`, restricted except its `MANIFEST.md` (SHA-256 `acf5d8a4252e88b3bbe51581943f1e3b25ba73f205a8240e8e45b9ca538b5e6d`); the first pass's record is `EV/track-u2-step4-pass-20261019/MANIFEST.md` (`1e91407b…a366fa`). This record states no count, value or other statistic from either pass. Under d1724 no further real-data run of either script is authorized. This step is done. +4. Complete the authorized pre-registration structural, reconciliation and F17 component pass; resolve mapping failures. The pass reads real PSID records, so it needs Max's authorization, recorded as a chief-of-staff decision. `scripts/track_u2_structure.py` and `scripts/track_u2_component_diagnostics.py` refuse to run without `--authorized-by `, exiting non-zero before any PSID path is resolved or opened, and the loader refuses an unfrozen load without the same record. The flag records the decision id, the git head and the UTC time in the pass's output; it only records the authorization and does not grant it. **First pass (d1649): stopped.** It ran once, on 2026-10-19 at `640adccf`, and stopped in the birth law without writing any output; the component diagnostics were not run (§16d). **Max's ruling of 2026-10-19 (d1724; §16d)** authorizes one more pass, structure and then component diagnostics, under d1649's scope with `--authorized-by d1724`, once the §16d change merges and master's Tests pass at the run head. **Second pass (d1724): completed.** It ran once, on 2026-10-21 at `1e739d32` (`1e739d328b73c99d259a72952b3452546721bfb8`, the merge of #552), after master's Tests run 39502262845 passed at that head. The d1597 structure-only U1 load (§13) ran first and passed: the staged inputs hash to U1's registered input-frame pin. It had also run, and passed, before the first pass, on 2026-10-19 at `640adccf`, as d1724's decision record states. `scripts/track_u2_structure.py` and then `scripts/track_u2_component_diagnostics.py`, each with `--authorized-by d1724`, exited 0, with no refusal, failure or mapping failure. Every family income identity and every WEALTH1 identity reconciles in all six support waves, so no reconciliation failure category is recorded, and the structural counts of rows U0 and U1 both carry §16d's disclosure keys (`n_seed_birth_year_counted_out`, with its four reasons, and `n_seed_birth_year_reseeded`). The outputs stay outside the repository, in `EV/track-u2-step4-pass-20261021-run2/`, restricted except its `MANIFEST.md` (SHA-256 `acf5d8a4252e88b3bbe51581943f1e3b25ba73f205a8240e8e45b9ca538b5e6d`); the first pass's record is `EV/track-u2-step4-pass-20261019/MANIFEST.md` (`1e91407b…a366fa`). This record states no count, value or other statistic from either pass. Under d1724 no further real-data run of either script is authorized. This step is done. 5. Materialize the final parameter block and U2 rulings record; require exact code/specification agreement. **Done (2026-10-21).** §15's block is the final contract, version `u2-ratified-1` with status `ratified_frozen`, and `rows.expected_specification_block()` states the same block from the code: the identity, plans and support waves; each wave's weight, family-unit and WEALTH1 variables, read from the committed registries; the income-concept, annuity, threshold, cut and SSI parameters and their capture pins; the cells, floor, design and memo settings; the rows, entry points and labels; and the rulings record. `rows.check_block_against_code` refuses a block that differs at any key, list length, JSON type or value and names every differing path. The registered run's preflight and the dry run apply it, and `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code and refuses a change to each of its leaves. In detail: - **The three `TO_VERIFY` keys.** `ssi.parameters.sha256` pins the U2 SSI capture, `a58d55c1…3762`, as the code already did (§17 item 5). `income_concept.employer_dc.reader` names the U2 adapter, `uniform_cut_track_u2/sources.py`, whose `employer_dc_balances` applies each wave's registry route (`dc_route`); U1's key names its own reader the same way. `later_wave_mapping_status` records that every pension-registry entry is RESOLVED under amendment 5, which the code checks before it states the block. - **Amendment 5's fragment.** `previous_routes` is the fragment §16a printed and d637 ratified, equal to `sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS` and to the 2015 refused branch the pension registry records. The registries cite §15's lines 1173 and 1185, so both keep their draft-3 number and text: the fragment fills lines 1174–1184 in place of draft 3's generic route, and the four keys that stood there follow line 1185 unchanged except `later_wave_mapping_status`. `population.relationship_rules` now names amendment 1 with §3's role amendment. - **`blocked_by` is empty.** Each draft-3 blocker is resolved: later-wave component and role adjudication, and the mapping manifests and independent review, by the RESOLVED registries and their reviews (steps 2 and 3); 2015 male code 20 by d1090's B1 exclusion (§16c); code-90 and code-92 routing by amendment 1a and 1b (d637, confirmed by d745) and d1090's B2 exclusion; later-wave employer-DC routing by amendment 5 (d637); the SSI capture by its verification and this pin (§17 item 5); cohort isolation and identity guards, and the invented dry run and U1 differential, by milestone 2 (§17 items 6 and 7); the pre-registration pass by step 4; and ratification by this revision's merge (step 6). The fresh forecast and the registration, which that last blocker also named, are steps 7 and 8: the registered run's preflight refuses without the issue #42 registration and its binding, and without the committed pre-registration evidence, so an empty `blocked_by` authorizes no run. - - **U2's rulings record.** `decisions` holds `rows.U2_RULINGS` with `ruled_by` Max and `ruled_on` 2026-09-28: d514's eleven entries, unchanged, which adopt every §16 ruling; d637's entries for amendments 1a–1c (confirmed by d745), 2, 3, 4, 5a, 5b and 6 and for the three PSID questions; d1090's entries for B1, B2 and amendment 5c; and one entry each for d1242 (B2's counting unit), d1597 (the structure-only U1 load), d1649 (the step-4 pass) and d1724 (the birth law's support). Each registry-applied entry names the registry entries it governs and copies their ruling, built from the registry module's own scopes, and `rows.check_rulings_against_code` holds the record to the committed registries, the role rules and the cohort code. The d1597, d1649 and d1724 entries record that each authorization was used once, with the head, date and value-free result. + - **U2's rulings record.** `decisions` holds `rows.U2_RULINGS` with `ruled_by` Max and `ruled_on` 2026-09-28: d514's eleven entries, unchanged, which adopt every §16 ruling; d637's entries for amendments 1a–1c (confirmed by d745), 2, 3, 4, 5a, 5b and 6 and for the three PSID questions; d1090's entries for B1, B2 and amendment 5c; and one entry each for d1242 (B2's counting unit), d1597 (the structure-only U1 load), d1649 (the step-4 pass) and d1724 (the birth law's support). Each registry-applied entry names the registry entries it governs and copies their ruling, built from the registry module's own scopes, and `rows.check_rulings_against_code` holds the record to the committed registries, the role rules and the cohort code. The d1597, d1649 and d1724 entries record each use of their authorization, with the head, date and value-free result: d1597's load ran before each of the two passes, and d1649's and d1724's passes ran once each. - **The pre-registration evidence pin.** The step-4 pass wrote `track-u2-preregistration-evidence.json`, the record of PSID file and frame hashes that the registered run rechecks; the restricted list keeps it unopened here. The pass's `MANIFEST.md` pins its SHA-256, `7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845`. §15's `preregistration_evidence` and `loader.PREREGISTRATION_EVIDENCE_SHA256` now pin the same value, so `loader.read_preregistration_evidence`, which the registered preflight and every frozen load call, refuses any file at `docs/design/u2_preregistration_evidence.json` whose bytes differ, before it parses it. A separate lane commits the file there, byte for byte, before step 8; this revision does not touch it. - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. @@ -2349,14 +2372,14 @@ Registration must state that U0 observes five birth years at exact age 67; row U ## Appendix: TO VERIFY item → where resolved -§§2–14 keep their draft-3 text and line numbers, because the registries cite those lines and the tests pin them. These are the lines there that still say **TO VERIFY**, with §17's closing line, and where each item was resolved (§20 step 5). Line numbers are this file's, which equal draft 3's below §15. +§§2–14 keep their draft-3 text and line numbers, because the registries cite those lines and the tests pin them. These are the lines there that still say **TO VERIFY**, with §17's closing line, and where each item was resolved (§20 step 5). Line numbers are this file's; before §15 they equal draft 3's. | Line | Item left open in draft 3 | Resolution | Evidence | |---:|---|---|---| | 59 | Later SSI capture: not yet created | Created and pinned: `data/external/track_u2_ssi_parameters_2012_2022.json`, SHA-256 `a58d55c1…3762`, in `parameters.SSI_SHA256` and §15 `ssi.parameters.sha256` (§17 item 5) | `test_ssi_capture.py::test_capture_manifest_hashes_and_bytes`, `::test_capture_reproduces_exactly_offline` | | 60 | COLA notices: downloaded bytes and parameter cross-checks | 106 sources staged with URL, retrieval time, SHA-256 and path in `tests/data/track_u2/ssi_sources/manifest.json`; each year's federal benefit rates checked against its notice, and the CFR provisions against their historical editions | `test_ssi_capture.py::test_source_manifest_schema_and_hashes`, `::test_fbr_independently_matches_official_notice`, `::test_2017_actual_notice_and_2018_corrected_republication`, `::test_historical_cfr_verification_and_parameter_crosscheck` | | 64 | Unverified items block registration where required | All 1,766 registry entries are RESOLVED and the loader's source preflight passes (§20 step 2) | `test_adjudication_applied.py::test_status_counts_after_independent_adjudication`; `test_u2_mappings.py::test_source_preflight_passes_at_the_committed_registries` | -| 152 | Code 92's OFUM income routing | 2017: documented route, amendment 1a (d637, confirmed by d745). 2019–2023: d1090's B2 exclusion (§16c). 2013 and 2015: the code is absent and refused as absent | `roles:2017.relationship.92`, `roles:2019.relationship.92` and the 2021 and 2023 entries; §15 `decisions.amendment_1a_documented_routes`, `.blocker_b2_codes_90_92_2019_2023` | +| 152 | Code 92's OFUM income routing | 2017: documented route, amendment 1a (d637, confirmed by d745). 2019–2023: d1090's B2 exclusion (§16c). 2013 and 2015: the code is absent and refused as absent | `roles:2013.relationship.92` and `roles:2015.relationship.92` (both refuse the absent code), `roles:2017.relationship.92`, `roles:2019.relationship.92` and the 2021 and 2023 entries; §15 `decisions.amendment_1a_documented_routes`, `.blocker_b2_codes_90_92_2019_2023` | | 154 | Code 90's OFUM income routing | 2013: inherited meaning, amendment 1b. 2015 and 2017: documented routes, amendment 1a (d637, confirmed by d745). 2019–2023: d1090's B2 exclusion | `roles:.relationship.90`; §15 `decisions.amendment_1b_2013_inherited_meanings`, `.amendment_1a_documented_routes`, `.blocker_b2_codes_90_92_2019_2023` | | 160 | Family-file routing of a male code-20 person in 2015 (blocker B1) | d1090's B1 exclusion, `male code 20 in 2015 family unit`, ratified as an amendment to this line (§16c) | `roles:2015.relationship.20`; §15 `decisions.blocker_b1_male_code_20_2015` | | 190 | Code 88's routing and use beyond administrative birth support | Amendment 1c's first-year cohabitor convention (d637, confirmed by d745) | `roles:.relationship.88`; §15 `decisions.amendment_1c_first_year_cohabitor` | diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index ae99be993..bf7a345b0 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -383,6 +383,9 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - `EV/RESTRICTED-FILES.md`, in full and first, through a saved copy of the tool output in this session's own transcript folder. No other session's file in that project folder was opened. The harness also auto-loaded that project's memory index (`MEMORY.md`, one-line pointers); no memory file was opened. - `EV/track-u2-step4-pass-20261021-run2/MANIFEST.md`, which the restricted list allows to be read and cited, and its SHA-256, which matched `acf5d8a4…e6d`. No other file in that folder and nothing in `EV/track-u2-step4-pass-20261019/` was opened or listed. - The chief-of-staff decision ledger's entries for d514, d637, d745, d1090, d1242, d1597, d1649 and d1724 (decision, ruling, ruling time and notes), selected by id, and the names in its folder. +- On 2026-10-22, to check the review's finding 1: the field names of the d1597, d1649 and d1724 ledger records, and the d1649 `ruling` and d1724 `why` fields, which state that the first pass (2026-10-19, `640adccf`) passed d1597's U1 load. +- The coordinating session's brief, the message that started this lane, and its later messages of 2026-10-21 and 2026-10-22. They carry value-free facts only: the step-4 outcomes this lane records, the evidence record's SHA-256 as its MANIFEST pins it, and the tier-slot queue. +- The review lane's report, `EV/rulings-records-20261021-u2-step5/review-r1b-out.md`, and the names in the evidence folder that match `rulings-records` (names only). - The repository at `1e739d32`: `CLAUDE.md`; the specification in full; the U1 specification's header, §15 block and §20; the U2 package, scripts and tests named in the PR; `data/external/track_u2/u1_identity.json` and the registries' entry ids and fields; the U2 SSI capture's provenance fields and its source manifest's records; `tests/README-tiers.md`; Track M's `specification.py` header; `estimates/cola_age_profile.specification_unratified_fields`. Also `~/PolicyEngine/CLAUDE.md`. **Not opened:** any other evidence-folder path; any comparator folder, seal or Boomers Report copy; `runs/track_b_b2_*`, `docs/design/track_b_attempt_ledger.md` and `paper/paper.qmd`; other sessions' transcripts or scratchpads; `~/.subfleet/` other than this session's own review jobs; any PSID file or codebook. @@ -390,3 +393,14 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses **Ran:** the U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to an empty directory, which stayed empty, so the checks that need PSID documentation skipped; Black and Ruff; the tier recount by collection; and the mutation checks listed in the PR. Every block, rulings and appendix check reads only the specification, the committed registries and the code. **Values:** it stated and inferred no 1946–55 value or direction. Every number it wrote is a hash, a commit, a CI run id, a date, a line number, a registry entry count, a test count or a constant of the code or the specification. + +**Review lanes.** +- **Subfleet `20261021-104325-u2-step5-review-r1`.** It waited for capacity and was cancelled before it launched, so it read nothing. +- **Subfleet `20261021-105010-u2-step5-review-r1b`.** It reviewed `690a1276` and returned REQUEST CHANGES. It ran on Claude Opus 5.5, in a standalone clone (`~/PolicyEngine/_clones/md-u2-step5-review-20261021`) with its own virtual environment. + - **What it read outside the clone:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, and the eight decision-ledger records selected by id. + - **Its runs:** the test suites with the empty PSID directory, which stayed empty, and its own U1 differential in clones under `/tmp/u2rev-diff`. + - **Its two disclosures:** + - One early command wrote git-ignored bytecode caches under the review clone's `src/`. + - Two `grep` searches over `docs/design/` scanned the bytes of `track_b_attempt_ledger.md` for fixed literals, with nothing from that file printed. That file is restricted for B2-successor work, not for U2; this record lists it for completeness. + - It opened no other evidence path, PSID file or comparator. Its findings were addressed in the next commit (§19). + diff --git a/scripts/track_u2_dry_run.py b/scripts/track_u2_dry_run.py index aac9a8e2b..7ae97fa64 100644 --- a/scripts/track_u2_dry_run.py +++ b/scripts/track_u2_dry_run.py @@ -870,13 +870,33 @@ def income(built=built, variant=variant): return out +def specification_checks( + registries: sources.RegistrySet, +) -> dict[str, Any]: + """The specification's checks against the code (sections 12 and 15): + rows, named deltas, the rulings record against the block and against + the code and registries, and the whole block against the code. Any + difference raises, so the dry run fails rather than records it.""" + + block = rows.specification_block() + return { + "specification_rows": rows.check_rows_against_block(block), + "named_deltas": rows.check_named_deltas_against_specification(), + "u2_rulings_against_block": rows.check_u2_rulings_against_block(block), + "u2_rulings_against_code": rows.check_rulings_against_code(registries), + "specification_block_against_code": rows.check_block_against_code( + block, registries + ), + } + + def checks( seed: int, inputs: cohort.U2Inputs, params: parameters.U2Parameters ) -> dict[str, Any]: """The dry run's checks, each recorded (none computes a PSID value).""" - block = rows.specification_block() registries = sources.RegistrySet.committed() + specification = specification_checks(registries) registry_roles = sources.RoleContext.from_registry(registries) declared = sources.RoleContext.declared() census = parameters.load_u2_thresholds() @@ -918,13 +938,7 @@ def checks( ) code88 = invented.with_code_88_cohabitor(inputs) return { - "specification_rows": rows.check_rows_against_block(block), - "named_deltas": rows.check_named_deltas_against_specification(), - "u2_rulings_against_block": rows.check_u2_rulings_against_block(block), - "u2_rulings_against_code": rows.check_rulings_against_code(registries), - "specification_block_against_code": rows.check_block_against_code( - block, registries - ), + **specification, "plans": { "u0_pairs": len(cohort.observation_plan(cohort.U2CohortSpec())), "u1_pairs": len( diff --git a/src/populace_dynamics/uniform_cut_track_u2/rows.py b/src/populace_dynamics/uniform_cut_track_u2/rows.py index 16795ca3a..8d2aaca05 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/rows.py +++ b/src/populace_dynamics/uniform_cut_track_u2/rows.py @@ -554,16 +554,27 @@ def _excluded(disposition: str, **record: Any) -> dict[str, Any]: "decision_record": "d1242", "ruled_on": "2026-10-16", }, - # d1597 (2026-10-16): section 13's optional structure-only U1 load. + # d1597 (2026-10-16): section 13's optional structure-only U1 load, + # run before each step-4 pass (d1649's condition, which d1724's rerun + # keeps). "u1_input_pin_check": { - "ruling": "run_the_optional_structure_only_u1_load_once", + "ruling": "run_the_optional_structure_only_u1_load_before_step_4", "u1_input_frames_sha256": _U1_INPUT_FRAMES_SHA256, "statistic_computed": None, - "authorization_used": { - "on": "2026-10-21", - "head": _STEP_4_SECOND_HEAD, - "result": "pin_matched", - }, + "authorization_used": [ + { + "on": "2026-10-19", + "head": _STEP_4_FIRST_HEAD, + "before": "the_d1649_pass", + "result": "pin_matched", + }, + { + "on": "2026-10-21", + "head": _STEP_4_SECOND_HEAD, + "before": "the_d1724_pass", + "result": "pin_matched", + }, + ], "specification_section": "13", "decision_record": "d1597", "ruled_on": "2026-10-16", @@ -586,11 +597,13 @@ def _excluded(disposition: str, **record: Any) -> dict[str, Any]: "the_preregistration_evidence_pandas_version_and_string_" "inference_setting" ), - "authorization_used": { - "on": "2026-10-19", - "head": _STEP_4_FIRST_HEAD, - "result": "stopped_in_the_birth_law_without_output", - }, + "authorization_used": [ + { + "on": "2026-10-19", + "head": _STEP_4_FIRST_HEAD, + "result": "stopped_in_the_birth_law_without_output", + } + ], "specification_section": "20", "decision_record": "d1649", "ruled_on": "2026-10-16", @@ -607,12 +620,14 @@ def _excluded(disposition: str, **record: Any) -> dict[str, Any]: "count_decides": "nothing", "estimates_career": "unchanged", "widening_approved_on": "2026-10-20", - "authorization_used": { - "on": "2026-10-21", - "head": _STEP_4_SECOND_HEAD, - "tests_run": _STEP_4_SECOND_TESTS_RUN, - "result": "completed", - }, + "authorization_used": [ + { + "on": "2026-10-21", + "head": _STEP_4_SECOND_HEAD, + "tests_run": _STEP_4_SECOND_TESTS_RUN, + "result": "completed", + } + ], "specification_section": "16d", "decision_record": "d1724", "ruled_on": "2026-10-19", @@ -633,12 +648,25 @@ def _excluded(disposition: str, **record: Any) -> dict[str, Any]: _BLOCK_SECTION = "\n## 15. Machine-readable parameter block" +def _no_repeated_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + """A JSON object, refused if it names a key twice: ``json.loads`` + would keep the last silently, so an earlier draft value could stand + in the text while the check saw the later one.""" + + keys = [key for key, _ in pairs] + repeated = sorted({key for key in keys if keys.count(key) > 1}) + if repeated: + raise ValueError(f"the section 15 block repeats keys {repeated}") + return dict(pairs) + + def specification_block( path: Path = identity.SPECIFICATION_PATH, ) -> dict[str, Any]: """The U2 specification's machine-readable JSON block (section 15). - Only section 15 is searched, and it must hold exactly one JSON block. + Only section 15 is searched, and it must hold exactly one JSON block, + in which no object may name a key twice. Other sections may quote JSON fragments (``u2-draft-4``'s section 16a proposes a replacement ``previous_routes`` fragment); a proposal is not the contract, so it is never read as the block. @@ -659,7 +687,7 @@ def specification_block( f"{path} section 15 must hold exactly one JSON block, found " f"{len(blocks)}" ) - block = json.loads(blocks[0]) + block = json.loads(blocks[0], object_pairs_hook=_no_repeated_keys) identity.refuse_u1_identity( {"specification": block.get("specification")}, what="the specification block", @@ -878,6 +906,14 @@ def _relative(path: Path) -> str: return str(Path(path).resolve().relative_to(identity.ROOT)) +def _existing(relative: str) -> str: + """``relative``, refused unless it names a file in the repository.""" + + if not (identity.ROOT / relative).is_file(): + raise ValueError(f"the block names {relative}, which does not exist") + return relative + + def _registry_variable( entries: Mapping[str, Mapping[str, Any]], entry_id: str ) -> str: @@ -940,6 +976,8 @@ def expected_specification_block( entry["wave"] for entry in pension if entry["id"] in refused["entries"] } reader = inspect.getsourcefile(sources.employer_dc_balances) + thresholds = parameters.load_u2_thresholds() + ssi = parameters.load_u2_ssi_parameters() return { "specification": identity.SPECIFICATION_ID, "target_id": identity.TARGET_ID, @@ -1053,16 +1091,16 @@ def expected_specification_block( "employer_dc": { "row": "U7", "reader": str( - Path(reader).relative_to( - identity.ROOT / "src" / "populace_dynamics" - ) + Path(reader) + .resolve() + .relative_to(identity.ROOT / "src" / "populace_dynamics") ), - "persons": ["head", "wife"], + "persons": list(sources.DC_PERSONS), "role_meaning": ( "reference_person_and_designated_spouse_income_slot" ), "current_job": "account_amount_when_plan_type_has_an_account", - "previous_plans": [1, 2], + "previous_plans": list(sources.DC_PLANS), "previous_routes": { "both_items": ["both"], "account_items_by_wave": { @@ -1118,7 +1156,7 @@ def expected_specification_block( "file": _relative(parameters.THRESHOLDS_PATH), "sha256": parameters.THRESHOLDS_SHA256, }, - "overlap_2012": "exact_equality_with_u1_capture", + "overlap_2012": thresholds.provenance["overlap_2012"], "poor_if": "income_below_threshold", }, "cut": { @@ -1138,7 +1176,7 @@ def expected_specification_block( "years": list(parameters.SSI_YEARS), "checked_against": "section_8_federal_register_cola_notices", "revision_rule": "refuse_missing_empty_or_unknown", - "overlap_2012": "all_seven_parameters_equal_u1", + "overlap_2012": ssi.provenance["overlap_2012"], }, }, "statistic": { @@ -1205,12 +1243,12 @@ def expected_specification_block( "wealth1_published": None, }, "entry_points": { - "dry_run": "scripts/track_u2_dry_run.py", - "registered_run": "scripts/run_track_u2_registered.py", + "dry_run": _existing("scripts/track_u2_dry_run.py"), + "registered_run": _existing("scripts/run_track_u2_registered.py"), "registered_artifact": _relative(identity.ARTIFACT_PATH), "environment": _relative(identity.SIDECAR_PATH), - "structure": "scripts/track_u2_structure.py", - "component_diagnostics": ( + "structure": _existing("scripts/track_u2_structure.py"), + "component_diagnostics": _existing( "scripts/track_u2_component_diagnostics.py" ), }, @@ -1218,7 +1256,7 @@ def expected_specification_block( "file": _relative(loader.PREREGISTRATION_EVIDENCE_PATH), "sha256": loader.PREREGISTRATION_EVIDENCE_SHA256, "schema_version": loader.PREREGISTRATION_SCHEMA, - "written_by": "scripts/track_u2_structure.py", + "written_by": _existing("scripts/track_u2_structure.py"), "authorized_by": U2_RULINGS["seed_birth_year_support"][ "decision_record" ], diff --git a/src/populace_dynamics/uniform_cut_track_u2/sources.py b/src/populace_dynamics/uniform_cut_track_u2/sources.py index b6f4fc01a..34b409b51 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/sources.py +++ b/src/populace_dynamics/uniform_cut_track_u2/sources.py @@ -151,6 +151,8 @@ "WEALTH_CONCEPTS", "DC_BALANCE_COLUMNS", "DC_CODE_DOMAINS", + "DC_PERSONS", + "DC_PLANS", "DcRoute", "FamilyUnitExclusion", "FieldSpec", @@ -393,8 +395,10 @@ _SLOT_FLAG_CONCEPTS = frozenset({"wife_age"}) _COUNTED_DISPOSITION = 3 _EXCLUDED_IRA_DISPOSITION = 2 -_DC_PERSONS = ("head", "wife") -_DC_PLANS = (1, 2) +#: The employer-DC persons and previous-employer plans the reader walks +#: (section 15 ``employer_dc.persons`` and ``previous_plans``). +DC_PERSONS: tuple[str, ...] = ("head", "wife") +DC_PLANS: tuple[int, ...] = (1, 2) _AGE_ACTUAL = (14, 120) _AGE_NA = 999 _NO_SPOUSE = 0 @@ -1315,9 +1319,9 @@ def pension_field_specs(wave: int, gate: SourceGate) -> list[FieldSpec]: """Fixed-width specs of the employer-DC items (row U7).""" specs = [] - for person in _DC_PERSONS: + for person in DC_PERSONS: names = [f"{person}_current_type", f"{person}_current_amount"] - for plan in _DC_PLANS: + for plan in DC_PLANS: names += [ f"{person}_prev{plan}_type", f"{person}_prev{plan}_combo_disposition", @@ -1819,7 +1823,7 @@ def decoded(concept: str) -> tuple[np.ndarray, ...]: value = np.where(reported, amount, 0).astype(np.int64) return amount, value, reported, dk, amount == codes["top"] - for person in _DC_PERSONS: + for person in DC_PERSONS: amount, value, reported, dk, topped = decoded( f"{person}_current_amount" ) @@ -1830,7 +1834,7 @@ def decoded(concept: str) -> tuple[np.ndarray, ...]: unreported += account & dk top += account & topped off_route += ~account & (amount != 0) - for plan in _DC_PLANS: + for plan in DC_PLANS: plan_type = raw[f"{person}_prev{plan}_type"].to_numpy( dtype=np.int64 ) diff --git a/tests/track_u2/test_adjudication_applied.py b/tests/track_u2/test_adjudication_applied.py index 4abfc6ae7..b2a7114f8 100644 --- a/tests/track_u2/test_adjudication_applied.py +++ b/tests/track_u2/test_adjudication_applied.py @@ -1331,6 +1331,12 @@ def test_section_16a_recommendations_and_options(): "U7's 2015 spouse slot is the documented female spouse or partner " "slot, and B1's exclusion applies to U7 as to every other row |", } +#: Section 20's status line, which section 20 step 5 rewrote from +#: "**Pending.**" (review of 690a1276, finding 10). +STATUS_20 = ( + "**Steps 1\N{EN DASH}5 done; step 6 is this revision's merge; steps " + "7\N{EN DASH}12 pending.**" +) #: The d1090 record, complete, by where it stands: every line of section #: 16c, the header lines, section 17 items 1, 2 and 8 and section 20's #: status and step 1. Each must be a whole line there, exactly once, and @@ -1555,7 +1561,7 @@ def test_section_16a_recommendations_and_options(): "of them.", ], "execution": [ - "**Pending.**", + STATUS_20, "1. Resolve the proposed decisions and source-verification " "blockers. **Decisions: done (Max, d514, 2026-09-28, adopting every " "\N{SECTION SIGN}16 default).** Source-verification blockers remain " @@ -1936,7 +1942,7 @@ def test_the_d745_record_catches_each_mutation(old, new): ), ( "execution", - "**Pending.**", + STATUS_20, "**Registered and authorized for execution.**", ), ("record", "is not known", "is known to be none"), @@ -2057,8 +2063,8 @@ def test_the_d745_record_catches_each_mutation(old, new): # Insertions after pinned lines (review of e7a558f, info finding 2). ( "execution", - "**Pending.**", - "**Pending.**\n\n**Current status: registered and authorized.**", + STATUS_20, + STATUS_20 + "\n\n**Current status: registered and authorized.**", ), ( "outstanding", diff --git a/tests/track_u2/test_u2_scripts.py b/tests/track_u2/test_u2_scripts.py index d4a834166..ff66f748e 100644 --- a/tests/track_u2/test_u2_scripts.py +++ b/tests/track_u2/test_u2_scripts.py @@ -773,6 +773,45 @@ def test_preflight_passes_the_committed_specification(registered): } +def test_preflight_refuses_rulings_the_code_does_not_make( + registered, monkeypatch +): + """Review of 690a1276, mutant G2: the preflight applies + ``check_rulings_against_code``; without B2's person count (d1242) + the committed rulings record no longer agrees with the code.""" + from populace_dynamics.uniform_cut_track_u2 import sources + + monkeypatch.setattr( + sources, "PERSON_COUNTED_EXCLUSIONS", (sources.MALE_CODE_20_2015,) + ) + with pytest.raises(ValueError, match="d1242"): + _preflight(registered) + + +def test_the_dry_run_applies_the_specification_checks(monkeypatch): + """Review of 690a1276, mutants H and Q: the dry run's specification + checks pass the committed state and raise on a block or rulings + record that differs from the code.""" + from populace_dynamics.uniform_cut_track_u2 import sources + + module = _script("track_u2_dry_run") + registries = sources.RegistrySet.committed() + passed = module.specification_checks(registries) + assert passed["specification_block_against_code"]["block_equals_code"] + assert passed["u2_rulings_against_code"]["rulings_equal_the_code"] + changed = _ratified_block() + changed["threshold"]["poor_if"] = "income_at_or_below_threshold" + with monkeypatch.context() as patch: + patch.setattr(rows, "specification_block", lambda *a, **k: changed) + with pytest.raises(ValueError, match="threshold.poor_if"): + module.specification_checks(registries) + monkeypatch.setattr( + sources, "PERSON_COUNTED_EXCLUSIONS", (sources.MALE_CODE_20_2015,) + ) + with pytest.raises(ValueError, match="d1242"): + module.specification_checks(registries) + + @pytest.mark.parametrize( "change, path", [ diff --git a/tests/track_u2/test_u2_specification_block.py b/tests/track_u2/test_u2_specification_block.py index 1f5e2af75..55ca34952 100644 --- a/tests/track_u2/test_u2_specification_block.py +++ b/tests/track_u2/test_u2_specification_block.py @@ -488,15 +488,16 @@ def test_the_to_verify_appendix_is_complete(spec_lines): assert table[-1][1].replace("TO VERIFY", "**TO VERIFY**") in closing -def test_the_appendix_evidence_exists(spec_lines): - """Each test the appendix cites exists, and each ``decisions`` or - ``income_concept`` key it names is in the block.""" +def test_the_appendix_evidence_exists(spec_lines, registries): + """Each test the appendix cites exists; each ``decisions`` key and + ``income_concept`` path it names is in the block; and each registry + entry it names (``roles:2017.relationship.92``) is RESOLVED.""" block = rows.specification_block() tests = { path.name: path.read_text(encoding="utf-8") for path in TESTS.glob("test_*.py") } - cited_tests = 0 + cited_tests = cited_paths = cited_entries = 0 for row in _appendix_rows(spec_lines): evidence = row[3] module = None @@ -512,4 +513,163 @@ def test_the_appendix_evidence_exists(spec_lines): ): name = key[0] or key[1] assert name in block["decisions"], name + for path in re.findall( + r"`(income_concept(?:\.[a-z0-9_]+)+)`", evidence + ): + _at(block, tuple(path.split("."))) + cited_paths += 1 + for name, entry_id in re.findall( + r"`([a-z]+):(\d{4}\.[a-z_]+(?:\.\d+)?)`", evidence + ): + (entry,) = ( + e for e in registries.entries(name) if e["id"] == entry_id + ) + assert entry["status"] == "RESOLVED", (name, entry_id) + cited_entries += 1 assert cited_tests >= 14 + assert cited_paths >= 1 + assert cited_entries >= 5 + + +# --------------------------------------------------------------------------- +# Review of 690a1276: the gaps its mutants and probes found +# --------------------------------------------------------------------------- +class _Altered: + """The committed registries with one entry's fields changed.""" + + def __init__(self, base, name: str, entry_id: str, **changes: Any): + self._base = base + self._name = name + self._id = entry_id + self._changes = changes + + def entries(self, name: str) -> list[dict]: + out = [dict(entry) for entry in self._base.entries(name)] + if name == self._name: + (entry,) = (e for e in out if e["id"] == self._id) + entry.update(self._changes) + return out + + +def test_a_repeated_key_is_refused(tmp_path): + """Finding 2: ``json.loads`` keeps the last of a repeated key, so a + draft value could stand in the text while the check saw the last.""" + text = identity.SPECIFICATION_PATH.read_text(encoding="utf-8") + top = ' "version": "u2-ratified-1",' + nested = ' "unreported_amount": "zero_counted",' + for old, new, key in ( + (top, ' "version": "u2-draft-4",\n' + top, "version"), + ( + nested, + ' "unreported_amount": "not_counted",\n' + nested, + "unreported_amount", + ), + ): + assert text.count(old) == 1 + path = tmp_path / f"spec-{key}.md" + path.write_text(text.replace(old, new), encoding="utf-8") + with pytest.raises(ValueError, match=f"repeats keys \\['{key}'\\]"): + rows.specification_block(path) + + +def test_dc_persons_and_plans_are_the_readers(monkeypatch, block, registries): + """Finding 3: the block's employer-DC persons and previous plans are + the ones the reader walks.""" + path = ("income_concept", "employer_dc") + monkeypatch.setattr(sources, "DC_PERSONS", ("head",)) + _refused(block, registries, (*path, "persons")) + monkeypatch.setattr(sources, "DC_PERSONS", ("head", "wife")) + monkeypatch.setattr(sources, "DC_PLANS", (1, 2, 3)) + _refused(block, registries, (*path, "previous_plans")) + + +def test_the_reader_resolves_through_a_symlink( + tmp_path, monkeypatch, block, registries +): + """Finding 4: a checkout reached through a symlink (macOS ``/tmp``) + names the same reader.""" + link = tmp_path / "linked-src" + link.symlink_to(ROOT / "src", target_is_directory=True) + linked = link / "populace_dynamics" / "uniform_cut_track_u2" / "sources.py" + assert linked.resolve() == Path(sources.__file__).resolve() + monkeypatch.setattr(rows.inspect, "getsourcefile", lambda _: str(linked)) + assert rows.check_block_against_code(block, registries)[ + "block_equals_code" + ] + + +def test_negative_zero_is_refused(block, registries): + """Finding 7: ``-0.0`` equals ``0.0`` under ``==`` but not as JSON.""" + path = ("income_concept", "annuity", "load") + assert _at(block, path) == 0.0 + _refused(_changed(block, path, -0.0), registries, path) + + +def test_a_named_script_must_exist(monkeypatch): + with pytest.raises(ValueError, match="does not exist"): + rows._existing("scripts/no_such_u2_script.py") + assert rows._existing("scripts/track_u2_structure.py") == ( + "scripts/track_u2_structure.py" + ) + + +@pytest.mark.parametrize( + "name, entry_id, message", + [ + ("pension", "2015.route.respondent_slots", "pension registry entry"), + ("individual", "2017.weight", "2017.weight is not RESOLVED"), + ("wealth", "wealth.2019.wealth1", "wealth1 is not RESOLVED"), + ("design", "common.stratum", "stratum is not RESOLVED"), + ], +) +def test_the_block_needs_resolved_registry_entries( + registries, name, entry_id, message +): + """Finding 5 (mutants M and N): the block is stated only from + RESOLVED registry entries.""" + altered = _Altered(registries, name, entry_id, status="TO_VERIFY") + with pytest.raises(ValueError, match=message): + rows.expected_specification_block(altered) + + +def test_the_rulings_need_resolved_registry_entries(registries): + """Finding 5 (mutant E): a named entry that is not RESOLVED refuses.""" + altered = _Altered( + registries, "roles", "2017.relationship.92", status="TO_VERIFY" + ) + with pytest.raises(ValueError, match="not a RESOLVED registry entry"): + rows.check_rulings_against_code(altered) + + +def test_b2s_person_count_must_be_made(monkeypatch, registries): + """Finding 5 (mutant C): d1242 needs B2 among the person counts.""" + monkeypatch.setattr( + sources, "PERSON_COUNTED_EXCLUSIONS", (sources.MALE_CODE_20_2015,) + ) + with pytest.raises(ValueError, match="d1242"): + rows.check_rulings_against_code(registries) + + +def test_each_authorization_records_every_use(): + """Finding 1: d1597's load ran before each step-4 pass; d1649's and + d1724's passes ran once each.""" + uses = { + key: rows.U2_RULINGS[key]["authorization_used"] + for key in ( + "u1_input_pin_check", + "preregistration_pass", + "seed_birth_year_support", + ) + } + assert [(u["on"], u["head"]) for u in uses["u1_input_pin_check"]] == [ + ("2026-10-19", rows._STEP_4_FIRST_HEAD), + ("2026-10-21", rows._STEP_4_SECOND_HEAD), + ] + assert {u["result"] for u in uses["u1_input_pin_check"]} == {"pin_matched"} + assert [u["head"] for u in uses["preregistration_pass"]] == [ + rows._STEP_4_FIRST_HEAD + ] + assert [u["head"] for u in uses["seed_birth_year_support"]] == [ + rows._STEP_4_SECOND_HEAD + ] + assert not rows.U2_RULINGS["u1_input_pin_check"]["ruling"].endswith("once") From 9f79818a30c1d4e13b310f1da0262e4f1cf1d0cf Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 21:47:26 -0400 Subject: [PATCH 05/12] =?UTF-8?q?U2=20=C2=A720=20step=206:=20record=20the?= =?UTF-8?q?=20U1=20differential=20at=20the=20last=20code=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/u2_u1_differential.py ran at 1742efb1 (this revision's last code change) against 9cee2423, on invented data with the PSID root pointed at an empty directory: equal in every check, the dry run included, with no loader file access. The report is docs/design/u2_m2_u1_differential_1742efb.json; §17 item 7, §20 step 6 and §19 cite it. This commit changes no code. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 6 +- .../design/u2_m2_u1_differential_1742efb.json | 142 ++++++++++++++++++ 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 docs/design/u2_m2_u1_differential_1742efb.json diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 132f281e2..3aa9d0d7f 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "3c6539800900e6cba7963111d0960cc40261f014feb0cea88eec7c9629c5e9a6", + "sha256": "c27cac5213365232cbc57d480a46fea494739f00ef71d6ab29eae96d295e6aba", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index 8e4aeb33a..f1e3b63ee 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2007,7 +2007,7 @@ What remains before ratification: 4. Verify identification support, observation plans and refreshed design domains; weight documentation follows the amendment 6 ruling (option 1, ratified 2026-09-30; §16b). **Done on the documents and invented data (2026-10-16):** milestone 2 (#487) holds the observation plans to the milestone-1 support registry (`tests/track_u2/test_u2_plans.py::test_plan_equals_the_milestone_1_support_registry`, `::test_u0_has_five_exact_age_pairs`, `::test_u1_has_fifteen_pairs_over_ten_births_summing_to_one`), seeds identification from the earliest positive-weight presence in the support waves (`::test_seed_is_the_earliest_positive_weight_presence`), and holds the refreshed design domains to the design registry, refresher strata 88–94 included, with support-only waves kept out of U0's observations and design frame (`test_u2_design_floor.py::test_design_domains_equal_the_design_registry`, `::test_refresher_strata_88_to_94_are_valid`, `test_u2_plans.py::test_support_waves_never_enter_u0_observations_or_design`). The d637 registry commits (#540) applied amendment 6's 2017 weight with its registration disclosure (`test_adjudication_applied.py::test_d637_amendment_6_uses_er34651_as_released`). Their counts on real records come from the structural pass (item 8), which resolves any failure it finds (§20 step 4). 5. Pin the SSI parameter file in §15. The capture exists and the adjudication reconfirmed all 106 source hashes. **Code pin checked (2026-10-16); §15's pin is open:** the code pins the capture by its SHA-256, `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762` (`parameters.SSI_SHA256`; `diagnostics.SSI_SHA256` equals it). Its loader takes no caller-supplied hash, both readers refuse any other bytes (`test_u2_parameters.py::test_loaders_take_no_caller_supplied_hash`, `::test_a_changed_ssi_capture_refuses`), and a registered check refuses another recorded hash (`::test_registered_check_needs_every_u2_pin`). §15's `ssi.parameters.sha256` still reads `TO_VERIFY`: §15 precedes §16a, so no revision of this draft edits it, and §20 step 5 writes the pin when it materializes the final parameter block (item 9). **Pinned (2026-10-21; §20 step 5):** §15's `ssi.parameters.sha256` is `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762`, and `rows.check_block_against_code` holds it equal to `parameters.SSI_SHA256` with the rest of the block (`test_u2_specification_block.py::test_the_committed_block_equals_the_code`). 6. Complete historical-isolation and cross-cohort refusal tests (milestone 2). **Done (milestone 2, #487; kept through #525):** every U2 module is an exact historical-source exclusion, unreachable from the birth evidence (`test_u2_isolation.py::test_every_u2_module_is_an_exact_historical_exclusion`; `tests/estimates/test_birth_evidence_artifact.py`), and §14's cross-cohort refusals hold both ways (`test_u2_parameters.py::test_u1_runner_refuses_the_track_m_and_u2_captures`, `::test_u1_captures_are_refused_as_u2_sources`, `test_u2_identity.py::test_u1_guards_refuse_u2_seed_and_rows`). `docs/design/u2_m2_report.md`'s §13 and §14 audit lists each test. -7. Produce U2 invented dry-run and exact U1 differential evidence (milestone 2). **Done (2026-10-16):** the invented dry run (`scripts/track_u2_dry_run.py`; `test_u2_scripts.py::test_dry_run_records_every_branch_and_refusal`) and the exact U1 differential at `f7a84d0b` against `9cee2423`, equal in every check, the dry run included (`docs/design/u2_m2_u1_differential_f7a84d0.json`). This is evidence at `f7a84d0b`; the differential runs again at the merge that ratifies the specification (§20 step 6). +7. Produce U2 invented dry-run and exact U1 differential evidence (milestone 2). **Done (2026-10-16):** the invented dry run (`scripts/track_u2_dry_run.py`; `test_u2_scripts.py::test_dry_run_records_every_branch_and_refusal`) and the exact U1 differential at `f7a84d0b` against `9cee2423`, equal in every check, the dry run included (`docs/design/u2_m2_u1_differential_f7a84d0.json`). This is evidence at `f7a84d0b`; the differential runs again at the merge that ratifies the specification (§20 step 6). **Rerun for ratification (2026-10-22):** at `1742efb1`, the ratifying revision's last code change, against `9cee2423`, equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`). 8. Complete the pre-registration structural, reconciliation and F17 component pass, including only the counts that the ratified B1 and B2 options name (under the 2026-10-10 ruling, the counts specified by §16a options 1d-2 and 1e-1, each disclosed and deciding nothing; §16c). **Done (2026-10-21; §20 step 4):** the second pass, under d1724, completed with no refusal, failure or mapping failure, and the structural counts of rows U0 and U1 carry the per-exclusion person counts (`n_family_unit_exclusion_persons` and `n_family_unit_exclusion_persons_distinct`); this record states none of them. 9. Check the specification, literal named deltas, parameter block (with the amendment 5 fragment, ratified 2026-09-30), manifests and ten-row implementation for equality. **Partly checked (2026-10-16):** the named deltas equal §12 literally and the rows equal the §15 block (`test_u2_identity.py::test_named_deltas_equal_section_12_literally`, `::test_rows_equal_the_section_15_block`). The equality checks of the specification, the manifests and the final parameter block wait for §20 step 5, which materializes the block and empties §15's `blocked_by`. **Done (2026-10-21; §20 step 5):** §15 holds the final block, `u2-ratified-1`, with the amendment 5 fragment, an empty `blocked_by` and U2's rulings record, and `rows.check_block_against_code` holds the whole block equal to the code, every key and value; the manifests it is checked against are the committed registries, all RESOLVED (§20 step 2), which give each wave's weight, family-unit and WEALTH1 variables and every registry entry the rulings record names (`test_u2_specification_block.py`). 10. Obtain ratification, then a fresh forecast and an independent forecast check. **Ratification:** the merge of the revision that completes §20 step 5 (§20 step 6). The fresh forecast and its independent check are §20 step 7. @@ -2264,7 +2264,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Finding 3.** The block takes the employer-DC persons and previous plans from the reader (`sources.DC_PERSONS` and `DC_PLANS`), the two `overlap_2012` values from the parameter loaders, and refuses to name a script that does not exist. - **Finding 4.** The reader's path resolves through a symlinked checkout. - **Findings 5 and 7.** New tests cover the preflight's rulings check and the dry run's specification checks (now one helper, `specification_checks`), B2's person count (d1242), unresolved registry entries and `-0.0`. - - **Finding 6.** The U1 differential report is committed at this revision's last code change (§20 step 6). + - **Finding 6.** The U1 differential report at this revision's last code change, `1742efb1`, is committed as `docs/design/u2_m2_u1_differential_1742efb.json` (§20 step 6). - **Finding 8.** The appendix names the 2013 and 2015 code-92 entries, and its test checks every registry entry and `income_concept` path the appendix cites. - **Finding 9.** The exposure record names the coordinating session's messages. - **Finding 10.** §15 says which ruling `decisions.ruled_on` dates, and §20's status line names the steps done and pending. @@ -2285,7 +2285,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **The pre-registration evidence pin.** The step-4 pass wrote `track-u2-preregistration-evidence.json`, the record of PSID file and frame hashes that the registered run rechecks; the restricted list keeps it unopened here. The pass's `MANIFEST.md` pins its SHA-256, `7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845`. §15's `preregistration_evidence` and `loader.PREREGISTRATION_EVIDENCE_SHA256` now pin the same value, so `loader.read_preregistration_evidence`, which the registered preflight and every frozen load call, refuses any file at `docs/design/u2_preregistration_evidence.json` whose bytes differ, before it parses it. A separate lane commits the file there, byte for byte, before step 8; this revision does not touch it. - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. -6. Ratify the specification by merge. **This revision's merge (2026-10-21).** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential runs again at the revision's last code change (§17 item 7). The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). +6. Ratify the specification by merge. **This revision's merge (2026-10-21).** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential ran again at the revision's last code change, `1742efb1`, against `9cee2423`: equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`; §17 item 7). Later commits change no code. The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). 7. Obtain and independently check the fresh forecast. 8. Post the issue #42 registration with exact commit, specification hash, input and parameter pins, rows, headline, plans and exposure disclosure. 9. Execute the one-shot under the required launchd process. diff --git a/docs/design/u2_m2_u1_differential_1742efb.json b/docs/design/u2_m2_u1_differential_1742efb.json new file mode 100644 index 000000000..c9e5a7904 --- /dev/null +++ b/docs/design/u2_m2_u1_differential_1742efb.json @@ -0,0 +1,142 @@ +{ + "base": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-base-9cee2423", + "head": "9cee2423f048683e15838aa5fee330afd072f85f" + }, + "candidate": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-cand", + "head": "1742efb1633de323643a6bc3068e0765b90d2d0b" + }, + "python": "/private/tmp/claude-501/-Users-maxghenis-Library-Application-Support-Claude-scratch-workspaces-ee3763f4-e7ea-4177-bcaf-1362266768c1-4457aa9d-2dc0-4bcf-a128-e50adae6be25-scratch-2026-09-22-b65ec9/59b436cf-29b0-43ac-a811-411fcc960646/scratchpad/venv/bin/python", + "seed": 20260924, + "tolerance": "zero: byte equality", + "excluded_json_paths": [ + "/run/git_head", + "/run/date" + ], + "ratified_u1_block_sha256": "aed6e724053a44da76e4d08069930c88fe6c978a5ff58fce42beffd3a78785eb", + "dry_run": { + "result_json_equal": true, + "results_md_equal": true, + "result_json_sha256": { + "base": "6f120f75eacf61ba862b8dc4dfa233c2bb86a0627712ab35736901fee5f17d71", + "candidate": "6f120f75eacf61ba862b8dc4dfa233c2bb86a0627712ab35736901fee5f17d71" + }, + "results_md_sha256": { + "base": "53cafc1972357d7dc8417f9423c2b58c1017984559ff2e4b0c04fe39df5b0a2a", + "candidate": "53cafc1972357d7dc8417f9423c2b58c1017984559ff2e4b0c04fe39df5b0a2a" + }, + "first_json_difference": null, + "first_md_difference": null, + "run": { + "base": { + "date": "2026-10-21", + "invented_seed": 20260924, + "git_head": "9cee2423f048683e15838aa5fee330afd072f85f", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-uyvmru1h/same-output --seed 20260924" + }, + "candidate": { + "date": "2026-10-21", + "invented_seed": 20260924, + "git_head": "1742efb1633de323643a6bc3068e0765b90d2d0b", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-uyvmru1h/same-output --seed 20260924" + } + } + }, + "refusal_expectations": { + "base": { + "met": true, + "failures": {} + }, + "candidate": { + "met": true, + "failures": {} + } + }, + "loader_file_accesses_empty": true, + "contract_checks": { + "equal": true, + "sha256": { + "base": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9", + "candidate": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9" + }, + "first_difference": null, + "checked": [ + "actual_loader", + "age67.WAVES", + "input_frames_sha256", + "observation_plan", + "pending_decisions", + "refusal_parity", + "rows.MAX_RULINGS", + "runner.NAMED_DELTAS", + "uniform_cut_tabulation.COMPARATOR_COLUMN", + "uniform_cut_tabulation.REPORT_ROWS" + ], + "refusal_parity": { + "existing_artifact": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "existing_sidecar": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "invalid_provenance": { + "class": "TrackURunError", + "message": "data_provenance must be one of ('invented', 'registered_real')", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invalid_registration_url": { + "class": "TrackURunError", + "message": "a registered run needs an issue #42 comment URL as its registration pointer (https://github.com/PolicyEngine/microcosm-dynamics/issues/42#issuecomment-)", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_labeled_registered": { + "class": "TrackURunError", + "message": "a registered run reads the staged PSID through age67.load_age67_inputs; these inputs record 'invented'", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_with_pointer": { + "class": "TrackURunError", + "message": "an invented run carries no registration pointer", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "u2_seed_under_u1": { + "class": "ValueError", + "message": "seed_wave_rule must be earliest_presence_wave", + "module": "builtins" + }, + "wrong_registered_headline": { + "class": "ValueError", + "message": "the registration names headline row U0, but the staged PSID gives U0-F under the fallback rule (WEALTH1 refused for waves [2005, 2007]): the staging changed after registration, so this is not the registered run", + "module": "builtins" + }, + "wrong_ssi_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed SSI capture; the SSI parameters record {'kind': 'policyengine_us_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "wrong_threshold_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed, pinned Census threshold capture (adjusted_poverty.THRESHOLDS_SHA256 is 'dc21a78736f5085872cf56c9cf291258034c1293572b77455cba689a1ed0eec5'); the thresholds record {'kind': 'census_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + } + }, + "loader_opened_under_fake_root": { + "base:supplements_refused": [], + "base:supplements_staged": [], + "candidate:supplements_refused": [], + "candidate:supplements_staged": [] + } + }, + "all_equal": true, + "dry_run_skipped": false, + "all_checks_passed": true +} From 14d9576af7a7cd81749cfef36c30aed6c6589929 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 06:27:28 -0400 Subject: [PATCH 06/12] =?UTF-8?q?U2=20=C2=A720=20steps=205=E2=80=936:=20an?= =?UTF-8?q?swer=20review=20r2=20(undated=20merge;=20record=20and=20test=20?= =?UTF-8?q?nits)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of 9f79818a (GPT-6.1 Sol, Subfleet 20261021-214735-u2-step5- review-r2, REQUEST CHANGES; every round-1 finding resolved): 1 (blocking): §20 step 6 no longer dates the merge; the merge commit and the step-8 registration record it. 2: the exposure record names this lane's session, scratchpad and every U1 differential run with both checkouts, records the venv's move after the scratchpad was wiped (pandas pinned to the evidence's 3.0.6), and reads d1649's ruling correctly. 3, 5: tests make each half of the d1242 guard fire alone and cover amendment 6's option. 4: a missing d1090 exclusion entry refuses with a message (next(..., None) instead of a bare StopIteration). 7: §15 and §20 step 5 say exactly what the preflight checks of the registration (an issue #42 comment URL and an equal binding). 8: a test holds u1_identity.json's version equal to the code's. 6 is left for later: its test would read U1's committed artifact, which waits for the coordinating session's PSID hold of 2026-10-23. Mutants N, N2, O and the missing-exclusion refusal are each killed. Tests (empty PSID root; no committed runs/*.json read under the hold): 775 U2 unit-tier and 161 specification tests pass. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 15 ++++++-- docs/design/u2_m2_exposure.md | 22 +++++++++-- .../uniform_cut_track_u2/rows.py | 14 +++++-- tests/track_u2/test_adjudication_applied.py | 3 ++ tests/track_u2/test_u2_specification_block.py | 37 +++++++++++++++++++ 6 files changed, 83 insertions(+), 10 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 3aa9d0d7f..80e88340c 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "c27cac5213365232cbc57d480a46fea494739f00ef71d6ab29eae96d295e6aba", + "sha256": "693b3c4764e18d8e337270cac288610a4906dbfac4ef0e7a11fa2af6b66417a3", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index f1e3b63ee..33a2d73f2 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -1038,7 +1038,7 @@ Changed source bytes, changed frame digests, duplicate identifiers, failed joins This is the final parameter contract, materialized under §20 step 5 (2026-10-21): every pin is resolved, the ratified amendments are applied and U2's rulings record is written under `decisions`. It uses U1’s key names where the concepts are inherited. Additional keys describe U2-specific identity and amendments. -`rows.check_block_against_code` holds this block equal to the code in every key and value, and the registered run's preflight refuses any other block (§20 step 5). The block holds no `TO_VERIFY` value and no blocker. It does not by itself authorize execution: a registered run also needs the issue #42 registration and its binding, and the committed pre-registration evidence (§§14 and 20). Its `decisions.ruled_on` is the date of d514, which adopted §16's rulings and ratification; every later entry carries its own `ruled_on`. +`rows.check_block_against_code` holds this block equal to the code in every key and value, and the registered run's preflight refuses any other block (§20 step 5). The block holds no `TO_VERIFY` value and no blocker. It does not by itself authorize execution: a registered run's preflight also needs an issue #42 comment URL as its registration pointer, a `--binding-sha256` equal to this checkout's registration binding, and the committed pre-registration evidence (§§14 and 20). That the comment exists, and that the forecast came before it, are kept by process. Its `decisions.ruled_on` is the date of d514, which adopted §16's rulings and ratification; every later entry carries its own `ruled_on`. ```json { @@ -2268,6 +2268,15 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Finding 8.** The appendix names the 2013 and 2015 code-92 entries, and its test checks every registry entry and `income_concept` path the appendix cites. - **Finding 9.** The exposure record names the coordinating session's messages. - **Finding 10.** §15 says which ruling `decisions.ruled_on` dates, and §20's status line names the steps done and pending. +- After the independent review of `9f79818a` (GPT-6.1 Sol, Subfleet job `20261021-214735-u2-step5-review-r2`, REQUEST CHANGES; it found every round-1 finding resolved; same folder): + - **Finding 1 (blocking).** §20 step 6 no longer dates the merge, which cannot fall on the date it gave; the merge commit and the step-8 registration record the date. + - **Finding 2.** The exposure record names this lane's session, its scratchpad and every U1 differential run, with both checkouts, and reads d1649's ruling correctly. + - **Findings 3 and 5.** Tests now make each half of the d1242 guard fire on its own, and test amendment 6's option. + - **Finding 4.** A missing d1090 exclusion entry refuses with a message instead of a bare `StopIteration`. + - **Finding 6.** Left for later: a test that holds the d1597 pin equal to U1's committed artifact would read that artifact, which waits for the coordinating session's PSID hold of 2026-10-23. The pin stays held to §13's text. + - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. + - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. + - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change (§20 step 6). ## 20. Ratification and execution record @@ -2280,12 +2289,12 @@ Revision verification comprised the two touched test files and `tests/track_u2/t 5. Materialize the final parameter block and U2 rulings record; require exact code/specification agreement. **Done (2026-10-21).** §15's block is the final contract, version `u2-ratified-1` with status `ratified_frozen`, and `rows.expected_specification_block()` states the same block from the code: the identity, plans and support waves; each wave's weight, family-unit and WEALTH1 variables, read from the committed registries; the income-concept, annuity, threshold, cut and SSI parameters and their capture pins; the cells, floor, design and memo settings; the rows, entry points and labels; and the rulings record. `rows.check_block_against_code` refuses a block that differs at any key, list length, JSON type or value and names every differing path. The registered run's preflight and the dry run apply it, and `tests/track_u2/test_u2_specification_block.py` holds the committed block equal to the code and refuses a change to each of its leaves. In detail: - **The three `TO_VERIFY` keys.** `ssi.parameters.sha256` pins the U2 SSI capture, `a58d55c1…3762`, as the code already did (§17 item 5). `income_concept.employer_dc.reader` names the U2 adapter, `uniform_cut_track_u2/sources.py`, whose `employer_dc_balances` applies each wave's registry route (`dc_route`); U1's key names its own reader the same way. `later_wave_mapping_status` records that every pension-registry entry is RESOLVED under amendment 5, which the code checks before it states the block. - **Amendment 5's fragment.** `previous_routes` is the fragment §16a printed and d637 ratified, equal to `sources.DECLARED_PREVIOUS_ACCOUNT_ITEMS` and to the 2015 refused branch the pension registry records. The registries cite §15's lines 1173 and 1185, so both keep their draft-3 number and text: the fragment fills lines 1174–1184 in place of draft 3's generic route, and the four keys that stood there follow line 1185 unchanged except `later_wave_mapping_status`. `population.relationship_rules` now names amendment 1 with §3's role amendment. - - **`blocked_by` is empty.** Each draft-3 blocker is resolved: later-wave component and role adjudication, and the mapping manifests and independent review, by the RESOLVED registries and their reviews (steps 2 and 3); 2015 male code 20 by d1090's B1 exclusion (§16c); code-90 and code-92 routing by amendment 1a and 1b (d637, confirmed by d745) and d1090's B2 exclusion; later-wave employer-DC routing by amendment 5 (d637); the SSI capture by its verification and this pin (§17 item 5); cohort isolation and identity guards, and the invented dry run and U1 differential, by milestone 2 (§17 items 6 and 7); the pre-registration pass by step 4; and ratification by this revision's merge (step 6). The fresh forecast and the registration, which that last blocker also named, are steps 7 and 8: the registered run's preflight refuses without the issue #42 registration and its binding, and without the committed pre-registration evidence, so an empty `blocked_by` authorizes no run. + - **`blocked_by` is empty.** Each draft-3 blocker is resolved: later-wave component and role adjudication, and the mapping manifests and independent review, by the RESOLVED registries and their reviews (steps 2 and 3); 2015 male code 20 by d1090's B1 exclusion (§16c); code-90 and code-92 routing by amendment 1a and 1b (d637, confirmed by d745) and d1090's B2 exclusion; later-wave employer-DC routing by amendment 5 (d637); the SSI capture by its verification and this pin (§17 item 5); cohort isolation and identity guards, and the invented dry run and U1 differential, by milestone 2 (§17 items 6 and 7); the pre-registration pass by step 4; and ratification by this revision's merge (step 6). The fresh forecast and the registration, which that last blocker also named, are steps 7 and 8: the registered run's preflight refuses unless its registration pointer is an issue #42 comment URL and `--binding-sha256` equals this checkout's binding, and refuses without the committed pre-registration evidence, so an empty `blocked_by` authorizes no run by itself. The preflight cannot check that the comment exists or that the forecast came before it; as before, the process keeps those. - **U2's rulings record.** `decisions` holds `rows.U2_RULINGS` with `ruled_by` Max and `ruled_on` 2026-09-28: d514's eleven entries, unchanged, which adopt every §16 ruling; d637's entries for amendments 1a–1c (confirmed by d745), 2, 3, 4, 5a, 5b and 6 and for the three PSID questions; d1090's entries for B1, B2 and amendment 5c; and one entry each for d1242 (B2's counting unit), d1597 (the structure-only U1 load), d1649 (the step-4 pass) and d1724 (the birth law's support). Each registry-applied entry names the registry entries it governs and copies their ruling, built from the registry module's own scopes, and `rows.check_rulings_against_code` holds the record to the committed registries, the role rules and the cohort code. The d1597, d1649 and d1724 entries record each use of their authorization, with the head, date and value-free result: d1597's load ran before each of the two passes, and d1649's and d1724's passes ran once each. - **The pre-registration evidence pin.** The step-4 pass wrote `track-u2-preregistration-evidence.json`, the record of PSID file and frame hashes that the registered run rechecks; the restricted list keeps it unopened here. The pass's `MANIFEST.md` pins its SHA-256, `7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845`. §15's `preregistration_evidence` and `loader.PREREGISTRATION_EVIDENCE_SHA256` now pin the same value, so `loader.read_preregistration_evidence`, which the registered preflight and every frozen load call, refuses any file at `docs/design/u2_preregistration_evidence.json` whose bytes differ, before it parses it. A separate lane commits the file there, byte for byte, before step 8; this revision does not touch it. - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. -6. Ratify the specification by merge. **This revision's merge (2026-10-21).** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential ran again at the revision's last code change, `1742efb1`, against `9cee2423`: equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`; §17 item 7). Later commits change no code. The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). +6. Ratify the specification by merge. **This revision's merge.** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential ran again at the revision's last code change, `1742efb1`, against `9cee2423`: equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`; §17 item 7). Later commits change no code. The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). 7. Obtain and independently check the fresh forecast. 8. Post the issue #42 registration with exact commit, specification hash, input and parameter pins, rows, headline, plans and exposure disclosure. 9. Execute the one-shot under the required launchd process. diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index bf7a345b0..75ced6c27 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -383,14 +383,23 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - `EV/RESTRICTED-FILES.md`, in full and first, through a saved copy of the tool output in this session's own transcript folder. No other session's file in that project folder was opened. The harness also auto-loaded that project's memory index (`MEMORY.md`, one-line pointers); no memory file was opened. - `EV/track-u2-step4-pass-20261021-run2/MANIFEST.md`, which the restricted list allows to be read and cited, and its SHA-256, which matched `acf5d8a4…e6d`. No other file in that folder and nothing in `EV/track-u2-step4-pass-20261019/` was opened or listed. - The chief-of-staff decision ledger's entries for d514, d637, d745, d1090, d1242, d1597, d1649 and d1724 (decision, ruling, ruling time and notes), selected by id, and the names in its folder. -- On 2026-10-22, to check the review's finding 1: the field names of the d1597, d1649 and d1724 ledger records, and the d1649 `ruling` and d1724 `why` fields, which state that the first pass (2026-10-19, `640adccf`) passed d1597's U1 load. +- On 2026-10-22, to check the review's finding 1: the field names of the d1597, d1649 and d1724 ledger records; d1724's `why` field, which states that the first pass (2026-10-19, `640adccf`) passed d1597's U1 load; and d1649's `ruling`, which requires that load to run before the pass. - The coordinating session's brief, the message that started this lane, and its later messages of 2026-10-21 and 2026-10-22. They carry value-free facts only: the step-4 outcomes this lane records, the evidence record's SHA-256 as its MANIFEST pins it, and the tier-slot queue. - The review lane's report, `EV/rulings-records-20261021-u2-step5/review-r1b-out.md`, and the names in the evidence folder that match `rulings-records` (names only). - The repository at `1e739d32`: `CLAUDE.md`; the specification in full; the U1 specification's header, §15 block and §20; the U2 package, scripts and tests named in the PR; `data/external/track_u2/u1_identity.json` and the registries' entry ids and fields; the U2 SSI capture's provenance fields and its source manifest's records; `tests/README-tiers.md`; Track M's `specification.py` header; `estimates/cola_age_profile.specification_unratified_fields`. Also `~/PolicyEngine/CLAUDE.md`. **Not opened:** any other evidence-folder path; any comparator folder, seal or Boomers Report copy; `runs/track_b_b2_*`, `docs/design/track_b_attempt_ledger.md` and `paper/paper.qmd`; other sessions' transcripts or scratchpads; `~/.subfleet/` other than this session's own review jobs; any PSID file or codebook. -**Ran:** the U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to an empty directory, which stayed empty, so the checks that need PSID documentation skipped; Black and Ruff; the tier recount by collection; and the mutation checks listed in the PR. Every block, rulings and appendix check reads only the specification, the committed registries and the code. +**Who and where.** This lane is the Claude Code session `59b436cf-29b0-43ac-a811-411fcc960646`. Its scratchpad, `/private/tmp/claude-501/-Users-maxghenis-Library-Application-Support-Claude-scratch-workspaces-ee3763f4-…-scratch-2026-09-22-b65ec9/59b436cf-29b0-43ac-a811-411fcc960646/scratchpad/`, is its own. Until the host wiped it, on 2026-10-22, it held this lane's virtual environment (`venv/`), the empty PSID directory (`empty-psid/`), its mutation runners and its differential reports. From then on they live outside the repository: the virtual environment at `~/PolicyEngine/_clones/md-u2-step5-venv` (Python 3.14.7; pandas pinned to 3.0.6, the version the step-4 evidence froze), the empty PSID directory at `~/PolicyEngine/_clones/md-u2-step5-empty-psid`, and the mutation runners at `~/PolicyEngine/_clones/md-u2-step5-mut`. The lane opened no other session's scratchpad or transcript. + +**Ran:** +- **Tests.** The U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to the empty directory, which stayed empty, so the checks that need PSID documentation skipped. Also Black and Ruff, the tier recount by collection, and the mutation checks listed in the PR. +- **Block, rulings and appendix checks.** Every one reads only the specification, the committed registries and the code. +- **The U1 differential** (`scripts/u2_u1_differential.py`, invented data only, with the empty PSID directory and this lane's virtual environment). Its two checkouts are worktrees of this repository: the base, `~/PolicyEngine/_clones/md-u2-diff-base-9cee2423`, detached at `9cee2423`, and the candidate, `~/PolicyEngine/_clones/md-u2-diff-cand`, detached at the head under test. + - **Trial at `690a1276`** (2026-10-21): equal in every check. + - **At `1742efb1`** (2026-10-22): equal in every check; report committed as `docs/design/u2_m2_u1_differential_1742efb.json`. + - **At FINAL_CODE_HEAD**, the revision's last code change: see §20 step 6. + - Each run's dry run wrote only to a temporary directory the harness made and removed. **Values:** it stated and inferred no 1946–55 value or direction. Every number it wrote is a hash, a commit, a CI run id, a date, a line number, a registry entry count, a test count or a constant of the code or the specification. @@ -403,4 +412,11 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - One early command wrote git-ignored bytecode caches under the review clone's `src/`. - Two `grep` searches over `docs/design/` scanned the bytes of `track_b_attempt_ledger.md` for fixed literals, with nothing from that file printed. That file is restricted for B2-successor work, not for U2; this record lists it for completeness. - It opened no other evidence path, PSID file or comparator. Its findings were addressed in the next commit (§19). - +- **Subfleet `20261021-214735-u2-step5-review-r2`.** It ran on GPT-6.1 Sol and reviewed `9f79818a` in full. It returned REQUEST CHANGES: one blocking finding (§20 step 6 dated the merge) and should-fix and nit findings, all answered in the next commit (§19). + - **What it read outside its worktree:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, the round-1 report and the eight decision-ledger records selected by id. + - **Where it worked:** in its own Subfleet worktree, writing nothing into the review clone. + - **Its three disclosures:** + - A `grep -rln` over `runs/` for the U1 pin's hex scanned the bytes of `runs/track_b_b2_*` and `runs/m2_pseudo_projection_v1.json`. Nothing from either file was printed. + - A `git archive` copy it started under `/tmp/r2rev/mut` may have included restricted files. It deleted the copy unopened. + - It loaded U1's artifact in memory to compare one hash, printing a boolean. + - It opened no PSID file, codebook or comparator, and the empty PSID directory stayed empty. diff --git a/src/populace_dynamics/uniform_cut_track_u2/rows.py b/src/populace_dynamics/uniform_cut_track_u2/rows.py index 8d2aaca05..0df2e1259 100644 --- a/src/populace_dynamics/uniform_cut_track_u2/rows.py +++ b/src/populace_dynamics/uniform_cut_track_u2/rows.py @@ -868,10 +868,18 @@ def check_rulings_against_code( ) for disposition in sources.FAMILY_UNIT_EXCLUSIONS: entry = next( - e - for e in U2_RULINGS.values() - if e.get("disposition") == disposition + ( + e + for e in U2_RULINGS.values() + if e.get("disposition") == disposition + ), + None, ) + if entry is None: + raise ValueError( + f"the rulings record has no entry for the d1090 exclusion " + f"{disposition!r}" + ) carrying = sorted( f"roles:{role['id']}" for role in registries.entries("roles") diff --git a/tests/track_u2/test_adjudication_applied.py b/tests/track_u2/test_adjudication_applied.py index b2a7114f8..c378395c4 100644 --- a/tests/track_u2/test_adjudication_applied.py +++ b/tests/track_u2/test_adjudication_applied.py @@ -997,6 +997,9 @@ def test_specification_revision_keeps_cited_lines(documents): ) record = pins["u2_specification"] assert record["path"] == SPEC and SPEC not in pins["sha256"] + from populace_dynamics.uniform_cut_track_u2 import rows + + assert record["version"] == rows.SPECIFICATION_VERSION raw = (ROOT / SPEC).read_bytes() assert hashlib.sha256(raw).hexdigest() == record["sha256"] assert record["draft_3_sha256"] == ( diff --git a/tests/track_u2/test_u2_specification_block.py b/tests/track_u2/test_u2_specification_block.py index 55ca34952..f52be25ee 100644 --- a/tests/track_u2/test_u2_specification_block.py +++ b/tests/track_u2/test_u2_specification_block.py @@ -420,6 +420,34 @@ def _without_reference(key: str, reference: str) -> dict: }, "another option", ), + ( + { + **rows.U2_RULINGS, + "amendment_6_blocker_b3_2017_weight": { + **rows.U2_RULINGS["amendment_6_blocker_b3_2017_weight"], + "option": "2", + }, + }, + "another option", + ), + ( + { + key: value + for key, value in rows.U2_RULINGS.items() + if key != "blocker_b1_male_code_20_2015" + }, + "no entry for the d1090 exclusion", + ), + ( + { + **rows.U2_RULINGS, + "b2_disclosed_count": { + **rows.U2_RULINGS["b2_disclosed_count"], + "person_counted_exclusions": [sources.MALE_CODE_20_2015], + }, + }, + "d1242", + ), ( { **rows.U2_RULINGS, @@ -648,6 +676,15 @@ def test_b2s_person_count_must_be_made(monkeypatch, registries): ) with pytest.raises(ValueError, match="d1242"): rows.check_rulings_against_code(registries) + # Review of 9f79818a, finding 3: with the record changed to match, the + # d1242 fact itself (B2 is a person count) still refuses. + record = copy.deepcopy(rows.U2_RULINGS) + record["b2_disclosed_count"]["person_counted_exclusions"] = [ + sources.MALE_CODE_20_2015 + ] + monkeypatch.setattr(rows, "U2_RULINGS", record) + with pytest.raises(ValueError, match="d1242"): + rows.check_rulings_against_code(registries) def test_each_authorization_records_every_use(): From fb7d92b96fbe4c6010c620ed956397bb41fca8b9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 06:29:32 -0400 Subject: [PATCH 07/12] =?UTF-8?q?U2=20=C2=A720=20step=206:=20record=20the?= =?UTF-8?q?=20U1=20differential=20at=2014d9576a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/u2_u1_differential.py ran at 14d9576a, now this revision's last code change, against 9cee2423, on invented data with an empty PSID root: equal in every check, the dry run included, with no loader file access. Report: docs/design/u2_m2_u1_differential_14d9576.json, cited by §17 item 7, §19 and §20 step 6; the earlier rerun at 1742efb1 stays recorded. This commit changes no code. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 6 +- docs/design/u2_m2_exposure.md | 2 +- .../design/u2_m2_u1_differential_14d9576.json | 142 ++++++++++++++++++ 4 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 docs/design/u2_m2_u1_differential_14d9576.json diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 80e88340c..00c33d699 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "693b3c4764e18d8e337270cac288610a4906dbfac4ef0e7a11fa2af6b66417a3", + "sha256": "78693e54c45e61abe87c8f728fb4f5fb9670bad4b8dc7a70f0fcb42d738f5a7c", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index 33a2d73f2..64a397c68 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2007,7 +2007,7 @@ What remains before ratification: 4. Verify identification support, observation plans and refreshed design domains; weight documentation follows the amendment 6 ruling (option 1, ratified 2026-09-30; §16b). **Done on the documents and invented data (2026-10-16):** milestone 2 (#487) holds the observation plans to the milestone-1 support registry (`tests/track_u2/test_u2_plans.py::test_plan_equals_the_milestone_1_support_registry`, `::test_u0_has_five_exact_age_pairs`, `::test_u1_has_fifteen_pairs_over_ten_births_summing_to_one`), seeds identification from the earliest positive-weight presence in the support waves (`::test_seed_is_the_earliest_positive_weight_presence`), and holds the refreshed design domains to the design registry, refresher strata 88–94 included, with support-only waves kept out of U0's observations and design frame (`test_u2_design_floor.py::test_design_domains_equal_the_design_registry`, `::test_refresher_strata_88_to_94_are_valid`, `test_u2_plans.py::test_support_waves_never_enter_u0_observations_or_design`). The d637 registry commits (#540) applied amendment 6's 2017 weight with its registration disclosure (`test_adjudication_applied.py::test_d637_amendment_6_uses_er34651_as_released`). Their counts on real records come from the structural pass (item 8), which resolves any failure it finds (§20 step 4). 5. Pin the SSI parameter file in §15. The capture exists and the adjudication reconfirmed all 106 source hashes. **Code pin checked (2026-10-16); §15's pin is open:** the code pins the capture by its SHA-256, `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762` (`parameters.SSI_SHA256`; `diagnostics.SSI_SHA256` equals it). Its loader takes no caller-supplied hash, both readers refuse any other bytes (`test_u2_parameters.py::test_loaders_take_no_caller_supplied_hash`, `::test_a_changed_ssi_capture_refuses`), and a registered check refuses another recorded hash (`::test_registered_check_needs_every_u2_pin`). §15's `ssi.parameters.sha256` still reads `TO_VERIFY`: §15 precedes §16a, so no revision of this draft edits it, and §20 step 5 writes the pin when it materializes the final parameter block (item 9). **Pinned (2026-10-21; §20 step 5):** §15's `ssi.parameters.sha256` is `a58d55c160b48cd3e21f730d45265374bd3c8a9eb7eae947469a5a5dc0b23762`, and `rows.check_block_against_code` holds it equal to `parameters.SSI_SHA256` with the rest of the block (`test_u2_specification_block.py::test_the_committed_block_equals_the_code`). 6. Complete historical-isolation and cross-cohort refusal tests (milestone 2). **Done (milestone 2, #487; kept through #525):** every U2 module is an exact historical-source exclusion, unreachable from the birth evidence (`test_u2_isolation.py::test_every_u2_module_is_an_exact_historical_exclusion`; `tests/estimates/test_birth_evidence_artifact.py`), and §14's cross-cohort refusals hold both ways (`test_u2_parameters.py::test_u1_runner_refuses_the_track_m_and_u2_captures`, `::test_u1_captures_are_refused_as_u2_sources`, `test_u2_identity.py::test_u1_guards_refuse_u2_seed_and_rows`). `docs/design/u2_m2_report.md`'s §13 and §14 audit lists each test. -7. Produce U2 invented dry-run and exact U1 differential evidence (milestone 2). **Done (2026-10-16):** the invented dry run (`scripts/track_u2_dry_run.py`; `test_u2_scripts.py::test_dry_run_records_every_branch_and_refusal`) and the exact U1 differential at `f7a84d0b` against `9cee2423`, equal in every check, the dry run included (`docs/design/u2_m2_u1_differential_f7a84d0.json`). This is evidence at `f7a84d0b`; the differential runs again at the merge that ratifies the specification (§20 step 6). **Rerun for ratification (2026-10-22):** at `1742efb1`, the ratifying revision's last code change, against `9cee2423`, equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`). +7. Produce U2 invented dry-run and exact U1 differential evidence (milestone 2). **Done (2026-10-16):** the invented dry run (`scripts/track_u2_dry_run.py`; `test_u2_scripts.py::test_dry_run_records_every_branch_and_refusal`) and the exact U1 differential at `f7a84d0b` against `9cee2423`, equal in every check, the dry run included (`docs/design/u2_m2_u1_differential_f7a84d0.json`). This is evidence at `f7a84d0b`; the differential runs again at the merge that ratifies the specification (§20 step 6). **Reruns for ratification:** at `1742efb1` (2026-10-22) and again at `14d9576a` (2026-10-23), the ratifying revision's last code change, each against `9cee2423` and equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json` and `u2_m2_u1_differential_14d9576.json`). 8. Complete the pre-registration structural, reconciliation and F17 component pass, including only the counts that the ratified B1 and B2 options name (under the 2026-10-10 ruling, the counts specified by §16a options 1d-2 and 1e-1, each disclosed and deciding nothing; §16c). **Done (2026-10-21; §20 step 4):** the second pass, under d1724, completed with no refusal, failure or mapping failure, and the structural counts of rows U0 and U1 carry the per-exclusion person counts (`n_family_unit_exclusion_persons` and `n_family_unit_exclusion_persons_distinct`); this record states none of them. 9. Check the specification, literal named deltas, parameter block (with the amendment 5 fragment, ratified 2026-09-30), manifests and ten-row implementation for equality. **Partly checked (2026-10-16):** the named deltas equal §12 literally and the rows equal the §15 block (`test_u2_identity.py::test_named_deltas_equal_section_12_literally`, `::test_rows_equal_the_section_15_block`). The equality checks of the specification, the manifests and the final parameter block wait for §20 step 5, which materializes the block and empties §15's `blocked_by`. **Done (2026-10-21; §20 step 5):** §15 holds the final block, `u2-ratified-1`, with the amendment 5 fragment, an empty `blocked_by` and U2's rulings record, and `rows.check_block_against_code` holds the whole block equal to the code, every key and value; the manifests it is checked against are the committed registries, all RESOLVED (§20 step 2), which give each wave's weight, family-unit and WEALTH1 variables and every registry entry the rulings record names (`test_u2_specification_block.py`). 10. Obtain ratification, then a fresh forecast and an independent forecast check. **Ratification:** the merge of the revision that completes §20 step 5 (§20 step 6). The fresh forecast and its independent check are §20 step 7. @@ -2276,7 +2276,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Finding 6.** Left for later: a test that holds the d1597 pin equal to U1's committed artifact would read that artifact, which waits for the coordinating session's PSID hold of 2026-10-23. The pin stays held to §13's text. - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. - - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change (§20 step 6). + - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change, `14d9576a`: equal in every check (`docs/design/u2_m2_u1_differential_14d9576.json`; §20 step 6). ## 20. Ratification and execution record @@ -2294,7 +2294,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **The pre-registration evidence pin.** The step-4 pass wrote `track-u2-preregistration-evidence.json`, the record of PSID file and frame hashes that the registered run rechecks; the restricted list keeps it unopened here. The pass's `MANIFEST.md` pins its SHA-256, `7bc5c3c26ff014271061a8228068c6e71103430ec299048462570476a510b845`. §15's `preregistration_evidence` and `loader.PREREGISTRATION_EVIDENCE_SHA256` now pin the same value, so `loader.read_preregistration_evidence`, which the registered preflight and every frozen load call, refuses any file at `docs/design/u2_preregistration_evidence.json` whose bytes differ, before it parses it. A separate lane commits the file there, byte for byte, before step 8; this revision does not touch it. - **Prose `TO VERIFY` items.** §§2–14 keep their draft-3 text, which the registries cite by line number and the tests pin. Each line there that still says **TO VERIFY**, and §17's closing line, which §16a quotes as draft-3 line 1391, is listed with its resolution in the appendix "TO VERIFY item → where resolved", which a test holds complete. The registries' own notes that §15 keeps draft 3's text "until §20 step 5" stay as written, because changing a registry byte would change the `registries_sha256` the step-4 evidence records; this step supersedes them. - **No substance changes.** Every change pins a capture the code already pinned, records a ruling Max made, applies an amendment he ratified, or names code that already existed. No row, plan, cell, role rule, parameter value, statistic or comparison rule changes, no registry changes, and so the step-4 evidence's `registries_sha256` still equals `sources.REGISTRY_SHA256`. No count, statistic or outcome was computed to materialize the block, and no PSID file was read. -6. Ratify the specification by merge. **This revision's merge.** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential ran again at the revision's last code change, `1742efb1`, against `9cee2423`: equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_1742efb.json`; §17 item 7). Later commits change no code. The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). +6. Ratify the specification by merge. **This revision's merge.** Its pull request merges only on the gates: every check green, the pull request mergeable, and an independent review's APPROVE at the merged head. The U1 differential ran again at the revision's last code change, `14d9576a`, against `9cee2423`: equal in every check, the dry run included, with no loader file access (`docs/design/u2_m2_u1_differential_14d9576.json`; §17 item 7). Later commits change no code. An earlier rerun at `1742efb1` was equal too. The merge commit and this file's SHA-256 at it go to the coordinating session and into the issue #42 registration (step 8). 7. Obtain and independently check the fresh forecast. 8. Post the issue #42 registration with exact commit, specification hash, input and parameter pins, rows, headline, plans and exposure disclosure. 9. Execute the one-shot under the required launchd process. diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index 75ced6c27..ae8d60c07 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -398,7 +398,7 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - **The U1 differential** (`scripts/u2_u1_differential.py`, invented data only, with the empty PSID directory and this lane's virtual environment). Its two checkouts are worktrees of this repository: the base, `~/PolicyEngine/_clones/md-u2-diff-base-9cee2423`, detached at `9cee2423`, and the candidate, `~/PolicyEngine/_clones/md-u2-diff-cand`, detached at the head under test. - **Trial at `690a1276`** (2026-10-21): equal in every check. - **At `1742efb1`** (2026-10-22): equal in every check; report committed as `docs/design/u2_m2_u1_differential_1742efb.json`. - - **At FINAL_CODE_HEAD**, the revision's last code change: see §20 step 6. + - **At `14d9576a`** (2026-10-23), the revision's last code change: equal in every check; report committed as `docs/design/u2_m2_u1_differential_14d9576.json`. This run and every later one used the relocated virtual environment and empty PSID directory. - Each run's dry run wrote only to a temporary directory the harness made and removed. **Values:** it stated and inferred no 1946–55 value or direction. Every number it wrote is a hash, a commit, a CI run id, a date, a line number, a registry entry count, a test count or a constant of the code or the specification. diff --git a/docs/design/u2_m2_u1_differential_14d9576.json b/docs/design/u2_m2_u1_differential_14d9576.json new file mode 100644 index 000000000..6ca95d7de --- /dev/null +++ b/docs/design/u2_m2_u1_differential_14d9576.json @@ -0,0 +1,142 @@ +{ + "base": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-base-9cee2423", + "head": "9cee2423f048683e15838aa5fee330afd072f85f" + }, + "candidate": { + "path": "/Users/maxghenis/PolicyEngine/_clones/md-u2-diff-cand", + "head": "14d9576af7a7cd81749cfef36c30aed6c6589929" + }, + "python": "/Users/maxghenis/PolicyEngine/_clones/md-u2-step5-venv/bin/python", + "seed": 20260924, + "tolerance": "zero: byte equality", + "excluded_json_paths": [ + "/run/git_head", + "/run/date" + ], + "ratified_u1_block_sha256": "aed6e724053a44da76e4d08069930c88fe6c978a5ff58fce42beffd3a78785eb", + "dry_run": { + "result_json_equal": true, + "results_md_equal": true, + "result_json_sha256": { + "base": "b3d5884fd1803a61b91f3a06add961473d850c786a524e841baf064a7763cd26", + "candidate": "b3d5884fd1803a61b91f3a06add961473d850c786a524e841baf064a7763cd26" + }, + "results_md_sha256": { + "base": "53ce68ccfb9b8409827e61bab1c3caee4b354a1a4220187adc259e0aa8a56115", + "candidate": "53ce68ccfb9b8409827e61bab1c3caee4b354a1a4220187adc259e0aa8a56115" + }, + "first_json_difference": null, + "first_md_difference": null, + "run": { + "base": { + "date": "2026-10-23", + "invented_seed": 20260924, + "git_head": "9cee2423f048683e15838aa5fee330afd072f85f", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-ez7kzyrx/same-output --seed 20260924" + }, + "candidate": { + "date": "2026-10-23", + "invented_seed": 20260924, + "git_head": "14d9576af7a7cd81749cfef36c30aed6c6589929", + "git_clean": true, + "python": "3.14.7", + "command": "python scripts/track_u_dry_run.py --output-dir /var/folders/9l/_wztzgbx7mgc7l1r0416cy7m0000gn/T/u1-differential-ez7kzyrx/same-output --seed 20260924" + } + } + }, + "refusal_expectations": { + "base": { + "met": true, + "failures": {} + }, + "candidate": { + "met": true, + "failures": {} + } + }, + "loader_file_accesses_empty": true, + "contract_checks": { + "equal": true, + "sha256": { + "base": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9", + "candidate": "49e47726948dc6faf4f39e0ae1ff60054e178b5feb4701cbb8b84400b6bdccd9" + }, + "first_difference": null, + "checked": [ + "actual_loader", + "age67.WAVES", + "input_frames_sha256", + "observation_plan", + "pending_decisions", + "refusal_parity", + "rows.MAX_RULINGS", + "runner.NAMED_DELTAS", + "uniform_cut_tabulation.COMPARATOR_COLUMN", + "uniform_cut_tabulation.REPORT_ROWS" + ], + "refusal_parity": { + "existing_artifact": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "existing_sidecar": { + "class": "ValueError", + "message": "/INVENTED/u1-refusal-parity/replication_boomers2004_uniform_cut_v1.json already exists: the registered run is one-shot", + "module": "builtins" + }, + "invalid_provenance": { + "class": "TrackURunError", + "message": "data_provenance must be one of ('invented', 'registered_real')", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invalid_registration_url": { + "class": "TrackURunError", + "message": "a registered run needs an issue #42 comment URL as its registration pointer (https://github.com/PolicyEngine/microcosm-dynamics/issues/42#issuecomment-)", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_labeled_registered": { + "class": "TrackURunError", + "message": "a registered run reads the staged PSID through age67.load_age67_inputs; these inputs record 'invented'", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "invented_with_pointer": { + "class": "TrackURunError", + "message": "an invented run carries no registration pointer", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "u2_seed_under_u1": { + "class": "ValueError", + "message": "seed_wave_rule must be earliest_presence_wave", + "module": "builtins" + }, + "wrong_registered_headline": { + "class": "ValueError", + "message": "the registration names headline row U0, but the staged PSID gives U0-F under the fallback rule (WEALTH1 refused for waves [2005, 2007]): the staging changed after registration, so this is not the registered run", + "module": "builtins" + }, + "wrong_ssi_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed SSI capture; the SSI parameters record {'kind': 'policyengine_us_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + }, + "wrong_threshold_hash": { + "class": "TrackURunError", + "message": "a registered run needs the committed, pinned Census threshold capture (adjusted_poverty.THRESHOLDS_SHA256 is 'dc21a78736f5085872cf56c9cf291258034c1293572b77455cba689a1ed0eec5'); the thresholds record {'kind': 'census_capture', 'sha256': '0000000000000000000000000000000000000000000000000000000000000000'}", + "module": "populace_dynamics.uniform_cut_track_u.runner" + } + }, + "loader_opened_under_fake_root": { + "base:supplements_refused": [], + "base:supplements_staged": [], + "candidate:supplements_refused": [], + "candidate:supplements_staged": [] + } + }, + "all_equal": true, + "dry_run_skipped": false, + "all_checks_passed": true +} From a2e2d688f5b55fcd8f0495f73f1040be736d21b3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 06:38:26 -0400 Subject: [PATCH 08/12] U2: hold the d1597 pin to U1's committed artifact (review r2, finding 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that the coordinating session's protective PSID hold of 2026-10-23 is lifted (d1899), test_u2_identity.py holds rows._U1_INPUT_FRAMES_SHA256 equal to the input_frames_sha256 U1's committed artifact records, comparing only that hash. §19 and the exposure record say so. Test-only; no code changes. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 2 +- docs/design/u2_m2_exposure.md | 2 +- tests/track_u2/test_u2_identity.py | 12 ++++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 00c33d699..74e200078 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "78693e54c45e61abe87c8f728fb4f5fb9670bad4b8dc7a70f0fcb42d738f5a7c", + "sha256": "a830fd3d2f9f802f88f018512700cffb6aa33e881adf5a131d86e00e7f5a7b18", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index 64a397c68..c87c681f2 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2273,7 +2273,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Finding 2.** The exposure record names this lane's session, its scratchpad and every U1 differential run, with both checkouts, and reads d1649's ruling correctly. - **Findings 3 and 5.** Tests now make each half of the d1242 guard fire on its own, and test amendment 6's option. - **Finding 4.** A missing d1090 exclusion entry refuses with a message instead of a bare `StopIteration`. - - **Finding 6.** Left for later: a test that holds the d1597 pin equal to U1's committed artifact would read that artifact, which waits for the coordinating session's PSID hold of 2026-10-23. The pin stays held to §13's text. + - **Finding 6.** A test holds the d1597 pin equal to the one U1's committed artifact records, comparing only that hash. It landed after the coordinating session's protective PSID hold of 2026-10-23 was lifted the same day (d1899). - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change, `14d9576a`: equal in every check (`docs/design/u2_m2_u1_differential_14d9576.json`; §20 step 6). diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index ae8d60c07..d08cc1a4c 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -394,7 +394,7 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses **Ran:** - **Tests.** The U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to the empty directory, which stayed empty, so the checks that need PSID documentation skipped. Also Black and Ruff, the tier recount by collection, and the mutation checks listed in the PR. -- **Block, rulings and appendix checks.** Every one reads only the specification, the committed registries and the code. +- **Block, rulings and appendix checks.** Every one reads only the specification, the committed registries and the code. After the PSID hold of 2026-10-23 was lifted (d1899), one identity test loads U1's committed artifact in memory to compare one hash, and prints nothing. While the hold stood, this lane ran only unit-tier and specification tests. - **The U1 differential** (`scripts/u2_u1_differential.py`, invented data only, with the empty PSID directory and this lane's virtual environment). Its two checkouts are worktrees of this repository: the base, `~/PolicyEngine/_clones/md-u2-diff-base-9cee2423`, detached at `9cee2423`, and the candidate, `~/PolicyEngine/_clones/md-u2-diff-cand`, detached at the head under test. - **Trial at `690a1276`** (2026-10-21): equal in every check. - **At `1742efb1`** (2026-10-22): equal in every check; report committed as `docs/design/u2_m2_u1_differential_1742efb.json`. diff --git a/tests/track_u2/test_u2_identity.py b/tests/track_u2/test_u2_identity.py index 930de98ee..64d16de08 100644 --- a/tests/track_u2/test_u2_identity.py +++ b/tests/track_u2/test_u2_identity.py @@ -49,6 +49,18 @@ def test_u1_literals_equal_the_u1_modules(): ) +def test_the_u1_input_frame_pin_is_u1s_artifacts(): + """The d1597 entry's pin is the one U1's committed artifact records + (review of 9f79818a, finding 6); only that hash is compared.""" + import json + + artifact = json.loads(identity.U1_ARTIFACT_PATH.read_text()) + assert ( + artifact["cohort_provenance"]["input_frames_sha256"] + == rows._U1_INPUT_FRAMES_SHA256 + ) + + def test_u2_identity_is_separate(): record = identity.identity() assert record["target_id"] == "U2" From ac808d4ad6037f0a4556b8058dfe63750731860e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 07:39:36 -0400 Subject: [PATCH 09/12] U2: answer review r3b's nit; record round 3 (docs only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of a2e2d688 (GPT-6.1 Sol, Subfleet 20261023-063829-u2-step5- review-r3b): APPROVE, every round-2 finding resolved, one nit. The exposure record said the round-2 findings were "all answered in the next commit"; finding 6's test came in a later one, so it now reads "the following commits". The exposure record and §19 also record both round-3 jobs (the first cancelled before launch). No code or test changes. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 1 + docs/design/u2_m2_exposure.md | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 74e200078..0b8bde62d 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "a830fd3d2f9f802f88f018512700cffb6aa33e881adf5a131d86e00e7f5a7b18", + "sha256": "3a4d1c9b19f4a534ec20f8976d0c8ffd1f8d0d2e6db60328d17a401f6322e82a", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index c87c681f2..f6d2e64a6 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2274,6 +2274,7 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Findings 3 and 5.** Tests now make each half of the d1242 guard fire on its own, and test amendment 6's option. - **Finding 4.** A missing d1090 exclusion entry refuses with a message instead of a bare `StopIteration`. - **Finding 6.** A test holds the d1597 pin equal to the one U1's committed artifact records, comparing only that hash. It landed after the coordinating session's protective PSID hold of 2026-10-23 was lifted the same day (d1899). +- After the delta review of `a2e2d688` (GPT-6.1 Sol, Subfleet job `20261023-063829-u2-step5-review-r3b`, APPROVE, with every round-2 finding resolved), its one nit is fixed: the exposure record said the round-2 findings were answered in one commit, and finding 6's came later. An earlier round-3 job (`20261023-062935-u2-step5-review-r3`) was cancelled before it launched, so that one review would also cover finding 6's commit. - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change, `14d9576a`: equal in every check (`docs/design/u2_m2_u1_differential_14d9576.json`; §20 step 6). diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index d08cc1a4c..2d9a8fc46 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -412,7 +412,7 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - One early command wrote git-ignored bytecode caches under the review clone's `src/`. - Two `grep` searches over `docs/design/` scanned the bytes of `track_b_attempt_ledger.md` for fixed literals, with nothing from that file printed. That file is restricted for B2-successor work, not for U2; this record lists it for completeness. - It opened no other evidence path, PSID file or comparator. Its findings were addressed in the next commit (§19). -- **Subfleet `20261021-214735-u2-step5-review-r2`.** It ran on GPT-6.1 Sol and reviewed `9f79818a` in full. It returned REQUEST CHANGES: one blocking finding (§20 step 6 dated the merge) and should-fix and nit findings, all answered in the next commit (§19). +- **Subfleet `20261021-214735-u2-step5-review-r2`.** It ran on GPT-6.1 Sol and reviewed `9f79818a` in full. It returned REQUEST CHANGES: one blocking finding (§20 step 6 dated the merge) and should-fix and nit findings, all answered in the following commits (§19). - **What it read outside its worktree:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, the round-1 report and the eight decision-ledger records selected by id. - **Where it worked:** in its own Subfleet worktree, writing nothing into the review clone. - **Its three disclosures:** @@ -420,3 +420,9 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - A `git archive` copy it started under `/tmp/r2rev/mut` may have included restricted files. It deleted the copy unopened. - It loaded U1's artifact in memory to compare one hash, printing a boolean. - It opened no PSID file, codebook or comparator, and the empty PSID directory stayed empty. +- **Subfleet `20261023-062935-u2-step5-review-r3`.** It waited for capacity and was cancelled before it launched, so that one review could also cover finding 6's commit. It read nothing. +- **Subfleet `20261023-063829-u2-step5-review-r3b`.** It ran on GPT-6.1 Sol and delta-reviewed `a2e2d688` against `9f79818a`. It returned APPROVE, with one nit about this record's wording, answered in the next commit. + - **What it read outside its worktree:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, the round-2 report, the eight decision-ledger records selected by id, and a review-workflow skill file (`~/.agents/skills/gitnexus-pr-review/SKILL.md`). It also listed the empty PSID directory, which was empty. + - **Its runs:** the full related suites with the empty PSID root, after the PSID hold was lifted. They read committed artifacts as CI does, and it printed no value from them. + - It opened no PSID record or codebook and computed nothing from PSID records. + From 8c35e9ca138cb9ccd76ae4afe40a98d872c5c642 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 12:07:13 -0400 Subject: [PATCH 10/12] =?UTF-8?q?U2:=20answer=20review=20r4's=20nit=20(?= =?UTF-8?q?=C2=A719=20entry=20order;=20EOF=20blank=20line)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of ac808d4a (GPT-6.1 Sol, Subfleet 20261023-073938-u2-step5- review-r4): APPROVE, one nit. The §19 entry for round 3 had been inserted inside round 2's list (before findings 7, 8 and the code-change note); it now follows that list, and §19 and the exposure record record round 4. The exposure record's extra blank line at EOF is removed. Docs only. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 3 ++- docs/design/u2_m2_exposure.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index 0b8bde62d..d6a9a9319 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "3a4d1c9b19f4a534ec20f8976d0c8ffd1f8d0d2e6db60328d17a401f6322e82a", + "sha256": "629f62e8b639741a6de0215ae4755b174f56a03a94e851c63a19245b57d5994e", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index f6d2e64a6..e2148cca3 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2274,10 +2274,11 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Findings 3 and 5.** Tests now make each half of the d1242 guard fire on its own, and test amendment 6's option. - **Finding 4.** A missing d1090 exclusion entry refuses with a message instead of a bare `StopIteration`. - **Finding 6.** A test holds the d1597 pin equal to the one U1's committed artifact records, comparing only that hash. It landed after the coordinating session's protective PSID hold of 2026-10-23 was lifted the same day (d1899). -- After the delta review of `a2e2d688` (GPT-6.1 Sol, Subfleet job `20261023-063829-u2-step5-review-r3b`, APPROVE, with every round-2 finding resolved), its one nit is fixed: the exposure record said the round-2 findings were answered in one commit, and finding 6's came later. An earlier round-3 job (`20261023-062935-u2-step5-review-r3`) was cancelled before it launched, so that one review would also cover finding 6's commit. - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change, `14d9576a`: equal in every check (`docs/design/u2_m2_u1_differential_14d9576.json`; §20 step 6). +- After the delta review of `a2e2d688` (GPT-6.1 Sol, Subfleet job `20261023-063829-u2-step5-review-r3b`, APPROVE, with every round-2 finding resolved), its one nit is fixed: the exposure record said the round-2 findings were answered in one commit, and finding 6's came later. An earlier round-3 job (`20261023-062935-u2-step5-review-r3`) was cancelled before it launched, so that one review would also cover finding 6's commit. +- After the delta review of `ac808d4a` (GPT-6.1 Sol, Subfleet job `20261023-073938-u2-step5-review-r4`, APPROVE), its nit is fixed: the entry above had been inserted inside round 2's list and now follows it. ## 20. Ratification and execution record diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index 2d9a8fc46..36075f761 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -425,4 +425,4 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - **What it read outside its worktree:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, the round-2 report, the eight decision-ledger records selected by id, and a review-workflow skill file (`~/.agents/skills/gitnexus-pr-review/SKILL.md`). It also listed the empty PSID directory, which was empty. - **Its runs:** the full related suites with the empty PSID root, after the PSID hold was lifted. They read committed artifacts as CI does, and it printed no value from them. - It opened no PSID record or codebook and computed nothing from PSID records. - +- **Subfleet `20261023-073938-u2-step5-review-r4`.** It ran on GPT-6.1 Sol and delta-reviewed `ac808d4a` against `a2e2d688`. It returned APPROVE, with one nit about the order of §19's entries, answered in the next commit. It read the round-3 reports, and in the clone it ran the specification tests with the empty PSID root. It opened no PSID file. From 7fc7ab21e7724115e81d162fe23a925bcfd850c4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 12:14:05 -0400 Subject: [PATCH 11/12] U2 #556: recount tiers at the slot (after #530) Tiers by collection at the merge of origin/master 6967142a: unit 18,125 (+37), artifact 4,261 (+8); integration_psid 1,450, reproduction_legacy 520 and oracle_policyengine 308 unchanged; total 24,664. The tier policy test passes against the full collection. Co-Authored-By: Claude Opus 5.5 --- tests/README-tiers.md | 6 +++--- tests/tier_counts.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/README-tiers.md b/tests/README-tiers.md index e2715c124..1f5976c1f 100644 --- a/tests/README-tiers.md +++ b/tests/README-tiers.md @@ -39,9 +39,9 @@ pytest --collect-only -q -m oracle_policyengine | tail -1 | Tier | Tests at HEAD | |---|---:| -| `unit` | 18,088 | -| `artifact` | 4,253 | +| `unit` | 18,125 | +| `artifact` | 4,261 | | `integration_psid` | 1,450 | | `reproduction_legacy` | 520 | | `oracle_policyengine` | 308 | -| **Total** | **24,619** | +| **Total** | **24,664** | diff --git a/tests/tier_counts.json b/tests/tier_counts.json index 0303dc6ab..032190c9b 100644 --- a/tests/tier_counts.json +++ b/tests/tier_counts.json @@ -1,8 +1,8 @@ { "schema_version": 1, "counts": { - "unit": 18088, - "artifact": 4253, + "unit": 18125, + "artifact": 4261, "integration_psid": 1450, "reproduction_legacy": 520, "oracle_policyengine": 308 From a9ff290e48e85a3a8309fdac2a241476de3ef0d5 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 24 Oct 2026 08:02:37 -0400 Subject: [PATCH 12/12] U2 #556: reword three record lines; record reviews r5 and r5b MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec §19, round 2's finding 6 now says it landed in a later commit, on 2026-10-23. In docs/design/u2_m2_exposure.md the block, rulings and appendix line and round 3b's runs line are reworded. The exposure record also adds review r5, the cancelled r5b job, the in-session r5b review, the 2026-10-24 post-merge checks and the coordinating session's later messages, and §19 records r5 and r5b. u1_identity.json repins the specification's SHA-256. Docs and one pin only; no code or test changes. Tests (empty PSID root): tests/track_u2 and tests/track_u, 1,607 passed and 254 skipped; the tier check collects 24,664, unchanged. Co-Authored-By: Claude Opus 5.5 --- data/external/track_u2/u1_identity.json | 2 +- docs/design/boomers2004_1946_55_comparison.md | 3 ++- docs/design/u2_m2_exposure.md | 19 +++++++++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/data/external/track_u2/u1_identity.json b/data/external/track_u2/u1_identity.json index d6a9a9319..35d85662d 100644 --- a/data/external/track_u2/u1_identity.json +++ b/data/external/track_u2/u1_identity.json @@ -60,7 +60,7 @@ "draft_3_commit": "f7412e00cdcdba6ee1156b74fa93039f5329cb8f", "draft_3_git_blob": "e07ee0d589d20f3b6d100e5d4ce173f9a653f5e9", "version": "u2-ratified-1", - "sha256": "629f62e8b639741a6de0215ae4755b174f56a03a94e851c63a19245b57d5994e", + "sha256": "a6cbbd61acfab8e1f0bff0fa42e8534cb08b4ebb3641f3547ff16048daccb9dd", "cited_lines_sha256": { "74": "7b299c9e4473fd51d04bdfec51af83268e517181ab734d3e76ccd1c744f6daa9", "75": "44454cc04e6a50863035d69ccb53edec175b4ad70a19d8877437901d2deb8428", diff --git a/docs/design/boomers2004_1946_55_comparison.md b/docs/design/boomers2004_1946_55_comparison.md index e2148cca3..7bb6a19a2 100644 --- a/docs/design/boomers2004_1946_55_comparison.md +++ b/docs/design/boomers2004_1946_55_comparison.md @@ -2273,12 +2273,13 @@ Revision verification comprised the two touched test files and `tests/track_u2/t - **Finding 2.** The exposure record names this lane's session, its scratchpad and every U1 differential run, with both checkouts, and reads d1649's ruling correctly. - **Findings 3 and 5.** Tests now make each half of the d1242 guard fire on its own, and test amendment 6's option. - **Finding 4.** A missing d1090 exclusion entry refuses with a message instead of a bare `StopIteration`. - - **Finding 6.** A test holds the d1597 pin equal to the one U1's committed artifact records, comparing only that hash. It landed after the coordinating session's protective PSID hold of 2026-10-23 was lifted the same day (d1899). + - **Finding 6.** A test holds the d1597 pin equal to the one U1's committed artifact records, comparing only that hash. It landed in a later commit, on 2026-10-23. - **Finding 7.** §15 and §20 step 5 say exactly what the preflight checks of the registration: an issue #42 comment URL and an equal binding. That the comment exists, and that the forecast came before it, are kept by process. - **Finding 8.** A test holds `u1_identity.json`'s version equal to the code's. - This round changes code in `rows.py`, so the U1 differential ran again at the new last code change, `14d9576a`: equal in every check (`docs/design/u2_m2_u1_differential_14d9576.json`; §20 step 6). - After the delta review of `a2e2d688` (GPT-6.1 Sol, Subfleet job `20261023-063829-u2-step5-review-r3b`, APPROVE, with every round-2 finding resolved), its one nit is fixed: the exposure record said the round-2 findings were answered in one commit, and finding 6's came later. An earlier round-3 job (`20261023-062935-u2-step5-review-r3`) was cancelled before it launched, so that one review would also cover finding 6's commit. - After the delta review of `ac808d4a` (GPT-6.1 Sol, Subfleet job `20261023-073938-u2-step5-review-r4`, APPROVE), its nit is fixed: the entry above had been inserted inside round 2's list and now follows it. +- After the delta review of `7fc7ab21` (GPT-6.1 Sol, Subfleet job `20261023-121425-u2-step5-review-r5`), which found no defects but withheld approval because its read boundaries blocked two checks, and a follow-up review that ran those checks (an in-session Claude Opus 5.5 reviewer, APPROVE at `7fc7ab21`; both reports in the same folder): finding 6's entry above and two lines of the exposure record are reworded, and the exposure record adds both reviews. ## 20. Ratification and execution record diff --git a/docs/design/u2_m2_exposure.md b/docs/design/u2_m2_exposure.md index 36075f761..e9009c6c3 100644 --- a/docs/design/u2_m2_exposure.md +++ b/docs/design/u2_m2_exposure.md @@ -380,12 +380,12 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses **Read:** -- `EV/RESTRICTED-FILES.md`, in full and first, through a saved copy of the tool output in this session's own transcript folder. No other session's file in that project folder was opened. The harness also auto-loaded that project's memory index (`MEMORY.md`, one-line pointers); no memory file was opened. +- `EV/RESTRICTED-FILES.md`, in full and first, through a saved copy of the tool output in this session's own transcript folder. No other session's file in that project folder was opened. The harness also auto-loaded that project's memory index (`MEMORY.md`, one-line pointers); no memory file was opened. It was read again, in full and the same way, on 2026-10-24. - `EV/track-u2-step4-pass-20261021-run2/MANIFEST.md`, which the restricted list allows to be read and cited, and its SHA-256, which matched `acf5d8a4…e6d`. No other file in that folder and nothing in `EV/track-u2-step4-pass-20261019/` was opened or listed. - The chief-of-staff decision ledger's entries for d514, d637, d745, d1090, d1242, d1597, d1649 and d1724 (decision, ruling, ruling time and notes), selected by id, and the names in its folder. - On 2026-10-22, to check the review's finding 1: the field names of the d1597, d1649 and d1724 ledger records; d1724's `why` field, which states that the first pass (2026-10-19, `640adccf`) passed d1597's U1 load; and d1649's `ruling`, which requires that load to run before the pass. -- The coordinating session's brief, the message that started this lane, and its later messages of 2026-10-21 and 2026-10-22. They carry value-free facts only: the step-4 outcomes this lane records, the evidence record's SHA-256 as its MANIFEST pins it, and the tier-slot queue. -- The review lane's report, `EV/rulings-records-20261021-u2-step5/review-r1b-out.md`, and the names in the evidence folder that match `rulings-records` (names only). +- The coordinating session's brief, the message that started this lane, and its later messages of 2026-10-21 to 2026-10-24. They carry value-free facts only: the step-4 outcomes this lane records, the evidence record's SHA-256 as its MANIFEST pins it, the tier-slot queue, the review lanes' read boundaries and the wording of this lane's records. +- The review lanes' reports in `EV/rulings-records-20261021-u2-step5/` (`review-r1b-out.md`, `review-r2-out.md`, `review-r3b-out.md`, `review-r4-out.md` and `review-r5-out.md`), round 5's full report in its own Subfleet worktree, and the names in the evidence folder that match `rulings-records` (names only). - The repository at `1e739d32`: `CLAUDE.md`; the specification in full; the U1 specification's header, §15 block and §20; the U2 package, scripts and tests named in the PR; `data/external/track_u2/u1_identity.json` and the registries' entry ids and fields; the U2 SSI capture's provenance fields and its source manifest's records; `tests/README-tiers.md`; Track M's `specification.py` header; `estimates/cola_age_profile.specification_unratified_fields`. Also `~/PolicyEngine/CLAUDE.md`. **Not opened:** any other evidence-folder path; any comparator folder, seal or Boomers Report copy; `runs/track_b_b2_*`, `docs/design/track_b_attempt_ledger.md` and `paper/paper.qmd`; other sessions' transcripts or scratchpads; `~/.subfleet/` other than this session's own review jobs; any PSID file or codebook. @@ -393,12 +393,13 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses **Who and where.** This lane is the Claude Code session `59b436cf-29b0-43ac-a811-411fcc960646`. Its scratchpad, `/private/tmp/claude-501/-Users-maxghenis-Library-Application-Support-Claude-scratch-workspaces-ee3763f4-…-scratch-2026-09-22-b65ec9/59b436cf-29b0-43ac-a811-411fcc960646/scratchpad/`, is its own. Until the host wiped it, on 2026-10-22, it held this lane's virtual environment (`venv/`), the empty PSID directory (`empty-psid/`), its mutation runners and its differential reports. From then on they live outside the repository: the virtual environment at `~/PolicyEngine/_clones/md-u2-step5-venv` (Python 3.14.7; pandas pinned to 3.0.6, the version the step-4 evidence froze), the empty PSID directory at `~/PolicyEngine/_clones/md-u2-step5-empty-psid`, and the mutation runners at `~/PolicyEngine/_clones/md-u2-step5-mut`. The lane opened no other session's scratchpad or transcript. **Ran:** -- **Tests.** The U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to the empty directory, which stayed empty, so the checks that need PSID documentation skipped. Also Black and Ruff, the tier recount by collection, and the mutation checks listed in the PR. -- **Block, rulings and appendix checks.** Every one reads only the specification, the committed registries and the code. After the PSID hold of 2026-10-23 was lifted (d1899), one identity test loads U1's committed artifact in memory to compare one hash, and prints nothing. While the hold stood, this lane ran only unit-tier and specification tests. +- **Tests.** The U2 and U1 test files, the specification tests and the birth-evidence artifact test, with `POPULACE_DYNAMICS_PSID_DIR` set to the empty directory, which stayed empty, so the checks that need PSID documentation skipped. Also Black and Ruff, the tier recount by collection, and the mutation checks listed in the PR. On 2026-10-24, the tier check and the adjudication tests also ran on a local merge of the head with master `4fc2b9f4` (`~/PolicyEngine/_clones/md-u2-step5-mergecheck`). +- **Block, rulings and appendix checks.** Every one reads only the specification, the committed registries and the code. From finding 6's commit on, one identity test loads U1's committed artifact in memory to compare one hash, and prints nothing. For part of 2026-10-23, before that commit, this lane ran only unit-tier and specification tests. - **The U1 differential** (`scripts/u2_u1_differential.py`, invented data only, with the empty PSID directory and this lane's virtual environment). Its two checkouts are worktrees of this repository: the base, `~/PolicyEngine/_clones/md-u2-diff-base-9cee2423`, detached at `9cee2423`, and the candidate, `~/PolicyEngine/_clones/md-u2-diff-cand`, detached at the head under test. - **Trial at `690a1276`** (2026-10-21): equal in every check. - **At `1742efb1`** (2026-10-22): equal in every check; report committed as `docs/design/u2_m2_u1_differential_1742efb.json`. - **At `14d9576a`** (2026-10-23), the revision's last code change: equal in every check; report committed as `docs/design/u2_m2_u1_differential_14d9576.json`. This run and every later one used the relocated virtual environment and empty PSID directory. + - **Again on `14d9576a`'s tree** (2026-10-24), in a local commit that was never pushed: equal in every check. Its report was not committed. - Each run's dry run wrote only to a temporary directory the harness made and removed. **Values:** it stated and inferred no 1946–55 value or direction. Every number it wrote is a hash, a commit, a CI run id, a date, a line number, a registry entry count, a test count or a constant of the code or the specification. @@ -423,6 +424,12 @@ A Claude Code session (Claude Opus 5.5), started by the dynamics coordinator ses - **Subfleet `20261023-062935-u2-step5-review-r3`.** It waited for capacity and was cancelled before it launched, so that one review could also cover finding 6's commit. It read nothing. - **Subfleet `20261023-063829-u2-step5-review-r3b`.** It ran on GPT-6.1 Sol and delta-reviewed `a2e2d688` against `9f79818a`. It returned APPROVE, with one nit about this record's wording, answered in the next commit. - **What it read outside its worktree:** `EV/RESTRICTED-FILES.md`, the run-2 `MANIFEST.md`, the round-2 report, the eight decision-ledger records selected by id, and a review-workflow skill file (`~/.agents/skills/gitnexus-pr-review/SKILL.md`). It also listed the empty PSID directory, which was empty. - - **Its runs:** the full related suites with the empty PSID root, after the PSID hold was lifted. They read committed artifacts as CI does, and it printed no value from them. + - **Its runs:** the full related suites with the empty PSID root. They read committed artifacts as CI does, and it printed no value from them. - It opened no PSID record or codebook and computed nothing from PSID records. - **Subfleet `20261023-073938-u2-step5-review-r4`.** It ran on GPT-6.1 Sol and delta-reviewed `ac808d4a` against `a2e2d688`. It returned APPROVE, with one nit about the order of §19's entries, answered in the next commit. It read the round-3 reports, and in the clone it ran the specification tests with the empty PSID root. It opened no PSID file. +- **Subfleet `20261023-121425-u2-step5-review-r5`.** It ran on GPT-6.1 Sol and delta-reviewed `7fc7ab21` against `ac808d4a`. It found no defects. It withheld approval because its read boundaries blocked two checks: the full-suite tier collection, and four adjudication tests that read saved documentation fixtures. It read `EV/RESTRICTED-FILES.md` and the round-4 report, ran the specification tests with the empty PSID root, and opened no PSID file. +- **Subfleet `20261024-072931-u2-step5-review-r5b`.** No lane could admit it, and it was cancelled before it launched. It read nothing. +- **Round 5b, an in-session Claude Opus 5.5 reviewer.** This lane started it with a fresh context, and it returned APPROVE at `7fc7ab21`. + - **Its runs:** those two checks, with nothing deselected, at `7fc7ab21` and on a local merge of `7fc7ab21` with master `4fc2b9f4` (a `git archive` extraction in `~/PolicyEngine/_clones/md-u2-step5-r5b-mergetree` and a clone in its own scratchpad), all with the empty PSID root. + - **Its reads:** pytest's own reads at collection and run time were permitted, and it opened none of those files itself. Outside the review clone it read `EV/RESTRICTED-FILES.md`, the round-5 report and two files of the virtual environment (`pyvenv.cfg` and the editable install's `.pth`). + - It opened no PSID file, codebook, Track B file or comparator, and the empty PSID directory stayed empty.