Problem
_reject_formula_owned_outputs (us_runtime/puf_support.py) correctly enforces the invariant that populace imputes/exports only formula-less inputs — but it checks against a hand-maintained blocklist (PUF_TAX_DETAIL_FORMULA_OWNED_OUTPUTS). A blocklist goes stale silently as policyengine-us adds variables; a new formula-owned aggregate slipping into an imputation target list would bypass the guard.
Why the invariant matters (measured)
The imputation paper measured what independent imputation of identity-linked variables costs: on a four-component balance sheet whose identity (networth = fin + nfin − debt) holds exactly in the donor, fitting the components independently costs 5.8 points of classifier two-sample AUC — balance sheets that don't add up, invisible to every marginal metric. Deriving aggregates through the rules engine (the current invariant) makes that failure unrepresentable; the guard just needs to be complete.
Proposal
Derive the rejection set from policyengine-us variable metadata at build time — reject any requested output whose variable has a formula and is not an input — instead of (or as a superset check against) the static list. Keep the static list only if there are known exceptions, each with a stated reason.
🤖 Generated with Claude Code
Problem
_reject_formula_owned_outputs(us_runtime/puf_support.py) correctly enforces the invariant that populace imputes/exports only formula-less inputs — but it checks against a hand-maintained blocklist (PUF_TAX_DETAIL_FORMULA_OWNED_OUTPUTS). A blocklist goes stale silently as policyengine-us adds variables; a new formula-owned aggregate slipping into an imputation target list would bypass the guard.Why the invariant matters (measured)
The imputation paper measured what independent imputation of identity-linked variables costs: on a four-component balance sheet whose identity (networth = fin + nfin − debt) holds exactly in the donor, fitting the components independently costs 5.8 points of classifier two-sample AUC — balance sheets that don't add up, invisible to every marginal metric. Deriving aggregates through the rules engine (the current invariant) makes that failure unrepresentable; the guard just needs to be complete.
Proposal
Derive the rejection set from policyengine-us variable metadata at build time — reject any requested output whose variable has a formula and is not an input — instead of (or as a superset check against) the static list. Keep the static list only if there are known exceptions, each with a stated reason.
🤖 Generated with Claude Code