Gate validation-config input coverage against source-stage outputs#311
Merged
Conversation
A validation config scores a policy provision whose effect is driven by pure-input leaves (engine variables with no formula). If a row's critical leaf is produced by no source stage, the provision is a structural zero and the row validates as a silent zero until an external benchmark exposes it (#252 auto-loan qualifying interest, #253 education tuition). This pure gate fails the build when a required leaf is neither a declared stage output nor allowlisted with a reason. Keyed on curated provision-critical leaves, not the full transitive input closure of the row measure: the closure of a broad measure like income_tax is the whole tax base (hundreds of legitimately-defaulted leaves), so a closure gate would drown the signal. Mirrors the reviewed-exclusion, stale/dormant, and named-failure conventions of the surrounding gates. Refs #272 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
us_validation_input_coverage_gate builds the named US gate: every provision-critical input leaf the reform-validation configs depend on must be a declared source_stages.json output or carry a reviewed exclusion with its tracking issue. Seeded with the two invisible-gap instances (#253 qualified_tuition_expenses, #252 qualified_passenger_vehicle_loan_interest), each allowlisted with its open issue. The registry names the provision-critical leaves per validation row, not the transitive input closure of the row measure. assert_validation_leaf_registry_ current proves each entry against the live PolicyEngine-US graph via the engine tracer — the leaf must be a pure input and a dependency of its provision — so the registry cannot rot as the engine evolves. Both engine-dependent paths degrade to a no-op without the [us] extra, so the base test suite and the wheels job (no policyengine-us) pass unchanged. The failing-first plant-a-missing-leaf tests prove both the pure gate and the public entry point fail loudly when a validation row keys on an un-imputed, un-allowlisted input leaf. Refs #272 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs us_validation_input_coverage_gate() as an early preflight in main (before the expensive calibration, next to the CD-vintage preflight), after checking the registry against the live PolicyEngine-US graph. A validation row keying on an un-imputed, un-allowlisted input leaf now fails the build with a named release-gate error instead of shipping a silent structural zero (#252/#253). The gate result is also recorded in calibration_diagnostics.json under release_gates.validation_input_coverage for auditability. Refs #272 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 tasks
e3e1082 to
d5e390d
Compare
…input-gate test_gates.py: keep main's TestFitWeightRecord/TestWeightsAuditGate and this branch's TestSourceStageInputCoverageGate; both are pure additions whose conflict came from coincidentally identical closing lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d5e390d to
331ee56
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #272.
Why
The reform-validation configs (
obbba_reforms.json,tax_expenditure_reforms.json,soi_baseline_levels.json) score policy provisions on the calibrated dataset. A provision's simulated effect is driven by pure-input leaves — PolicyEngine-US variables with no formula, whose values must come from a source stage. When a provision's critical leaf is produced by no stage, the provision is a structural zero and the row validates as a silent zero until an external benchmark exposes it. Nothing checked this, which is exactly how the recent gaps stayed invisible:qualified_tuition_expenses— populated on 641 of 160,858 person records, so education credits validate ~40% low (qualified_tuition_expenses essentially unimputed (641 records, $4.5B) — education credits understated ≥40% #253); andqualified_passenger_vehicle_loan_interest— never imputed, so the provision is structurally $0 (qualified_passenger_vehicle_loan_interest is never imputed — OBBBA auto-loan deduction is structurally $0 #252).What
A release gate that fails the build when a validation row's provision-critical input leaf is neither produced by a source stage nor allowlisted with a reason.
source_stage_input_coverage_gate(pure, ingates.py): given{leaf -> validation rows that need it}, the declared source-stage outputs, and reviewed exclusions, it passes iff every required leaf is a declared output or carries a reason. Mirrors the reviewed-exclusion, stale/dormant, and named-failure conventions of the surrounding gates (a failure names the leaf and every affected row).us_validation_input_coverage_gate+ theUS_VALIDATION_PROVISION_INPUT_LEAVESregistry (inus_runtime/validation_input_coverage.py): resolves the declared surface fromsource_stages.json(typedSourceManifestread) and runs the pure gate. Seeded with the two invisible-gap instances above, each allowlisted with its open issue.assert_validation_leaf_registry_current— anti-rot: each registry entry is checked against the live PolicyEngine-US graph via the engine tracer (the leaf must be a pure input and an input-leaf dependency of its provision), the reverse-direction check that keeps the other engine-derived guards from rotting. A no-op without the[us]extra.build_us_fiscal_refresh_release.pyas an early preflight inmain(before the expensive calibration, next to the CD-vintage preflight), and recorded incalibration_diagnostics.jsonunderrelease_gates.validation_input_coverage.Scope decision — curated critical leaves, not the transitive closure
The issue's literal phrasing is "every pure-input leaf a config variable depends on." Applied as the full transitive closure this is intractable as a gate:
education_tax_credits/cdcceach touch 195 input leaves, and even after unioning every packaged input surface (source-stage outputs + CPS-carried + PUF defaults + structural) the residual undeclared set is 145 leaves — energy-credit expenditures, clean-vehicle flags, state-specific earnings, take-up flags. A gate failing on 145 leaves behind a 145-entry allowlist would drown the real signal and would never have singled out #252/#253. The registry instead names, per validation concern, the leaf whose absence zeroes that provision — the small high-signal set the issue's evidence table describes — and is kept honest by the live-graph consistency check rather than being a static list that silently rots. A new validation row whose provision keys on an un-imputed, un-allowlisted leaf fails CI here instead of shipping a silent zero.Open question for review: this catches gaps for registered provisions. If you'd rather catch every new un-imputed leaf automatically (at the cost of a ~150-entry reasoned allowlist and churn as PolicyEngine-US adds variables), the pure gate already supports that mode — say the word and I'll widen the registry to the full closure.
Tests
TDD, failing-first. The plant-a-missing-leaf tests prove both the pure gate and the public entry point fail loudly (with the variable and affected row named) when a validation row keys on an un-imputed, un-allowlisted input leaf; passing cases and stale/dormant-exclusion handling are covered too. The live-graph registry-consistency test runs where the
[us]extra is installed.Local, reproducing each CI job:
testjob (uv sync --all-packages, no[us]extra):uv run pytest— 1348 passed, 48 skipped;uv run ruff check .— clean.wheelsjob (built wheels installed into a clean venv, no[us]extra):pytest packages/*/tests— 1309 passed, 57 skipped; the new module is present in the builtpopulace_buildwheel and importable.[us]extra installed, the live-graph registry check passes against real PolicyEngine-US.Note on #307
This branch originally also carried a deflake for
test_refit_l0_selection_threads_l2_lambda_to_refit(#307). PR #309 (l2-anchor-semantics) already fixes #307 at the root cause — it replaces the noisy ESS-direction assertion with the threading contract and shows the direction this branch's epsilon asserted actually inverts at production scale — so that change was dropped and this PR is #272-only, with no overlap ontest_solve.py.🤖 Generated with Claude Code