Consume pinned Ledger artifacts and enforce the period contract - #287
Merged
Conversation
This was referenced Jul 2, 2026
Loads Ledger consumer artifacts (manifest.json + consumer_facts.jsonl) with hash verification and optional build-config pins, and records the artifact identity in both build and release manifests, so a release's target values are reproducible from a named Ledger artifact (#160, #271). Bare consumer-facts files still load, content-addressed by their own hash. The period contract (PolicyEngine/chronicle#71) is now enforced at compile time: an ageable observation dollar level whose fact period differs from the build period raises PeriodContractError instead of silently calibrating at the wrong period — the populace#212 failure mode. Builds either age the value under the named cbo_growth_factor_aging model (now the release-tool default, with --no-age-targets to disable), or pass an explicit allow_unaged_dollar_targets waiver that stamps period_contract_waiver metadata on every affected target. Aged targets carry alignment_model_id/alignment_model_version alongside the existing factor lineage, publisher projections consumed at their published level are exempt by assertion, and CBO rows typed as observations are refused as growth factors. Consumer rows' assertion and fact period now flow into ledger_* target metadata and reference selectors. Refs #116, #160, #212, #271; PolicyEngine/chronicle#71, PolicyEngine/chronicle#73. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Existing tests compile 2024/2025 surfaces from TY2022/23 fixture facts un-aged — exactly the state the contract now refuses silently. Each compile call declares the explicit allow_unaged_dollar_targets waiver so the tested behavior is unchanged and the waived state is visible, and the release-builder test stubs the artifact loader instead of the retired bare-JSONL helper. A new compile-level test asserts the unwaived path raises. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The artifact loader validated row assertions by writing the observation default onto rows that omit the field. Measured against a real bundle feed, that broke aging: pre-#73 CBO projection rows arrive unlabeled, the stamp typed them as observations, and the growth-factor filter then refused them — zero targets aged. Rows now pass through exactly as published; an assertion is validated when present and never fabricated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured against a real three-year Ledger bundle feed, v1.0.0 aged only the 28 national TY2023 dollar targets: the CBO February 2026 projection detail starts at TY2023, so all 1,480 TY2022 state rows had no denominator and fell to period-contract waivers — an incoherent joint surface (national anchors at 2024 levels, states at 2022). cbo_growth_factor_aging v1.1.0 bridges years the projection detail does not cover with the same series' national SOI actuals: factor = soi(pivot)/soi(source) x cbo(build)/cbo(pivot), observed growth for observed years and projected growth only where nothing is observed. Chain sources are an explicit map (AGI via Table 1.1, wages and net capital gain via Table 1.4); everything else keeps the AGI default, now also chainable. Chained factors record both bridge facts in their lineage. On the same feed the full surface now ages: 1,480 targets, zero waivers, aged-dollar total $184.0T -> $202.7T (+10.2%); the TY2022 US AGI row lands at $16.56T against $16.62T from the TY2023 national path — independent vintages converging on the same build-year level. Refs #116, #212; PolicyEngine/chronicle#71. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reviewer found two sibling scorer tools this branch broke: score_us_fiscal_targets.py and score_us_state_files.py imported the retired _load_ledger_facts and compiled without contract flags, so both crashed at import and would then have raised PeriodContractError on any real feed. They now load through the pinned artifact loader and expose --age-targets / --allow-unaged-dollar-targets mirroring the release builder, defaulting to the incumbent un-aged-waived surface so existing releases score against the surface they were calibrated to. Also from review: publisher-projection-backed dollar targets are never re-aged (aging_factor_source=source_projection_level, mirroring the period-contract exemption — re-projecting a published projection would silently compound models); ledger_assertion metadata is recorded only when the fact asserts it, matching the loader's no-stamp rule; the CBO and SOI series indexers fail loudly on conflicting facts for the same (series, year) instead of last-write-wins; and one misindented test kwarg. Branch rebased onto current main (#289 et al.). The review harness behind the PR's measured-implications section is checked in as tools/diff_us_target_surface_aging.py so reviewers can reproduce it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
ledger-artifact-period-contract
branch
from
July 2, 2026 22:13
b3988c5 to
eb08c43
Compare
The re-review found a third importer of the removed _load_ledger_facts: audit_us_ctc_line19_proxies.py failed at import. An exhaustive sweep then found build_us_puf_support_base.py carrying its own private copy of the same bare loader. Both now load through the pinned artifact loader, so no unverified feed path remains anywhere in tools/. Also covers the SOI-chain conflict guard with its own regression test (the CBO guard already had one). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 3, 2026
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.
Summary
The populace side of the Ledger facts-only transition (PolicyEngine/chronicle#71, shipped in PolicyEngine/chronicle#73). Builds now consume pinned, hash-verified Ledger artifacts, and consuming an observation dollar level at a period other than its fact period is a hard error instead of a silent default.
Pinned artifact consumption (#160, #271). New
populace.build.ledger_artifact(duck-typed, stdlib-only, like the rest of our Ledger consumption):load_ledger_consumer_artifactloads a Ledger consumer-artifact directory (manifest.json+consumer_facts.jsonl), verifies the fact file against the manifest hash, honors optional--ledger-facts-sha256/--ledger-manifest-sha256build-config pins, validates row assertions, and exposes aprovenance()block that the release tool now records in bothbuild_manifest.jsonandrelease_manifest.json— a release's target values are reproducible from a named Ledger artifact. Bare consumer-facts files still load (content-addressed by their own hash) so nothing breaks today.Period-contract enforcement (the populace#212 guard).
compile_us_fiscal_target_registrynow always ends withenforce_period_contract: an ageable observation dollar target (USD sum, not already period-aligned within-surface) whose fact period differs from the build period raisesPeriodContractErrornaming every violation — unless aging transformed it or the build passesallow_unaged_dollar_targets=True, which stampsperiod_contract_waivermetadata on each affected target so the waived state is auditable in diagnostics. Publisher projections consumed at their published level (JCT/CBO-backed targets) are exempt byassertion; that's projection-basis by construction, not un-aged consumption.Aging is the named, versioned alignment model.
AGING_MODEL_ID = "cbo_growth_factor_aging"/AGING_MODEL_VERSION = "1.0.0"; every aged target recordsalignment_model_id/alignment_model_versionalongside the existingbasis/aging_factor/factor-lineage metadata — the declaration shape Ledger records but never executes. Bumping the model version changes no Ledger fact and no artifact hash, only build outputs. The release tool ages by default now (--no-age-targetsto disable, in which case the contract requires the explicit waiver flag); #116's SOI dollar targets are the first user.Assertion-aware consumption (ledger#73). Consumer rows'
assertionand fact period flow intoledger_assertion/ledger_fact_periodtarget metadata and reference selectors ({"assertion": "source_projection"}now selects). CBO rows that look like projections structurally but are typedobservationare refused as growth factors — inconsistent data must not supply ratios.Behavior changes to review:
compile_us_fiscal_target_registryraises on silently un-aged cross-period observation dollars (waiver available and auditable).tools/build_us_fiscal_refresh_release.pydefaults to--age-targets(was off).--ledger-factsaccepts a directory or a file.Existing tests that compile TY2022/23 fixture surfaces at 2024/2025 un-aged now declare the explicit waiver (second commit) — the tested behavior is unchanged, the waived state visible. A new compile-level test asserts the unwaived path raises.
Test plan
uv run ruff check .anduv run pytest -q(full workspace) both pass locally.test_ledger_artifact.py(hash verification, tamper detection, pins, assertion validation, provenance); period-contract unit + compile-level tests; alignment-declaration metadata; assertion selector/metadata; refused mistyped growth factors.Refs #116, #160, #212, #271; PolicyEngine/chronicle#71, PolicyEngine/chronicle#73.
🤖 Generated with Claude Code
Measured implications (real feed)
Measured on a real Ledger bundle feed (105,216 consumer-fact rows built from fetched 2022/2023/2024 publisher artifacts,
facts_sha256=f6fa6152…), compiling the identical US surface at the 2024 build period, un-aged vs the new default:The measurement also drove two fixes now in this PR / ledger: model v1.1.0 chaining (v1.0.0 aged only 28 national rows because the CBO detail starts at TY2023 — all 1,480 state TY2022 rows would have needed waivers, an incoherent joint surface), and the loader no longer stamps default assertions onto legacy rows (the stamp made unlabeled CBO projections look like mistyped observations and zeroed out factor eligibility). PolicyEngine/chronicle#76 types the CBO projection record sets at the source.