Skip to content

Fail the eCPS parity gate on stale exemptions; remove six rotted entries#337

Open
daphnehanse11 wants to merge 3 commits into
mainfrom
claude/distracted-cori-d88f21
Open

Fail the eCPS parity gate on stale exemptions; remove six rotted entries#337
daphnehanse11 wants to merge 3 commits into
mainfrom
claude/distracted-cori-d88f21

Conversation

@daphnehanse11

@daphnehanse11 daphnehanse11 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

parity_gate skipped names in the known-gaps exemption register (ecps_parity_known_gaps.json) unconditionally, with no staleness check — unlike its two sibling gates, which both fail on stale reviewed exclusions (default_valued_columns_gate at gates.py:707, source_stage_input_coverage_gate at gates.py:1499). That let register entries rot silently once the candidate started producing the exempted layer, which is exactly what happened to takes_up_medicaid_if_eligible when PR #334 started materializing the column.

Gate change

  • parity_gate now fails on any stale exemption — a known_gaps name whose candidate nonzero share is positive — with a message to remove or re-reason it.
  • Exemptions for layers the reference never populates are classified dormant and only reported (different reference vintages populate different layers), matching the sibling gates' treatment.
  • Details gain stale_exemptions and dormant_exemptions, flowing into the release manifest through _ecps_parity_gate unchanged.

Register audit: six entries removed

Checked every exemption against the current release pipeline (rebased onto main after #293/#294/#345/#330/#344/#348/#350/#358 landed). The parity gate runs on the base frame after the immigration, take-up, hours-worked, SNAP take-up, and eligibility-input transforms, and each transform's own signal gate hard-fails unless its columns are present and non-constant — so six exempted layers are deterministically populated at parity-gate time (all confirmed in PolicyEngineUSEngine().variables(), so they are measured):

Entry Produced by
takes_up_tanf_if_eligible contract-seeded take-up (#315)
takes_up_eitc contract-seeded take-up (#315)
ssn_card_type immigration stage (#266)
immigration_status_str immigration stage (#266)
hours_worked_last_week hours-worked stage (#293, merged without removing its register lines)
weekly_hours_worked_before_lsr hours-worked stage (#293, merged without removing its register lines)

The register was seeded (#316) from a parity run of an artifact predating the first four; #293 merged after the seeding without cleaning up its two. hourly_wage stays: it is an org_wages output and that stage is not executed anywhere yet. #294 (SNAP) and #350 (eligibility inputs) removed their own register lines at merge, so nothing further rotted. The remaining entries are safe: the other source stages declaring exempted outputs (SCF wealth, ORG wages, SIPP tips, …) are manifest declarations no tool currently executes.

Static CI guard

test_register_exempts_no_release_produced_layer pins the statically-knowable release-produced set so a stale entry fails in CI rather than first on a release build:

Interaction with #334

#334 deletes the takes_up_medicaid_if_eligible exemption from the same register when it materializes Medicaid take-up. The hunks are disjoint, so the two PRs merge cleanly in either order, and they compose consistently: on current main the candidate does not yet produce that column (treatment rate_unsourced), so the entry stays; once #334 lands, its own deletion is exactly what the new enforcement (and the static guard, via the treatment flip) demands. The medicaid case is the motivating example for this change. Closes the follow-up from the #334 review.

Tests

  • Five new TestParityGate cases: stale fails and names the variable; stale fails even when the reference lacks the layer; dormant passes but is reported; a stale exemption does not mask real gaps; clean-exemption details stay empty.
  • The static register guard above in test_us_parity_reference.py.
  • test_gates.py, test_us_parity_reference.py, and test_us_nonzero_shares.py pass (141 tests); ruff check/format clean.

Note: the runtime enforcement means the next release build fails if any exemption not statically verifiable here turns out to be populated on the real base H5 — that failure names exactly which entry to remove or re-reason, which is the intended behavior.

🤖 Generated with Claude Code

MaxGhenis added a commit that referenced this pull request Jul 7, 2026
…duces it

The register reason ('Populace does not yet produce it') becomes false once
this PR's SNAP stage populates the layer, and the entry would fail the
stale-exemption check when #337 lands. #337 removes the four entries rotted
by already-merged work; each producing PR removes its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Follow-up from Max's #294 review: the static register guard now derives its produced set from contract treatments ({seed, count_calibrated}) rather than seeded_take_up_programs(), so programs materialized by count calibration (Medicaid in #334, CHIP next per the contract followup) are covered in CI instead of first failing on a release build.

Deliberately keyed on the treatment string rather than #334's count_calibrated_take_up_programs() helper, which doesn't exist on main yet — this branch stays green now, and the guard picks up the Medicaid flip automatically in either merge order (#334 deletes its register line in the same commit that flips the treatment). A future PR that flips a treatment but forgets the register line fails this test.

@daphnehanse11 daphnehanse11 requested a review from MaxGhenis July 7, 2026 14:42
daphnehanse11 pushed a commit that referenced this pull request Jul 7, 2026
…duces it

The register reason ('Populace does not yet produce it') becomes false once
this PR's SNAP stage populates the layer, and the entry would fail the
stale-exemption check when #337 lands. #337 removes the four entries rotted
by already-merged work; each producing PR removes its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
daphnehanse11 added a commit that referenced this pull request Jul 7, 2026
…ate (#294)

* Assign SNAP take-up from reported receipt and the FNS rate

Add a snap_take_up source stage (manifest entry, runtime handler, frame
transform, release gate, builder wiring) following the immigration and
hours templates. Reported SPM_SNAPSUB recipients always take up;
non-reporters fill via seeded stable-identity draws to the cited FNS
0.82 participation rate. Heals the published all-True landmine.
Closes #243; second half of #248 alongside #293.

* Remove the takes_up_snap_if_eligible parity exemption: this stage produces it

The register reason ('Populace does not yet produce it') becomes false once
this PR's SNAP stage populates the layer, and the entry would fail the
stale-exemption check when #337 lands. #337 removes the four entries rotted
by already-merged work; each producing PR removes its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Max Ghenis <mghenis@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
daphnehanse11 and others added 3 commits July 8, 2026 12:41
…ries

parity_gate skipped known-gap names unconditionally, unlike its sibling
gates (default_valued_columns_gate at gates.py:707 and
source_stage_input_coverage_gate at gates.py:1499), which both fail on
stale reviewed exclusions. That let register entries rot silently once
the candidate started producing the exempted layer (the
takes_up_medicaid_if_eligible case surfaced in PR #334).

The gate now fails on any exemption whose candidate nonzero share is
positive, reports exemptions the reference never populates as dormant,
and records both in the gate details. Auditing the register against the
current release pipeline found four entries stale on arrival — the
pipeline deterministically materializes them before the gate runs:
takes_up_tanf_if_eligible and takes_up_eitc (contract-seeded take-up,
PR #315), ssn_card_type and immigration_status_str (immigration stage,
#266). A new register test pins that statically-knowable subset so a
stale entry is caught at test time, not first at release-build time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Max's review of #294 (SNAP take-up) spotted that the static guard
covered immigration outputs plus seeded_take_up_programs() only, so a
program whose stage produces its flag under a different contract
treatment sat outside it — SNAP (out_of_scope) was hand-fixed on #294,
and count_calibrated programs (Medicaid in #334, CHIP next per the
contract followup) had the same exposure: the staleness failure would
only surface on the first release build, not in CI.

The guard now derives the produced set from contract treatments
({seed, count_calibrated}) instead of the seeded helper, making it the
CI enforcement of the rule that each producing PR removes its own
register line. Keyed on the treatment string rather than #334's
count_calibrated_take_up_programs() helper so the test imports nothing
that does not exist on main yet; it picks up the Medicaid flip (and any
future CHIP flip) automatically at merge, in either merge order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he guard

The #293 hours stage merged without removing its parity register
exemptions, so hours_worked_last_week and weekly_hours_worked_before_lsr
carried false "not yet on the candidate frame" reasons — the stage runs
before the parity gate and its signal gate requires both non-constant,
so the first release build after the staleness enforcement lands would
have failed on them. Both confirmed as engine input variables (its third
output, weeks_worked, has no register entry). hourly_wage stays: it is
an org_wages output and that stage is not executed anywhere yet.

The static register guard now also pins the deterministic stage output
constants (hours, SNAP take-up, eligibility inputs) alongside the
immigration outputs and contract-produced take-up flags, so the next
producing stage that forgets its register line fails in CI rather than
on a release build — #293 slipped through exactly because the guard
did not know stage outputs, only take-up programs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@daphnehanse11 daphnehanse11 force-pushed the claude/distracted-cori-d88f21 branch from b943eb3 to 4f6b760 Compare July 8, 2026 16:44
@daphnehanse11 daphnehanse11 changed the title Fail the eCPS parity gate on stale exemptions; remove four rotted entries Fail the eCPS parity gate on stale exemptions; remove six rotted entries Jul 8, 2026
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