Skip to content

Register gate_epuf_fill: learned career fills scored on held-out EPUF (awaiting ratification) - #515

Open
MaxGhenis wants to merge 18 commits into
masterfrom
epuf-career-fill-20261003
Open

MaxGhenis wants to merge 18 commits into
masterfrom
epuf-career-fill-20261003

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

What this does

This PR registers gate_epuf_fill, which scores fills learned from SSA's 2006 Earnings Public-Use File (EPUF) against the career assembler's two fixed fill rules:

  • each odd income year from 1997 is the mean of its neighbours;
  • nothing counts before max(1968, birth_year + 22).

A gate here is a pass-or-fail test whose rules and thresholds are fixed and published before anything is scored against it.

It is stacked on #509, whose EPUF reader it uses. Merge #509 first.

State: the gate is registered, its floors are built, and three independent referee rounds have run. It is not locked. The lock waits on Max's ratification of the tolerance reading (decision d927). Until then epuf_fill_gate.test_part() refuses to read TEST, and no TEST person has been read.

The gate, in plain words

EPUF records capped taxable earnings every year from 1951 to 2006. The gate hides the years the PSID would be missing (odd years 1997-2005, and years before the career start), fills them, and compares the filled careers with the real ones on held-out persons.

  • Split: person-disjoint by salted hash. TRAIN 60%, DEV 20%, TEST 20%.
  • Cells: 326 in all.
    • Rank persistence: r1, r2, r3, r4, pr_in, pr_cross, yr_cross.
    • Zero-year rates by age: zint, zexit, wint, pzero, yzero.
    • Levels and positive-share quantiles.
    • The AIME under the statutory 35-year rule, plus a partial AIME through 2006 for later cohorts.
    • Each by sex, age band or cohort, with pooled groups.
  • Tolerance: one PSID-2010-size sampling standard error per cell. It comes from 200 pairs of disjoint real DEV samples at the PSID's size, which is the "two halves of real EPUF" floor read at the PSID's scale.
  • Bite: both checks hold on DEV.
    • The current odd-year rule fails 96 of 183 gating cells by more than two tolerances.
    • The current pre-career rule fails 112 of 136.
    • On the pooled 1930-45 cohorts, the pre-career rule lowers median AIME by 20% for men and 22% for women.

Full record: docs/amendments/gate_epuf_fill_registration_proposal.md.

Referee rounds (independent Opus 5.5 lanes)

Round Report Verdict
1 reviews/gate_epuf_fill_round1_referee_20261004.md AMEND BEFORE LOCK
2 reviews/gate_epuf_fill_round2_verification_20261004.md LOCK AFTER LISTED FIXES
3 reviews/gate_epuf_fill_round3_confirmation_20261004.md LOCK AFTER LISTED FIXES, no rebuild
  • Round 1 found a floor priced on the wrong population (now group_rows defines it once). It also asked for pooled cells and a union-mask scoring path.
  • Every fix is in the proposal's sections 12, 12a and 12b.

Disclosures

  • DEV candidate scores. Candidates were developed on DEV, which is allowed. Every DEV score is committed:
    • docs/amendments/gate_epuf_fill_dev_scores_before_amendment_1.json;
    • ..._after_amendment_1.json;
    • ..._after_round_2.jsonl.
  • Dry runs. The TRAIN dry runs are committed as lineage.

Invariants, and where they are tested

  • Floors and partition.

    • Every group's floor is priced on exactly the population its cells count. Pool size equals the cells' n.
    • Populations read no cell their own family masks.

    Tests: tests/harness/test_epuf_fill_gate.py, tests/test_epuf_fill_gate_floors.py.

  • Scoring path. score_candidate gives every fill both families' masked cells as unknown and refuses any output that:

    • is not finite on its own cells;
    • leaves [0, 1];
    • changes a cell it does not own.

    The scored matrix keeps every other cell true. Tested.

  • AIME. aime_35 equals ss.statutory_aime.aime for people born 1929-1945 (Hypothesis differential test).

  • Floor estimator. floor_group's sigma estimates the standard error of a mean within 15% (Hypothesis property test).

  • Artifacts.

    • The v3 floors recompute from the stored replicates.
    • v3 equals v2 exactly in truth, floors, tolerances and partition.
    • The build is bound to its commit and was clean.
    • epuf_fill_scoring pins v3's SHA-256.
  • TEST access. TEST is unreadable except through test_part() after lock. Tested with unlocked and wrong-id gates.yaml files.

Downstream

Nothing downstream changes in this PR. career.py stays byte-identical, sealed by the birth-evidence reducer. The DYNASIM projection comparisons (registered one-shots) keep the current rule. Adopting a learned fill for them needs a new registration, which will be queued once candidates are scored on TEST.

Remaining, after Max's ruling on d927

  1. Merge Register gate_epuf against SSA's Earnings Public-Use File (unlocked, report-only) #509, then this PR.
  2. Flip the draft block (docs/design/gate_epuf_fill_block_draft.yaml) into gates.yaml with locked: true.
  3. Register the candidates: code commit and fitted-artifact SHA-256.
  4. Score TEST once with epuf_fill_scoring.score_registered, and publish the result whether it passes or fails.

axiom: n/a: evaluation infrastructure only; no policy rule is encoded or changed.

Tests

  • 93 targeted tests pass on the host. The EPUF and PSID builds are host-only.
  • tests/tier_counts.json and tests/README-tiers.md are updated.

Merge method: merge commit only, never squash or rebase. The binding tests diff cb76ad1 (the registered floor build) against HEAD. If that commit drops out of master's history, they skip silently instead of failing.

🤖 Generated with Claude Code

MaxGhenis and others added 15 commits October 3, 2026 22:41
Work in progress, not yet a registration: no DEV floor or TEST value
has been computed. The rules commit will follow with the proposal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and floor

Includes a Hypothesis differential test of the vectorised AIME against
ss.statutory_aime.aime, and checks that the universes read no masked year.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draft: no DEV floor, no candidate fit, no TEST person read.

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

The rules commit. Floor groups at the PSID-2010 cohort's size, the
gating partition, scoring over 20 draw seeds, a pre-registered adoption
rule (certified / improves / not adopted), and two report-only oracles.
The PSID-scale and DEV floor builders follow, run at this commit. No DEV
floor, candidate or TEST person yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built at the rules commit 14045be from the staged PSID; unweighted.
Reads no EPUF value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built once on DEV at d21aa65 (rules 14045be plus the PSID-scale
counts). 130 of 136 cells gate; B1 (current odd-year rule) fails 48 of 70
gating odd cells by more than two tolerances, B2 (current pre-career
rule) 54 of 60 pre cells: the gate is lockable. Oracles O1 and O2 are
reported without a verdict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_epuf_fill_gate_floors.py recomputes sigmas, tolerances, the
partition and the checks on bite from the stored replicates and binds
the rules and builders to the build's commit. The opt-in gate module is
excluded from the birth-evidence reducer's identity seal and proven
unreachable from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…DEV candidate scores; dry-run lineage

The independent round-1 referee (Opus 5.5, subfleet job
20261004-012036-epuf-fill-referee-r1) found a blocking floor-pool
mismatch and asks for amendments before lock. Before amending, every
DEV score of a candidate produced so far is committed (finding 7), and
the TRAIN dry run that changed two rules is kept as lineage (finding
14; built from the working tree when HEAD was 61ade83).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each floor group's population is defined once (group_rows) and used by
both the cells and the floors (finding 1, blocking). Odd units start at
age 22 (finding 2). Pooled groups, AIME p10/p90, a partial AIME for
cohorts born 1946-1980, r3 and positive-share quantiles (findings 3, 9).
Every fill is given both families' masks as unknown through a registered
scoring path that checks its output, and TEST is read only through
test_part() after lock (findings 4, 5). The improves tier is capped at
three tolerances (finding 6). Dosed perturbations D1-D6 are reported
(finding 8). The proposal records each response and the forks.
The v2 PSID counts and DEV floors are built at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built at the amendment-1 rules commit 7061200; reads no EPUF value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built once on DEV at 23bee82 (amendment-1 rules 7061200 plus the v2
PSID counts). 319 of 326 cells gate; B1 fails 97 of 183 gating odd cells
and B2 112 of 136 pre cells by more than two tolerances, so the gate is
lockable. Dosed perturbations D1-D6 and oracles O1/O2 are reported
without a verdict. Tier counts updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The odd family owns only odd years inside the career; the current rules
are scored as fills through score_candidate (CurrentOddFill averages
dollars and falls back to one known neighbour, as _impute_gap does);
perturbations and oracles move only their family's own cells; D1 copies
shares; the build records whether its bound files were clean and names
each dose's cell. A registered TEST entry point (epuf_fill_scoring)
reads TEST only through test_part and is pinned to the coming v3 build.
The amendment-1 TRAIN dry run and every DEV candidate score after the
amendment-1 rules commit are disclosed. Floors are unchanged; the v3
build at this commit reproduces v2's floors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built once on DEV at cb76ad1 with its bound files clean. Its truth,
floors, tolerances and partition equal the v2 build's exactly (tested);
the current rules, now scored as fills, still fail 96 and 112 gating
cells by more than two tolerances. epuf_fill_scoring pins the build's
SHA-256. The proposal's results section reports v3, discloses the
cap-bound men's q90 cells and the dose cells, and logs DEV candidate
scores since round 2's fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No rebuild. The TEST scoring builds its own wage bases and NAWI and
records their hash, drops and reports a gating cell whose TEST truth is
undefined, loads each candidate through load_fill with its registered
SHA-256, and scores both readings of the current odd rule; the improves
allowance uses the smaller current gap per cell (round 3 finding 1:
in the PSID-2010 cohort 274 of 513 age-22 fills have the age-21 year
recorded). The DEV log runs through 07:45 UTC with candidate code bytes
kept from then. The draft gates.yaml block awaits Max's ratification of
the materiality tolerance (decision d927).

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

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
social-security-model Ready Ready Preview Oct 4, 2026 4:51pm UTC

Request Review

score_registered refuses a candidate spec without a 64-hex SHA-256 and
marks any run with an injected matrix or injected fills as not the
registered TEST scoring. Tests now reach the hash mismatch, check the
oracle's permutation within every stratum, check that the combined
current reading takes the smaller gap on real readings, and show the
neutral rule can only tighten the improves tier. The proposal notes
the two nits in bound files left as they are, the rounding of three
tolerance bounds, and that #516 merges before TEST.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run pointed at another gates.yaml or data folder is also marked as
not the registered TEST scoring. The combined-reading test now says
what it checks and asserts the two readings differ in a gating cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 4, 2026
…reachable commit

Rebasing onto #515's fixes left the manifest's code commit unreachable
from the branch. It is refitted at this commit; the artifacts' bytes are
unchanged. The branch syncs with #515 by merge from here on, so the
manifest's commit stays reachable.

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

Copy link
Copy Markdown
Contributor Author

Status at 9f2dd835

#509 merged as 492d5a6, so this branch now targets master. Merged rather
than rebased so the registered floor build's commit (cb76ad1) stays on
the branch's history, which the bound-file test diffs against.

Only the tier-count files conflicted; recounted against the full
collection: unit 6,017, artifact 3,388 (total 11,486).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis changed the base branch from epuf-gate-registration-20261001 to master October 4, 2026 16:50
MaxGhenis added a commit that referenced this pull request Oct 4, 2026
…ates branch

Brings in master through #509 (492d5a6). Merged, not rebased, so the
manifest's code_commit (b722382) stays on this branch's history; the
manifest, the staged fills and every pinned file are unchanged.

Only the tier-count files conflicted; recounted against the full
collection: unit 6,037, artifact 3,398 (total 11,516).

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

Copy link
Copy Markdown
Contributor Author

Head aa2928d9: synced with master after #509 landed, reviewed APPROVE (independent in-session Opus 5.5 reviewer with a shell; the Subfleet lane was queued on capacity, so I cancelled it; posted verbatim after the status).


Verdicts

Findings

  1. Use a merge commit for both PRs. This is a merge-method caveat, not a defect. The repo allows merge, squash and rebase. If either PR is squashed or rebased, cb76ad15 or b722382e drops out of master's history. CI checks out with fetch-depth: 0 (.github/workflows/tests.yml:24), so a fresh clone may not have those commits. The two binding checks then skip instead of failing, and the binding stops being enforced with no red check:

    • _assert_bound at tests/test_epuf_fill_gate_floors.py:259-265 skips with "the build's commit is not in this clone".
    • test_the_fill_code_is_unchanged_since_the_fit at tests/test_epuf_fill_candidates_manifest.py:73-80 skips the same way.

    Register gate_epuf against SSA's Earnings Public-Use File (unlocked, report-only) #509 itself went in as a merge commit (492d5a60).

  2. CI is not finished. gh pr checks shows pytest-shard (3.14, 2/4) and (4/4) pending on both PRs. Both are MERGEABLE and not drafts.

  3. The brief's master delta is understated. Register gate_epuf_fill: learned career fills scored on held-out EPUF (awaiting ratification) #515's real merge base is 5129ac32, not 65daf03b. Since that base, master also changed docs/design/gate_epuf_block_draft.yaml, scripts/render_gate_epuf_block_draft.py, docs/amendments/gate_epuf_registration_proposal.md, reviews/gate_epuf_round4_confirmation_20261002.md and tests/test_gate_epuf_block_draft.py. None of these overlaps a Register gate_epuf_fill: learned career fills scored on held-out EPUF (awaiting ratification) #515/EPUF-learned career fills: candidates, PSID-2010 application, registered before TEST #516 file, and grepping both PRs' files for those names finds nothing. src/ and gates.yaml are unchanged from 5129ac32 to master, so this doesn't matter.

What I verified

  • Heads and parents match the brief. aa2928d9 has parents 9f2dd835 and 492d5a60; ee1c2f78 has parents 881f0377 and aa2928d9. 65daf03b is an ancestor of master. cb76ad15 is an ancestor of both heads; b722382e is an ancestor of HEAD.

  • Only the tier files were resolved by hand. git show --remerge-diff for both merges touches only tests/tier_counts.json and tests/README-tiers.md. The only files both sides touched since 5129ac32 are those two.

  • The merges left the PR content unchanged. Excluding the tier files, diff(5129ac32..9f2dd835) is byte-identical to diff(origin/master..aa2928d9), and diff(9f2dd835..881f0377) is byte-identical to diff(aa2928d9..ee1c2f78). What was reviewed is what is proposed.

  • The tier counts add up (tiers are assigned per module, tests/conftest.py:89-104).

    Revision unit artifact
    Merge base 5129ac32 5,948 3,361
    Master 5,960 3,361
    aa2928d9 (Register gate_epuf_fill: learned career fills scored on held-out EPUF (awaiting ratification) #515) 6,017 3,388
    ee1c2f78 (EPUF-learned career fills: candidates, PSID-2010 application, registered before TEST #516) 6,037 3,398
  • Collection matches at HEAD (unit 6,037, artifact 3,398, total 11,516) and on a git archive export of aa2928d9 (6,017 / 3,388); the tier-count test passes on both.

  • Targeted tests at HEAD: 234 passed. On the aa2928d9 export: 198 passed, 3 skipped, 3 failed, all three because the export is not a git repo (git rev-parse --show-toplevel exit 128); they pass at HEAD.

  • Pinned and bound files are unchanged: cb76ad15..origin/master on epuf_cells.py, epuf_operator.py, data/epuf.py empty (same from 65daf03b); cb76ad15..aa2928d9 and cb76ad15..HEAD on the five bound files empty; b722382e..HEAD on the fit's code files empty; the manifest hashes to 83d17a14f960033c….

  • Merging rather than rebasing matches repo practice: 118 of the last 200 master commits are merges, 42 of them "merge master into the branch". CLAUDE.md says nothing about merge method.

What I could not verify

  • The git-dependent tests at aa2928d9 exactly (code identical to HEAD, where they pass).
  • The final CI result.

This branch was successfully deployed

1 active deployment
Preview — aa2928d9 Deployed Oct 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant