Summary
Several build-identity keys omit inputs that change results, and the code already works around the gaps by hand. This is the input-side complement to #326 (which is specifically the missing solver_config_sha256 — not duplicated here) and #314 (crosswalk pinning, since fixed in the checkpoint key).
The gaps (each verified on main, 8264c49)
1. The target-frame checkpoint hashes the raw H5, not the staged frame it actually materializes from.
_target_frame_checkpoint_identity (tools/build_us_fiscal_refresh_release.py:1291-1315) keys on: base H5 SHA, pe-us version, seed, period, registry version, crosswalk SHA. But materialization runs on base_frame after immigration, take-up, hours, SNAP, eligibility, pregnancy, SCF wealth, ACA, and Medicaid stages have rewritten it. The code acknowledges this hole explicitly — tools/build_us_fiscal_refresh_release.py:161-167:
# 2: the medicaid_take_up stage (populace #331) changed base_frame's
# takes_up_medicaid_if_eligible before target-frame materialization, ...
# the checkpoint identity hashes the on-disk base dataset, not the staged frame,
# and would otherwise silently reuse pre-stage frames.
TARGET_FRAME_CHECKPOINT_MATERIALIZER_VERSION = 2
Every future stage edit must remember to bump this integer by hand; forgetting reuses a stale checkpoint silently.
2. The reform-vector cache excludes seed while the vectors are computed on a seed-dependent frame.
REFORM_VECTOR_CACHE_CONTEXT_KEYS (:155-160) is only {base sha, pe-us version, period, crosswalk}; commit/seed/registry are intentionally excluded (:1184-1187) so calibration-only reruns reuse the cache. But the per-household reform vectors are evaluated on the post-source-stage frame (:6124), and the take-up / SCF-wealth / pregnancy stages draw seeded Bernoullis — so two runs with the same base and row count but different --seed share cache entries computed under the other seed's frame, pairing stale reform vectors with a freshly computed baseline. The materializer-version constant guards the target-frame checkpoint but there is no equivalent key on the reform cache (only TARGET_MATERIALIZATION_CACHE_SCHEMA_VERSION).
3. The constraint matrix is not fingerprinted, and incumbent comparison keys on a hash that excludes it.
_target_surface_payload (packages/populace-calibrate/src/populace/calibrate/diagnostics.py:130-149) hashes target rows (names/periods/measure selectors/values); the matrix contributes only rows/columns/nnz. _assert_incumbent_target_surface_matches (:1822-1850) accepts incumbent diagnostics on target_surface.sha256 equality alone — an engine change that moves coefficients (A) while preserving the target rows (b) is treated as the same surface.
4. CLI-overridable donor inputs skip identity and provenance.
The default SCF extract is SHA-pinned in scf_wealth.py, but a --scf-summary-extract path is loaded directly (:5950-5961); its digest enters neither the checkpoint identity nor the build/release manifests. Same family: _download_base_h5 (:1596-1604) fetches the mutable root with no revision (the observed SHA is recorded after the fact, so the identity is sound, but the input is not re-fetchable at that identity), and the Ledger --ledger-facts-sha256 / --ledger-manifest-sha256 pins are optional with bare-JSONL feeds accepted (:593-617).
5. The exact selected support set is not propagated into release lineage.
SelectionReport.as_manifest() (packages/populace-build/src/populace/build/us_runtime/warm_start_selection.py:124-147) drops identities_sha256 (the selection-source manifest embeds and verifies it on load, but the release manifest's selection_source payload carries only mode/join key/counts/source provenance). The US country package (us/country_package.json) commits no selection resource. A release therefore does not record which records were selected in a directly comparable form.
Fix sketch
One canonical build-run identity (the umbrella #326 also feeds into):
- add a canonical post-source-stage frame hash and derive both the target-frame checkpoint key and the reform-vector cache key from it (this retires the manual materializer integer and closes the seed hole at once);
- include CSR
indptr/indices/data (or a content hash of them) plus stable record IDs in target_surface, and require engine identity in incumbent comparisons;
- record CLI-supplied donor/extract SHAs in the build manifest and require them in the checkpoint identity;
- carry
identities_sha256 through as_manifest() into the release manifest.
Acceptance criteria
Cross-links
Surfaced by an external architecture review (Codex/gpt-5.6-sol, 2026-07-09); citations independently verified.
Summary
Several build-identity keys omit inputs that change results, and the code already works around the gaps by hand. This is the input-side complement to #326 (which is specifically the missing
solver_config_sha256— not duplicated here) and #314 (crosswalk pinning, since fixed in the checkpoint key).The gaps (each verified on main, 8264c49)
1. The target-frame checkpoint hashes the raw H5, not the staged frame it actually materializes from.
_target_frame_checkpoint_identity(tools/build_us_fiscal_refresh_release.py:1291-1315) keys on: base H5 SHA, pe-us version, seed, period, registry version, crosswalk SHA. But materialization runs onbase_frameafter immigration, take-up, hours, SNAP, eligibility, pregnancy, SCF wealth, ACA, and Medicaid stages have rewritten it. The code acknowledges this hole explicitly —tools/build_us_fiscal_refresh_release.py:161-167:Every future stage edit must remember to bump this integer by hand; forgetting reuses a stale checkpoint silently.
2. The reform-vector cache excludes
seedwhile the vectors are computed on a seed-dependent frame.REFORM_VECTOR_CACHE_CONTEXT_KEYS(:155-160) is only {base sha, pe-us version, period, crosswalk}; commit/seed/registry are intentionally excluded (:1184-1187) so calibration-only reruns reuse the cache. But the per-household reform vectors are evaluated on the post-source-stage frame (:6124), and the take-up / SCF-wealth / pregnancy stages draw seeded Bernoullis — so two runs with the same base and row count but different--seedshare cache entries computed under the other seed's frame, pairing stale reform vectors with a freshly computed baseline. The materializer-version constant guards the target-frame checkpoint but there is no equivalent key on the reform cache (onlyTARGET_MATERIALIZATION_CACHE_SCHEMA_VERSION).3. The constraint matrix is not fingerprinted, and incumbent comparison keys on a hash that excludes it.
_target_surface_payload(packages/populace-calibrate/src/populace/calibrate/diagnostics.py:130-149) hashes target rows (names/periods/measure selectors/values); the matrix contributes onlyrows/columns/nnz._assert_incumbent_target_surface_matches(:1822-1850) accepts incumbent diagnostics ontarget_surface.sha256equality alone — an engine change that moves coefficients (A) while preserving the target rows (b) is treated as the same surface.4. CLI-overridable donor inputs skip identity and provenance.
The default SCF extract is SHA-pinned in
scf_wealth.py, but a--scf-summary-extractpath is loaded directly (:5950-5961); its digest enters neither the checkpoint identity nor the build/release manifests. Same family:_download_base_h5(:1596-1604) fetches the mutable root with no revision (the observed SHA is recorded after the fact, so the identity is sound, but the input is not re-fetchable at that identity), and the Ledger--ledger-facts-sha256/--ledger-manifest-sha256pins are optional with bare-JSONL feeds accepted (:593-617).5. The exact selected support set is not propagated into release lineage.
SelectionReport.as_manifest()(packages/populace-build/src/populace/build/us_runtime/warm_start_selection.py:124-147) dropsidentities_sha256(the selection-source manifest embeds and verifies it on load, but the release manifest'sselection_sourcepayload carries only mode/join key/counts/source provenance). The US country package (us/country_package.json) commits no selection resource. A release therefore does not record which records were selected in a directly comparable form.Fix sketch
One canonical build-run identity (the umbrella #326 also feeds into):
indptr/indices/data(or a content hash of them) plus stable record IDs intarget_surface, and require engine identity in incumbent comparisons;identities_sha256throughas_manifest()into the release manifest.Acceptance criteria
--seedcannot share reform-vector cache entries.target_surfacedistinguishes two calibrations whose targets match but whose constraint coefficients differ.identities_sha256when a selection source is used.Cross-links
Surfaced by an external architecture review (Codex/gpt-5.6-sol, 2026-07-09); citations independently verified.