Skip to content

Stale TANF degenerate-input exclusion on main aborts every default US build before calibration (Build I fix never ported) #377

Description

@MaxGhenis

Summary

Current main (8264c49) contains a cross-gate contradiction that makes every default US release build abort before target materialization and calibration. The build seeds takes_up_tanf_if_eligible nonconstant and requires it nonconstant (take-up signal gate), while the degenerate-input gate's reviewed-exclusion register still excuses that same column as constant-True. The register's anti-rot rule then fires: a present, excluded column that carries signal is a stale exclusion → hard failure (no bypass flag exists for this gate). No build can pass both gates:

  • constant True → take-up signal gate fails ("universal take-up landmine"), tools/build_us_fiscal_refresh_release.py:5783-5798;
  • seeded nonconstant (the only path main() offers — seeding is unconditional at tools/build_us_fiscal_refresh_release.py:5778) → degenerate-input gate fails at tools/build_us_fiscal_refresh_release.py:6069-6087.

The abort happens after the expensive source stages (immigration, take-up, hours, SNAP, eligibility, pregnancy, SCF wealth, ACA, Medicaid) and before target materialization (:6110) and calibration, so it wastes the full source-stage compute on every attempt.

This blocks the next release run (Build J, #368) unless the runner hand-patches the worktree again.

The two contradictory pieces on main

  1. The register entry, tools/build_us_fiscal_refresh_release.py:423-426:
US_DEGENERATE_INPUT_REVIEWED_EXCLUSIONS = {
    "takes_up_tanf_if_eligible": (
        "TANF take-up imputation backlog; constant True forces 100% take-up."
    ),
  1. The seeding + nonconstant requirement, tools/build_us_fiscal_refresh_release.py:5778-5798 (with_us_take_up_inputs seeds TANF at SPM-unit by calibrated Bernoulli, 21.9%, per Seed take-up inputs across programs (parity with policyengine-us-data's stochastic flags) #312/Seed TANF and EITC take-up from administrative rates; engine-asserted take-up contract (#312) #315; us_take_up_signal_gate fails on a constant column and enforces the [0.10, 0.40] share band — packages/populace-build/src/populace/build/us_runtime/take_up.py).

The stale-exclusion rule that turns this into a hard abort is default_valued_columns_gate, packages/populace-build/src/populace/build/gates.py:702-710: an exclusion whose column is present and not degenerate is stale and fails ("so the list cannot rot").

The new #369 input-coverage gate independently confirms the fix direction: release_input_coverage_manifest.json lists takes_up_tanf_if_eligible with "status": "required" — i.e. the column must carry nondefault signal. Main currently ships two gates that demand TANF signal and one register entry that excuses its absence.

This is not hypothetical — it has fired twice

  1. Build G (2026-07-07): both Build G artifacts failed the Gate releases on input columns stuck at the engine default #286 degenerate-input gate on this stale register. The fix ("retire the now-Seed TANF and EITC take-up from administrative rates; engine-asserted take-up contract (#312) #315-seeded takes_up_eitc/takes_up_tanf_if_eligible exclusions") was ported into the Build G/H run machinery: export-mass gate fixes, M-CHIP skip, zero-support exclusions, SOI income-target wiring (#299) #357 branch — its sub-commit message documents exactly this — but the squash-merged final state of Build G/H run machinery: export-mass gate fixes, M-CHIP skip, zero-support exclusions, SOI income-target wiring (#299) #357 (aba4a8f) only retired takes_up_eitc. The TANF entry survived the merge.

  2. Build I (2026-07-08): the first sparse attempt aborted with

    RuntimeError: Release gates failed: Degenerate input signal failed: Stale reviewed
    exclusions — the column carries signal now, remove the exclusion: ['takes_up_tanf_if_eligible'].
    

    (chain log _buildi-runtime/logs/buildi-run/release_sparse.log:10). The gate-directed fix was committed as 055642662ea on the build-i-run branch only ("Build I: drop stale TANF degenerate-exclusion — rmloss100 selection carries take-up signal (gate-directed) (Build I: selection-side misc-income support fix for a certifiable 57k default successor #359)") and was never PR'd to main. Materialize Medicaid take-up via anchored count-calibration to CMS state snapshots #334's later register edit on main removed only the Medicaid entry.

So the same three-line fix has now been made twice on throwaway/build branches and lost twice. Main today reproduces the Build I abort deterministically.

Minimal reproduction (current main, no build required)

from populace.build.gates import default_valued_columns_gate

gate = default_valued_columns_gate(
    {"takes_up_tanf_if_eligible": [True, False, True, False]},  # seeded, healthy
    {"takes_up_tanf_if_eligible": True},                        # engine default
    reviewed_exclusions={
        "takes_up_tanf_if_eligible": "TANF take-up imputation backlog; ..."
    },
)
assert not gate.passed
# failures: ("Stale reviewed exclusions — the column carries signal now,
#             remove the exclusion: ['takes_up_tanf_if_eligible'].",)

The constant-True counterfactual passes this gate but fails us_take_up_signal_gate — the pincer is closed on both sides.

Fix

  1. Delete the takes_up_tanf_if_eligible entry from US_DEGENERATE_INPUT_REVIEWED_EXCLUSIONS on main — a three-line deletion mirroring 055642662ea on build-i-run.
  2. Add the cross-register consistency test that would have prevented both recurrences: for every program in seeded_take_up_programs(), assert its variable does not appear in US_DEGENERATE_INPUT_REVIEWED_EXCLUSIONS (and, symmetrically, that no "status": "required" column of the Full eCPS input-column coverage as a HARD release gate + reform-coverage smoke (#368 Deliverable 1) #369 coverage manifest carries a degenerate-input exclusion). The registers each have local anti-rot rules, but nothing checks them against each other — that is exactly the gap this bug lives in.

Acceptance criteria

Cross-links


Surfaced by an external architecture review (Codex/gpt-5.6-sol, 2026-07-09); citations independently verified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions