Fail the eCPS parity gate on stale exemptions; remove six rotted entries#337
Conversation
…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>
|
Follow-up from Max's #294 review: the static register guard now derives its produced set from contract treatments ( Deliberately keyed on the treatment string rather than #334's |
…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>
…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>
…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>
b943eb3 to
4f6b760
Compare
MaxGhenis
left a comment
There was a problem hiding this comment.
Approve
Verified the removals and the fail-on-stale mechanism against origin/main.
Every removed exemption is genuinely rotted. I reconstructed the static-test produced set on origin/main (immigration + hours + eligibility + SNAP take-up + take-up-contract programs treated seed/count_calibrated) and intersected it with the register. The stale set is exactly the six this PR removes — no more, no less:
| removed | why it is produced now |
|---|---|
hours_worked_last_week, weekly_hours_worked_before_lsr |
in US_HOURS_WORKED_OUTPUT_COLUMNS (the #293/#242 hours stage) |
immigration_status_str, ssn_card_type |
in US_IMMIGRATION_OUTPUT_COLUMNS |
takes_up_eitc, takes_up_tanf_if_eligible |
populace_treatment: "seed" in take_up_contract.json — the only two produced take-up flags |
Because the main loop only fails when a reference-populated layer is candidate-all-zero, removing an exemption for a layer the candidate populates (cand_share > 0) produces no failure — the gate passes without it. Keeping it now fails. Register shrinks 103 → 97.
Kept entries are correctly kept. The remaining take-up exemptions (takes_up_medicaid_if_eligible, medicare, ssi, dc_ptc, head_start, early_head_start, housing_assistance) all carry rate_unsourced/near_universal, i.e. not produced, so they're legitimately still exempted. takes_up_medicaid_if_eligible is rate_unsourced on main — that's why it's the docstring's illustrative rot case rather than a removal here.
Fail-on-stale matches the #286 "cannot rot" pattern — literally. The new stale/dormant block is a direct port of the reviewed-exclusions structure already sitting above parity_gate in gates.py (the #286 degenerate-input gate: "Stale reviewed exclusions — the leaf is produced now, remove the exclusion"). The parity adaptation is correct on two axes: stale keys off candidate share (a produced layer makes the exemption a lie, even when the reference lacks it), dormant keys off reference share and is guarded by name not in stale. gap_count is snapshotted before the stale append, so stale exemptions don't inflate the gaps metric — test_stale_exemption_does_not_mask_real_gaps pins exactly that.
SNAP-train interaction is clean. No column carried by the recently-merged SNAP train (#350/#352/#353) — is_disabled, is_blind, is_full_time_college_student, own_children_in_household, veterans_benefits, nor any snap/abawd/pregnan input — is left in the register as a stale exemption; those producing PRs removed their own lines. takes_up_snap_if_eligible (out_of_scope in the contract, produced by the #294 stage) is correctly covered by the unconditional US_SNAP_TAKE_UP_OUTPUT_COLUMN term rather than by treatment. Post-removal the static test yields an empty stale set.
One cross-PR dependency worth calling out: #334 flips takes_up_medicaid_if_eligible to count_calibrated. Once both land, whichever merges second must ensure the medicaid register line is gone, or test_register_exempts_no_release_produced_layer fails — which is exactly the forcing function this PR installs (the comment already anticipates it). Confirm #334 drops that register entry.
The trailing test_missing_issue_is_refused hunk is a cosmetic one-line reformat. CI green (test + wheels, 3.13/3.14), MERGEABLE/CLEAN. Not merging (review-only per request).
Summary
parity_gateskipped 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_gateat gates.py:707,source_stage_input_coverage_gateat gates.py:1499). That let register entries rot silently once the candidate started producing the exempted layer, which is exactly what happened totakes_up_medicaid_if_eligiblewhen PR #334 started materializing the column.Gate change
parity_gatenow fails on any stale exemption — aknown_gapsname whose candidate nonzero share is positive — with a message to remove or re-reason it.stale_exemptionsanddormant_exemptions, flowing into the release manifest through_ecps_parity_gateunchanged.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):takes_up_tanf_if_eligibletakes_up_eitcssn_card_typeimmigration_status_strhours_worked_last_weekweekly_hours_worked_before_lsrThe 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_wagestays: 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_layerpins the statically-knowable release-produced set so a stale entry fails in CI rather than first on a release build:{seed, count_calibrated}(per Max's Assign SNAP take-up from reported receipt and the FNS participation rate #294 review: each producing PR removes its own register line; keyed on the treatment string rather than Materialize Medicaid take-up via anchored count-calibration to CMS state snapshots #334'scount_calibrated_take_up_programs()helper so this branch needs nothing that isn't on main yet, and picks up the Medicaid flip automatically in either merge order).Interaction with #334
#334 deletes the
takes_up_medicaid_if_eligibleexemption 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 (treatmentrate_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
TestParityGatecases: 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.test_us_parity_reference.py.test_gates.py,test_us_parity_reference.py, andtest_us_nonzero_shares.pypass (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