Skip to content

Fail the US housing gate on rent outside renter households, and zero it in the CPS draw - #1529

Merged
MaxGhenis merged 3 commits into
mainfrom
us-housing-gate-nonrenter-rent
Nov 6, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
us-housing-gate-nonrenter-rent

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1446 (step 2).

us_housing_inputs_signal_gate now fails when anyone outside a renter household carries positive pre_subsidy_rent. Until now the gate counted these people (positive_rent_nonrenter) and passed anyway. impute_us_pre_subsidy_rent now gives heads of owner and no-cash-rent households zero rent, so the CPS rent draw passes the stricter gate.

Why the gate fails this rent

#1444 fixed rent placement: impute_us_pre_subsidy_rent drew rent in household order and wrote it to heads in person order, so heads received other households' rent. with_us_housing_inputs returns a frame unchanged when the gate passes, and the gate passed misplaced rent, so a base built before the fix kept it.

The Route A support bases from before and after #1444 hold the same 907,863 people and differ only in person.pre_subsidy_rent. Each build writes the gate's counts to base_populace_us_2024_puf_support.summary.json:

Support base Gate positive_rent_nonrenter positive_rent_nonhead Weighted rent
c0513db, before #1444 passed 66,623 0 $648.26bn
9434c50, after #1444 passed 0 0 $672.02bn

With this change the first base fails the gate and the second passes.

Why the draw zeroes rent outside renter households

The ACS rent model draws for every CPS head, with tenure among its predictors. The shared QRF's zero-or-positive classifier gives each owner and no-cash-rent head a small chance of positive rent. On the Route A pre-clone frame (168,852 heads, 53,055 of them in renter households), the draw on main put rent on these heads outside renter households:

Seed Heads Weighted rent Largest draw
0 0 $0 none
1 1 $4.18m $5,520
2 1 $3.07m $3,960
3 4 $49.99m $20,400
4 2 $19.78m $12,000
5 2 $55.48m $24,000
6 3 $63.70m $22,800
7 4 $103.36m $63,600

Under the stricter gate, seeds 1 to 7 would each stop a support-base rebuild. The draw still runs for every head and then sets rent to zero outside renter households, so renters keep their draws. For seeds 0 to 7 on that frame, everyone in a renter household keeps main's draw, everyone else gets zero, and with_us_housing_inputs passes the gate. Route A's support base at 9434c50 used seed 0, and at seed 0 main's draw put rent on no one outside a renter household.

Where the gate runs

  • tools/build_us_puf_support_base.py checks it before support cloning. When it fails, the builder draws rent again from the ACS donor that --acs-h5 names, and without that flag the build stops. If the gate fails again after PUF-support imputation, the build stops.
  • tools/build_us_fiscal_refresh_release.py checks the base frame and raises RuntimeError with no redraw, so a release from a base that predates Place each ACS rent draw on its own household's head #1444 stops at the housing gate. The Route A release dry run reads the 9434c50 base through --base-h5, and that base passes.
  • The multispine pool calls with_us_housing_inputs when it prepares source inputs, so it draws such rent again.

Limits

  • If rent moves from one renter household's head to another's, the gate passes it, because both households rent.
  • ACS rows get pre_subsidy_rent from transfer_acs_inputs, which fits its own QRF on the donor and draws with the same sign classifier, and nothing afterwards sets owners' rent to zero. Published ACS rows put rent on 30% of ACS owners (State child SPM rates: next steps from the November trace #1446). A fiscal refresh that takes its base from --pool-manifest checks ACS rows, so any such rent stops it at this gate until State child SPM rates: next steps from the November trace #1446 step 3 zeroes owners' rent after the transfer. That arm already fails the gate's tenure check on main when ACS households keep OWNED_OUTRIGHT, which acs_inputs.py assigns to ACS tenure code 2.

Invariants

packages/microcosm-build/tests/engine_free/us/test_us_housing_inputs.py checks these properties:

  • For any household sizes and person order, the gate's count equals the number of draws that person-order placement moves off renter heads. The gate reports the renter failure line if and only if that count exceeds zero, and with heads listed in household order, placement moves nothing (Hypothesis, 40 examples).
  • With a stub model that draws positive rent for every household, each renter head holds its own household's draw under any person order, and everyone else holds zero (Hypothesis, 40 examples). Under the same stub, with_us_housing_inputs passes the gate (Hypothesis, 40 examples).
  • For any rent vector over {0, 1, 12,000}, the gate's count matches a brute-force count, and the gate reports the renter failure line if and only if it exceeds zero (Hypothesis, 60 examples).
  • With rent on one person in an owner household, the gate fails with positive rent outside renter households: 1. With rent written in person order on a reversed person table, it fails with 4, and with_us_housing_inputs draws that rent again onto households 1 to 4.
  • With the stub model, owner and no-cash-rent heads get zero rent, renter heads keep draws of 1,000 to 4,000, and the gate passes.

Removing the gate's new failure line fails four of these tests, and removing the zeroing line fails three others.

Locally test_us_housing_inputs.py, test_spec_engine_loader.py and test_spec_engine_coverage_tool.py pass (40 tests), and ruff check ., both repinner --check modes and tools/spec_engine_coverage.py --check exit 0. test_us_multispine_pool_tool.py needs policyengine_us, so the engine-us CI job runs it.

Re-pin

The change moves attested kernel source, so tools/repin_us_spec_engine_contracts.py rewrote seed_map, seed_protocol, two spec_sha256 pins and docs/evidence/spec-engine/us-f0-coverage.json. The bundle generator needs the US engine and did not run locally; the engine-us CI job runs the bundle tests. One repinner pass leaves covered_item_count at 40 in test_spec_engine_coverage_tool.py, and a second pass restores 41 (#1530).

axiom: n/a: build-pipeline gate, no policy rule changes

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits November 5, 2026 21:39
us_housing_inputs_signal_gate counted positive pre_subsidy_rent outside
renter households (positive_rent_nonrenter) but passed regardless. A base
built before #1444 carries rent on the wrong heads, and
with_us_housing_inputs returns a frame unchanged when the gate passes, so
such a base kept its misplaced rent. The gate now fails on any such rent:
the c0513db support base (66,623 such people) fails and the base rebuilt
at 9434c50 (none) passes.

Tests: the old test that allowed the rent now expects the failure; a replay
of the pre-#1444 placement fails the gate and gets its rent drawn again;
property tests check the count against a brute-force count, flag exactly
the draws person order moves off renters, and pass correctly drawn rent
under any person order.

Re-pinned with tools/repin_us_spec_engine_contracts.py: the seed map and
seed protocol digests, the spec_sha256 vectors and us-f0-coverage.json.

Part of #1446 (step 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gate fails positive rent on anyone in an owner or no-cash-rent household
and passes rent moved between two renter households' heads. The docstrings now
say so. They move the attested source hashes, so the repinner rewrote
seed_map, seed_protocol, two spec_sha256 pins and the coverage report.

Part of #1446 (step 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared QRF's zero-or-positive classifier gives every head a small
chance of positive rent, owners included. With the housing gate now
failing rent outside renter households, a leaked draw would stop a
support-base rebuild. On the Route A pre-clone frame (168,852 heads,
53,055 renter heads), seeds 1 to 7 each put rent on one to four owner or
no-cash-rent heads, and seed 0 on none.

impute_us_pre_subsidy_rent still draws for every head, so renters' draws
stay unchanged, and then sets rent to zero outside renter households.
For seeds 0 to 7 on that frame, renter draws match the old ones exactly,
every other head gets zero, and the gate passes.

The kernel source moved, so the spec-engine pins move again (two
repinner passes, as in #1530).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title Fail the US housing gate on rent outside renter households Fail the US housing gate on rent outside renter households, and zero it in the CPS draw Nov 6, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review November 6, 2026 04:19
@MaxGhenis
MaxGhenis merged commit 3f7cfc1 into main Nov 6, 2026
11 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged at head bad73e4 with a merge commit (3f7cfc1). GPT-6.1 Sol reviewed that head through Subfleet (job 20261105-225819-pr1529-rereview) and approved it; an earlier Opus review at fdc785a requested the zeroing this head adds. Every check passed on the latest run, and no one requested changes. Sol's sandbox could not run pytest, so the local test and mutation counts in the body come from this session's runs. #1446 step 3 stays open for rent on ACS owners, and #1530 tracks the repinner's second pass.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis deleted the us-housing-gate-nonrenter-rent branch November 6, 2026 10:56
MaxGhenis added a commit that referenced this pull request Nov 6, 2026
Populace US means to differ from Census where it fixes CPS errors, so the note
now measures differences from Table 18 and no longer treats them as misses:

- The title and opening say so, and name the question: separating intended
  corrections from noise and bugs. "Systematic error" becomes a systematic
  shift, and "state-specific error" becomes state-specific variation.
- "What would fix it" becomes "What we would change", and says a smaller gap
  alone does not make a better file.
- The rent sections now record what landed. #1444 fixed the assignment, #1529
  made the gate fail on rent outside renter households (the test the note named
  no longer exists), and no published file carries the fix yet. PolicyEngine/child-poverty-impact-dashboard#127
  has left draft.

No figure changes: a token comparison of the old and new text finds only
punctuation differences. ACS-local figures stay on hold for the fill-0 rerun.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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