Skip to content

Cite US and UK spec sources by symbol or at a commit, never by a drifting line - #1146

Open
MaxGhenis wants to merge 7 commits into
mainfrom
spec-symbol-citations-us-uk
Open

MaxGhenis wants to merge 7 commits into
mainfrom
spec-symbol-citations-us-uk

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Invariants

The guard is test_spec_source_citations.py, with validators in test_support/microcosm_build/spec_source_citations.py. It scans every resource in every country spec package, meaning every build/<country>/ directory with a country_package.json (today am, be, nz, uk, us). JSON and YAML resources are parsed, and every string is scanned, mapping keys included. For those resources:

  1. No line citation of a file in this repository, pinned or not (in_repository_line_citations). A cited path counts as in-repository when it has two or more components and is the tail of a source file in this checkout, under packages/, tools/, experiments/, test_support/ or docs/. So tools/x.py, packages/microcosm-build/src/…, microcosm/graph/executor.py and a partial us_runtime/asec_pool.py all count. These sources are cited by symbol instead.
  2. No line citation of any other source unless it is pinned at a commit (unpinned_line_citations). A citation is pinned when one of these holds:
    • its own string names a commit: commit <sha>, at <sha>, @<sha> or a GitHub /blob/<sha>/ link, where <sha> is 7-40 lowercase hex digits with at least one digit and one letter, not running on into a file name;
    • the resource holds a path/commit mapping for that file, at any depth, whose commit has the same sha form. As in Cite NZ spec sources by symbol, not line number #1141, pins match by file name across the whole resource, so one pin of cps.py covers every cps.py citation in that resource, whichever repository it means.
  3. Every dotted microcosm.<shard>.* name in a resource resolves through import plus getattr. Only names whose second component is a shard in this checkout count (build, calibrate, data, diagnostics, fit, frame, graph), so a format id such as Build benefit units from concept pointers and execute group encoding (NZ v0, G4) #1122's microcosm.axiom_input_closure.v1 is not read as a symbol.

Scope of each claim:

  • Rules 1 and 2 cover only the locator spellings the module documents and only text-source suffixes (py, yaml, json, toml, sh, md, csv, rs, and the like). The spellings are x.py:12, x.py:12-19, x.py:12,19, #L12, L12, , L12, (L12), line 12, , lines 12-19, : lines 12-19 and (lines 12-19), plus the reverse lines 12-19 of x.py and line 12 in x.py. The file name may be quoted or backticked. A symbol between the file and its lines (x.py some_function, lines 12-19) is not recognised.
  • A bare file name such as concepts.py cannot be resolved, so only rule 2 covers it.
  • An inline commit pins every citation in its string, whichever repository the commit belongs to.
  • The guard does not check that the cited lines say what the prose claims. For this PR that was checked by reading: every changed citation was read at its commit, and all 251 commit-pinned citations in the US and UK packages were checked mechanically (the file exists at that commit and the range is within the file).

Each rule has Hypothesis properties, at least one per refusal branch:

  • an unpinned citation is refused in any spelling, at any depth, as a value or as a key;
  • a path/commit pin covers a citation, and so does an inline commit in any spelling, before or after the citation;
  • a commit named in another string does not pin a citation;
  • a decimal, an all-letter word, a ≤6-hex prefix, a 41-64-hex digest or a branch name names no commit;
  • a pin of another file, or a pin at a branch, does not cover a citation;
  • symbol citations are not line citations;
  • an in-repository path is refused even when pinned inline or by path/commit.

Why

The US and UK packages cited sources by line numbers that drift. The in-repository ones drifted under ordinary edits, and the external ones had no commit, so they drifted with the other repository's main. #1141 fixed this for NZ and added a guard for the NZ package only. Before this PR the guard flagged US 42 unpinned + 21 in-repository citations and UK 53 unpinned (NZ: 5, which #1141 fixes).

Changes

US, ecps_parity_known_gaps.json (7 hermetic_build_contract strings and is_unmarried_partner_of_household_head.evidence.semantic_non_substitutes.derived_A_EXPRRP):

Was Now
asec_pool.py lines 61-75 microcosm.build.us_runtime.asec_pool.load_asec_h5_tables (plus _prepare_year_input where the restore matters)
asec_pool.py lines 397-422 microcosm.build.us_runtime.asec_pool._with_relationship_recode
tools/build_us_puf_support_base.py lines 85-88, 647-687, 659-699, 748-755 _parse_args (--asec-h5), _pooled_asec_sources_from_args, _asec_sources_from_args. Lines 748-755 were _load_frame, the --base-h5 loader, which never touches --asec-h5, so that citation is dropped.
buildj_base.sh lines 65-69 / 65-81 / 70-81 and 103-108 the script's --asec-h5 2024=, 2023=, and 2022= (and --puf-h5) arguments. Three of these ranges were already stale when written.
base_j.summary.json lines 55-75 / 317-318 base_source.sources[].sha256, puf_sha256, and base_source.metadata.sources[].share for the equal shares. Lines 55-75 never held the shares.

#720 (39b8e7b, 2026-09-23) made several of these sentences false as written on main, for example "reads the prebuilt person tables without refreshing omitted Census columns". The pool now restores 11 reviewed Census person columns. Each sentence now says which columns the restore leaves out (NOW_OWNGRP/HIPAID/GRPFTYP, FIN_VAL/FIN_YN/I_FINVAL, SRVS_VAL, PERRP/PECOHAB/A_FAMREL). Differential tests check those columns against ASEC_CENSUS_PERSON_COLUMN_NAMES. They also check that the restore runs before the relationship fallback, that the fallback never emits code 13 (a Hypothesis property over synthetic households), and that the tool defines the functions the evidence names. No exclusion decision or reason changes.

US, source_stages.json → regenerated spec/sources.yaml:

  • The four notes that said "archived cps.py lines …" now name archived commit 42ed5d45c56df80d754fbe24cce21cfeb8d05cbe and package-relative paths (datasets/cps/cps.py, parameters/take_up/wic_takeup.yaml, utils/randomness.py, datasets/cps/extended_cps.py).
  • target_parity_manifest.json and its generator cite utils/loss.py:67-70 at 42ed5d45…. The quoted comment is at 67-70 there. At PR UK was_lisa: Lifetime ISA holdings from the WAS round-8 person tab (#1003) #1059's own squash commit it sits at 62-65.

UK:

  • uk_data_target_parity.json and its generator uk_runtime/data_target_parity.py: all 13 uk-data citations name the resource's incumbent_commit, 8629dbbfe82727278d47ee5ff09fd1da4acefa38. Each cited blob's sha256 matches uk_data_target_inventory.json.
  • uk_population_targets.json: 21 strings name registry_parity.pinned_ref, 12a1e028afeef08d8b2d74ee03fd9de3a78b2dd3, with targets/-qualified paths.
  • Differential tests assert that every commit those two resources name is the resource's own declared ref.

Wrong pointers corrected. Each cited range was read at its commit, and these six pointed at other code:

Resource Was Read at the commit Now
us ssi_take_up notes "cps.py line 584 maps … SSI_VAL …, lines 1497-1499 export it" 584 loads the SSI rate; 1497-1499 map SSI_VAL to the temporary ssi_reported anchor; the save is 759-766 labels fixed, 759-766 added
us scf_wealth notes "source_impute.py lines 1146-1164 fits that anchor with these eight predictors" 1146-1164 only add the anchor to the QRF outputs fit 1227-1240, SCF_PREDICTORS 168-177
uk parity hmrc_cgt hmrc_cgt.py:195 blank line, at every commit 196 (def _band_targets)
uk parity DfE education datasets/imputations/frs_only.py no education code at any commit services/services.py:137-148
uk population land (3 sectors) sources/_land.py:52-73 the file has 65 lines at every commit 48-52, 54-59, 60-65 per sector
uk population salary sacrifice (8 strings) "silently suppressed by the bare-except drop in build_loss_matrix.py:376-394" 376-394 is the dispatch block, with no except the except Exception in targets/sources/hmrc_salary_sacrifice.py lines 34-109, which logs the error and returns only the contributions target

Smaller fixes:

  • prior_year_income gains the predictor-definition range extended_cps.py 234-248, matching its sibling stage.
  • The TV-licence parity citation widens from obr.py:617-728 to 570-728 to include _parse_tv_licence.

Tests whose literal assertions moved. Four exclusion tests asserted "buildj_base.sh lines 65-69". They now assert the content anchors and check that base_j.summary.json holds three 64-hex base_source.sources[].sha256.

Re-pins

Every byte edit to source_stages.json moves these (afaffae and 808ee8f moved parts of the same set):

Pin Old New
source_stages.json raw sha256 (sources.yaml stage_asset, generator FROZEN_LEGACY_RESOURCE_SHA256, us_spec_bundle.py, test_us_bundle_core_contracts.py) e75217c0… 41e8194a…
US spec_sha256 (us-f0-coverage.json ×2, test_us_multispine_pool_tool.py) f76f921d… (main after 6c27a68) cf79472d…
US documentation_sha256 (us-f0-coverage.json) 0931cbd3… b3d98ce9…
EXPECTED_HASHES.source_manifest f4f8bfeb… 4e0d2a60…
EXPECTED_HASHES.late_resource_semantics cc34ba11… 5d84124a…
EXPECTED_HASHES.full_checkpoint 3db095ce… 9a86a977…
us-f0-coverage.json pool-code observed sha 61a5484e… 934b244e…

How they were produced:

  • sources.yaml was regenerated with tools/generate_us_bundle_from_constants.py. --check reported only sources.yaml stale, and after merging main --check passes and prints spec_sha256=cf79472d….
  • us-f0-coverage.json was regenerated with tools/spec_engine_coverage.py: 41/41 inventory checks, and exactly the 10 predicted leaves moved.
  • A grep for every superseded hash, and for its 8-character prefix, finds nothing.
  • The seed protocol, the loader golden, the engine ABI lock and the UK spec and documentation hashes do not move.
  • The UK resources are legacy_json, so only the unpinned UK package fingerprints move.

Operational cost: the stacked US pool checkpoint identity binds the late producer resource semantics, which hash source_stages.json's bytes. Existing stacked US pool stage checkpoints will therefore be treated as stale and rebuilt on the next build, as after any source-stage note edit. No behaviour or data changes.

How this was verified

  • A workflow of 15 agents ran the verification. Proposers read every unpinned or in-repository citation at the commit it was written against: they dated the text with git log -S and read the lines in us-data, uk-data or microcosm. Separate adversarial verifiers re-derived each proposal; all 49 proposed edits were confirmed, and one audit finding was refuted and dropped. That finding claimed the ESI "cannot execute" wording was wrong, but _validate_raw_cps_schema raises first.
  • Auditors read all 166 already-pinned citations, which surfaced the six wrong pointers above.
  • I spot-checked every UK correction and the in-repository claims against source myself.
  • I checked all 251 commit-pinned US/UK citations mechanically against local clones of policyengine-us-data and policyengine-uk-data: each file exists at its commit and each range is within the file.

Tests

Only targeted files, each run with -p no:cacheprovider --basetemp=.hub-scratch/pytest -o tmp_path_retention_policy=failed (no full suite):

  • Engine-free: test_spec_source_citations, test_us_bundle_core_contracts, test_us_spec_bundle, test_country_spec, test_spec_engine_{country_bundles,reemission,inventory_coverage,loader}, test_us_{scf_wealth,plan,relationship_inputs,parity_reference,other_health_insurance,financial_assistance_exclusion,survivor_benefits_exclusion,wic_nutritional_risk_exclusion,dc_ptc_take_up_exclusion,early_head_start_exclusion,medicare_take_up,asec_sources}, test_release_{input_coverage,target_parity}, test_uk_{data_target_parity,incumbent_surface_evaluation,population_targets,cgt_targets,bus_targets,benefit_target_scope,target_references}, test_nz_spec_package. Before the main merge and the NZ fold-in, all passed except this guard's two NZ cases, which Cite NZ spec sources by symbol, not line number #1141 fixes; Cite NZ spec sources by symbol, not line number #1141 is now merged into this branch. One failure this run caught was fixed: test_no_incumbent_data_package_references_in_live_tree (the new test named the incumbent package; it now uses neutral paths).
  • US engine: engine_contract/us test_us_spec_bundle (generator byte reproduction), test_spec_engine_inventory_coverage, test_spec_engine_coverage_tool, test_spec_engine_engine_abi, test_release_input_coverage; engine_scenario/us test_us_relationship_inputs; engine_workflow/us test_us_multispine_pool_tool. Not run locally: free disk fell below the 40 GB floor (to 33-38 GB from other activity), so I stopped local runs; CI's engine-us job runs them. The two tools these tests compare against were run on the merged tree: generator --check passes, and spec_engine_coverage.py reports 41/41.
  • ruff check . and ruff format --check are clean.

Depends on #1141

The guard's NZ cases pass only with #1141's NZ fixes, so this branch merges #1141's head (fba45621). Its five files show in this diff until #1141 merges. Do not merge this before #1141. This PR also moves #1141's NZ validators into the shared helper: test_nz_spec_package.py keeps its NZ-specific checks and imports the rest. #1142 rewrites the same NZ citations, and the NZ hub agrees #1141 lands instead.

Review

Round 1 was an independent Opus 5.5 review, run read-only with no shell (~/reviews/microcosm-1146/review-r1.md). It approved with six non-blocking findings. 0a841e4 addresses them:

  1. The locator spellings it found missing are now recognised (backticked or quoted names, : lines, , L12, (L12)). The symbol-between form is documented as out of scope.
  2. Partial in-repository paths are now refused even when pinned.
  3. The path/commit pin needs the inline sha form.
  4. The unmarried-partner contract now says Build J predates Pooled ASEC 2022/2023 vintages lack the NOW_* coverage recodes: reported Medicaid at interview thins to 24.6M vs ~58M survey under 65 on the certified artifact #720, and a test checks the summary's per-year recode source.
  5. The gap's reason is left to the follow-up below.
  6. This body's string count is corrected.

Follow-up (not in this PR)

Since #720, Census A_EXPRRP (whose reviewed domain includes code 13) is restored for the 2022 and 2023 inputs. So the is_unmarried_partner_of_household_head gap's reason and semantic_non_substitutes.A_EXPRRP read as stale. Whether the exclusion stands is a methodology call, so it is split into its own task rather than changed here.

axiom: n/a: spec citation text, test-only validators and re-pins; no policy or rule change.

🤖 Generated with Claude Code

MaxGhenis and others added 7 commits October 7, 2026 15:58
benefit_unit_rule.json cited concepts.py:880-919 and concepts.py lines
892-901 for the relationship pointers. #1120 shifted both ranges by one
line on main, and #1134 (open) adds seven more lines above them: at its
head, lines 892-901 are the partner pointer's text rather than the
"always parent 1" sentence, and 880-919 stops before reference_person_id.
The file also cited executor.py by line, and scenarios.json cited the
harness as "ops main run.py:168", which moves with ops main.

Cite the Relationships block, _pointer("parent_1_person_id"), the
executor functions, and WEEKS_IN_MODEL_YEAR at ops ba6e7d6e instead.
Add unpinned_python_line_citations: no NZ spec resource may cite a line
of a Python source unless it pins that source at a commit (as
as_rate_bridge.json pins the harness), with Hypothesis cases per refusal
branch, plus checks that the cited pointers match the concepts source
and that every dotted microcosm symbol the package names resolves.
Re-pin the two resource hashes and the NZ spec fingerprint in the golden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng line

Port PR #1141's NZ citation guard to every country spec package as a shared
validator (test_support/microcosm_build/spec_source_citations.py) and an
engine-free test (test_spec_source_citations.py). The guard refuses a line
citation of a file in this repository, pinned or not, and a line citation of
any other source unless its string names the commit or the resource pins the
file in a path/commit mapping.

US: ecps_parity_known_gaps.json now cites asec_pool symbols, the PUF support
tool's functions, the build script's --asec-h5 arguments and the Build J
summary's keys; where #720's Census person-column restore had made a
sentence false, it now names the columns the restore leaves out. The
remaining us-data citations in source_stages.json and
target_parity_manifest.json name archived commit 42ed5d45.

UK: uk_data_target_parity.json (and its generator) names incumbent commit
8629dbbf; uk_population_targets.json names registry ref 12a1e028.

Reading every cited range at its commit corrected six wrong pointers (SSI
take-up, SCF net worth fit, hmrc_cgt.py 195, frs_only.py, _land.py 52-73,
the salary-sacrifice drop). Re-pins the source_stages.json hash, the
regenerated sources.yaml, the US spec and documentation hashes, three
inventory EXPECTED_HASHES and us-f0-coverage.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's 6c27a68 re-cut the US spec digest (d1df6b31 -> f76f921d) and the
seed hashes. On the merged tree the US spec_sha256 is cf79472d (generator
--check agrees), the three inventory digests this branch moves are
unchanged, and us-f0-coverage.json is regenerated (41/41).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_nz_spec_package.py now imports nested_strings, line_citations,
commit_pinned_sources and unpinned_line_citations from
test_support/microcosm_build/spec_source_citations.py and keeps only its
NZ-specific checks: the harness pin, the five replaced citations as
regression vectors, and the relationship-pointer differential. The
package-wide scan, the Hypothesis properties and the dotted-symbol
resolution now run once for every country in test_spec_source_citations.py,
which also asserts that the NZ and US dotted symbol citations are scanned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1122 adds an NZ resource whose format id is microcosm.axiom_input_closure.v1,
which the dotted-symbol scan read as a Python symbol and failed to import.
A dotted name now counts as a symbol citation only when its second component
is a shard in this checkout (build, calibrate, data, diagnostics, fit, frame,
graph). Every dotted name the packages carry today is under a shard, so the
scanned sets are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Recognise a quoted or backticked file name and the `x.py: lines N`,
  `x.py, LN` and `x.py (LN)` locators; the Hypothesis spellings cover them.
- A cited path of two or more components is this repository's when it is
  the tail of a source file here (`us_runtime/asec_pool.py`), not only when
  it resolves from the root, so a partial in-repo path pinned inline is
  still refused.
- A path/commit mapping now needs the same sha form as an inline commit
  (7-40 lowercase hex with a digit and a letter); a decimal, a word or an
  upper-case value pins nothing.
- The unmarried-partner build contract says Build J predates #720, and a
  test checks the summary's per-year relationship_recode_source.

No package resource gains or loses a citation hit under the wider rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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