Repository navigation
Materialize the Census SPM independence role in US builds - #959
Merged
Merged
Conversation
…section The proof derives the role for a population through the certified derivation (derive_spm_role_source, unchanged) from the pinned complete Census ASEC person CSVs, attaches it to the frame the release tool itself loads, and records check_spm_composition before and after, plus the invariants the brief names: no unit re-grouped, no person invented, every offending unit resolved by its own 15-to-17-year-old, support clones agreeing with their source person. The buildp subcommand compares the same derivation against the enrichment lane's pinned reference evidence person for person. The journal's first-session State/Next are historicized in place and a Step 2 section records what step 1 found on arrival (#948 already green). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…uild P byte for byte Phase-2 base (base-q3, 352,932 households / 907,382 persons / 367,306 SPM units), read-only, through the unchanged derive_spm_role_source: before, 222 units without a classified adult (head-only fallback); after, 0. All 238 units with no member aged 18 or over are resolved by their own 15-to-17-year- old carrying the source role (240 such persons); person ids, SPM membership, the spm_unit table and every age are unchanged; zero unmatched persons; zero count-reconciliation mismatches; the 432,523 distinct source persons repeated across the two support channels agree on the role in every case. Build P parent (pinned digest, read-only): the same derivation reproduces the enrichment lane's reference evidence byte for byte (SHA-256 22b5968d…), 166,321 of 166,321 roles equal, every EXPECTED_COUNTS value matched. The proof loader symlinks a suffix-less Hugging Face blob under a temporary directory because the engine's dataset loader insists on .h5. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Answers from code at this head: the rule's columns never enter the pool (no frozen census_cps vintage carries SPM_HEAD; only 2024 carries A_FAMTYP and A_FAMREL), so the stage restores the role from the pinned complete Census person CSVs by exact (source_year, PERIDNUM) join, the same repair class as LKWEEKS/ED_VAL/PAW_TYP, and calls derive_spm_role_source unchanged; how clones inherit it (many_to_one across native units, refused within one; zero disagreements on 432,523 source persons); why ACS-origin rows are refused rather than defaulted; the carve-out in both classification paths, driven by policyengine_us.spm.DATASET_SOURCE_INPUTS; and the moved-pin table with each artifact's generator. Corrects two claims in the earlier note: the zero-adult refusal already exists in _reconcile_units, and the engine does not assert DATASET_SOURCE_INPUTS disjoint from REJECTED_DATASET_INPUTS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A manifest stage shaped like relationship_inputs (read_table person -> derive_spm_independence_role) whose derivation body is a call into the certified derive_spm_role_source, unchanged: the handler projects the frame's identity, age and Census count columns and the spm_unit table into a temporary H5, hashes it, and runs the derivation against the pinned complete Census ASEC person CSVs. Every refusal of that derivation (no ASEC origin, a repeated or mixed source unit, more or fewer than one SPM_HEAD, a unit left without a classified adult, a Census count that does not reconcile) surfaces as a SourceRuntimeError naming the stage; the stage adds its own required- column check first. The idempotence guard, person_id alignment, coverage refusal, summary and signal gate follow the model; the gate additionally runs check_spm_composition on the produced frame and requires zero unresolved units from the source column. Registered in US_DONORS, US_STAGE_NAMES (after relationship_inputs, before puf_support), source_stages.json, the sources schema, the allowed operation kinds and the US handler registry. The pool operator order is deliberately untouched: ACS-origin rows carry no PERIDNUM and the stage refuses to default them (docs/us-spm-role-stage.md §3, §7). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…leaves
policyengine_us/spm.py declares DATASET_SOURCE_INPUTS = {is_spm_independent_
minor_role} and says a producer must retain its observed boolean rather than
treat the fallback formula as ownership. The adapter classified the role by
formula presence alone, in both of its paths, so write_dataset refused the
one input the engine asks a producer to deliver.
Live engine: _engine_dataset_source_inputs reads the declaration off
policyengine_us.spm, requires a non-empty frozenset of names disjoint from
REJECTED_DATASET_INPUTS (the engine itself does not assert that), and
variables(), formula_owned_outputs() and _engine_computed_columns() treat
those names as leaves. Import-free metadata index: the generated audit block
is regenerated by tools/refresh_us_generated_variable_audit.py, which now
reads the same declaration through the same validator, emits formula_owned=
False for a declared source input, refuses a declared name the snapshot does
not cover, and pins spm.py as a policyengine-us activation file so an
upstream change to the declaration fails the audit closed. No name list of
microcosm's own is involved.
Observed at policyengine-us 2.2.1 / spm-calculator 1.0.0: 926 input leaves
(was 925), 5,241 formula-owned (was 5,242), in both paths.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e export POST_REFERENCE_ECPS_REQUIRED_INPUTS gains is_spm_independent_minor_role in the module and the generator, with a note explaining the engine's declaration and why no reviewed exclusion is possible (the anti-rot check fails an exclusion whose column carries signal, and the certified default's does, through the Build P enrichment). Manifest regenerated: 164 required, 7 reviewed exclusions, 41 probes. The L0/refit export's required non-constant person columns gain the same leaf. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…engine contracts The pool's projection receipt requires the default-value surface to equal variables() exactly; default_values still skipped the role by formula presence. With the same declaration-driven rule applied there, tools/repin_us_pool_engine_contracts.py re-derived the five pinned quantities against the installed 2.2.1 engine: 926 inputs and defaults (was 925), the projection and defaults digests, the provision counts (the role is a declared-absent pool input: the pool has no producer for it, see docs/us-spm-role-stage.md §3), the 1,059-row remaining-stage manifest digest, and the regenerated engine ABI lock. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d snapshot The metadata-index pins move to the observed values under the regenerated audit: seven spm-calculator formula-owned outputs, two input leaves, 926 inputs, 5,241 formula-owned. 17 passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…elease tool Base builder: both paths (the byte-compatible monolithic _run_all and the checkpointed pre_clone_enrichment stage) run with_us_spm_independence_role immediately after relationship_inputs, on the ASEC-only frame before cloning, with the pinned Census person CSV paths resolved from --asec-education-source or the verified cache; the signal gate is checked and recorded as spm_independence_role_signal in the summary; the stage joins the pre_clone_enrichment boundary list. Release tool: the stage runs on the pool_frame-is-None path after the relationship gate (a no-op on a base that already carries the role) and its signal gate is evaluated on every path, with telemetry, like the other with_us_* stages. The composition report's docstring no longer claims the role cannot be written, and SPM_COMPOSITION_REMEDY names the stage as remedy (a) for a base built from raw sources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The stage's gate reuses check_spm_composition, but release_gate_preflight also hosts the preflight runbook, whose _release_tool_module imports the release tool by name; the primary-QRF worker identity walks every import statement in the package's static closure, so reaching the check from a stage put 'build_us_fiscal_refresh_release' into the worker's external roots and the bundle generator refused. The check, its role resolution, CheckResult and the remedy text move verbatim into us_runtime/ spm_composition.py, which imports only the Frame; release_gate_preflight re-exports them (same objects, same public names, __all__ unchanged) and the stage imports the pure module directly. 78 preflight and engine drift tests pass unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The frozen generation-0 source_stages.json digest moves with its bytes, as in the 2.2.1 bump (afaffae): 7935d891ed16eac618f04813a7b8a3ceb9a971c8b4c4273683594ba61e7dd631 -> 9f4f983091fb18dea8f4524cc100be20f9fca2b006435461592f8edf53c9a5d8 in tools/generate_us_bundle_from_constants.py and its two pin tests. tools/generate_us_bundle_from_constants.py then regenerated spec/ sources.yaml (the engine ABI lock had already been regenerated by the pool contract re-pin). The US bundle spec_sha256 is now 53a24962e567a9c44193385da7e80346e6704069306dc34cf198b878b3f0d4f2. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e-blindness inventory Both join the reviewed non-population inventory beside relationship_inputs: the composition check reads only age and the role, and the stage restores a measured Census field by exact identity. 497 passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eded agreement sweep Pins the stage's manifest and plan wiring, every refusal the brief names (a missing source column, a unit with more or fewer than one SPM_HEAD, a unit left without a classified adult, a person with no ASEC origin, a native unit mixing two source units, a Census count that does not reconcile, an unpinned CSV), the frame integration and idempotence, the signal gate, the release coverage and export contract, the adapter's persistence of the role as an input leaf (engine-gated), and a 12-seed agreement sweep showing the stage's role equals the raw rule on each person's Census row and derive_spm_role_source run directly on the same frame. Recovered from the third session's salvage ref and made to run on pandas 3: factorize takes a MultiIndex rather than a list of tuples, the sweep draws one weight per household, and dropping source_year is refused by the frame wrapper (ValueError) before the handler (SourceRuntimeError) — both are asserted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…le stage moves The new stage adds 18 configuration fields to the US bundle (10 authored under the source_stages claim, 8 resolved under generated_authorities for the remaining-stage input manifest row the role now owns), and us_runtime.source_runtime — an attested kernel module — gained the handler registration, so the seed protocol and compiled seed map digests move. Observed at this head and written verbatim, never hand-derived: - field_usage: 42,174 configuration / 32,394 authored / 9,780 resolved (was 42,156 / 32,384 / 9,772); source_stages claim 1,712 pointers, generated_authorities claim 8,614 pointers, each with its observed digest. - inventory_coverage: seed_protocol, seed_map, source_manifest, full_checkpoint and late_resource_semantics digests; source_stages 38. - tools/spec_engine_coverage.py pointer-inventory digest; docs/evidence/spec-engine/us-f0-coverage.json regenerated by that tool (42174/42174 fields, 41/41 inventory checks, --check green). - test pins: coverage-tool and field-usage counts, the minimal-loader golden spec_sha256, and the pool tool's bundle spec_sha256 (53a24962…, as tools/generate_us_bundle_from_constants.py --check prints). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
16 tasks
#948, this branch's base, merged into main with a review round the branch predates. Conflicts and how they were resolved: - release_gate_preflight.py / spm_composition.py: this branch moved the SPM composition check into spm_composition.py so build stages can import it; main hardened the check in place (report cap and its hard ceiling, age bands instead of exact ages, the person-to-unit join guard). The check stays in spm_composition.py and carries main's hardened bodies verbatim; the preflight module re-exports MAX_REPORTED_SPM_UNITS_HARD_CAP from there. The branch's updated remedy text (naming the new stage) is kept. - Generated spec-engine pins (field_usage counts, inventory seed digests, the pointer-inventory digest, us-f0-coverage.json, the loader golden vector, the pool tool's bundle spec_sha256): both sides moved them, so each was re-derived at the merged tree, not picked from either side: 42,180 fields (32,400 authored / 9,780 resolved), 41/41 inventory checks, bundle spec_sha256 1c46dda9... - PROGRESS-spm-composition.md: both sides' historical notes kept, with a dated note that #948 merged and #959 was re-levelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts (release input coverage) resolved as a union of both sides: is_spm_independent_minor_role (this branch, #893) and the three ASEC reported-receipt inputs receives_wic/snap/tanf (#980, #978 option 1) are all required with no reviewed exclusion; manifest counts 167 required / 174 total; derivation text names both additions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No textual conflicts. build_us_fiscal_refresh_release.py (restated age-band ledger filters, #986) and test_us_spine_blindness.py (spm_universe_source.py, #977) auto-merged alongside this branch's spm_independence_role stage and module registrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two test_us_spine_blindness failures that already existed at the pre-merge head 96761c6 (the stacked draft never ran CI): - spm_independence_role.py read the existing role with person.get(NATIVE_SPM_ROLE); the scanner resolves only module-local string constants, so a .get() keyed by the imported name fails closed. Read it as an explicit column subscript guarded by a columns check, which is the same lookup and keeps spm_role_source.NATIVE_SPM_ROLE the single source of the name. - build_us_multispine_pool.py now reaches 73 runtime modules, not 70: the pool's engine-input projection imports the role's output columns, which pulls in spm_independence_role, spm_role_source and spm_composition. All three are already classified and scanned; re-pin the count with a note naming them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's serializer registry (e7bccc3) forbids a bare DataFrame.to_hdf sink in production code and requires every writable HDF site to be classified. The role stage wrote its scratch parent H5 for the certified derive_spm_role_source with two to_hdf calls, so the registry test failed after the re-level. The projection now goes through _write_role_projection: one pd.HDFStore(mode="w") with put_frame_table (the shared nullable-boolean boundary, pandas fixed format), returning the digest. It is registered as spm_role_derivation_projection with a round-trip adapter in the registry test, which now pins ten serializers. derive_spm_role_source is unchanged and still reads the projection with pd.read_hdf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI engine-us (3.14, us-am) failed test_h3_us_post_transfer_parity: the pinned graph declaration carries multispine_pool's remaining-stage manifest and engine-input projection/defaults digests, which this branch re-pinned when the pool projection learned the SPM role is a required source input (926 inputs, 1,059 rows). Regenerated with tools/graph_us_post_transfer_fixture.py: only those three digests in us_post_transfer.json change; sources/, expected.csv, NORMALIZATION.md and PRODUCED_BY.txt are byte-identical, so the derive/seed/materialize outputs are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stage passes it The base stage hands the role stage its --asec-education-source paths, which name the official Census archives (asecpub23csv.zip and so on); the education loader accepts an archive or its extracted person CSV. The role loader only accepted the CSV, so a production base-stage run refused every year with "CSV byte length mismatch" (route A's first run, 2026-09-23 03:58). The loader now reads the pinned member from inside an archive: the archive's SHA-256 must equal the pin, it must hold exactly one pinned member whose size and SHA-256 equal the CSV pins, and it is re-hashed after reading. A CSV path is checked exactly as before. Verified on the three real archives (146,133 / 144,265 / 142,125 persons, the pinned counts); tests for archive equivalence and each refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47976be taught spm_role_source.py to read the pinned person member from the official Census archive, so the design note's "byte-identical to main" and "Build P lane untouched" claims no longer held. The note now describes the reader (archive SHA-256 pin, exactly one pinned member, member size and SHA-256 pins, re-hash after reading; the extracted-CSV path unchanged) and its consequence: spm_role_source.py is in PRODUCER_SOURCE_FILES, so a role enrichment recorded before it must be certified from its recorded producer commit, as the 2026-09-15 recertification already was. SPM_COMPOSITION_REMEDY no longer offers household-structure fallback as a remedy, since a release now requires the source role. Dated notes mark what this PR changed in both SPM role design notes and the journal. Docstrings and comments name archive-or-CSV sources. No code path, condition or value changes; only the remedy message text differs at runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two derivation receipts listed up to 20 internal spm_unit_id values of minor-only SPM units in gated populations. They now carry the count (n_offending_units_reported), as main's composition receipt reports only counts, and each records its pre-redaction SHA-256, which the wrapper receipts' historical_receipt_sha256 names. The proof script emits the count, so a re-run produces the same shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
September 23, 2026 09:29
MaxGhenis
added a commit
that referenced
this pull request
Sep 23, 2026
voluntary_filing, sipp_head_start and ssi_disability_criteria are seed sites, so the seed protocol's implementation digest and the compiled seed map digest move with their source, and CI's engine-shared job refused #992 merged with main (test_us_inventory_is_structure_exact_and_complete, test_semantic_hash_has_golden_vector_and_surface_separation and the coverage tool's committed-report checks). Observed at this head and written verbatim, never hand-derived, as #959 did: - inventory_coverage EXPECTED_HASHES: seed_protocol 56f832aa…, seed_map 18594e59… - the minimal-loader golden spec_sha256 db3fdab1… - docs/evidence/spec-engine/us-f0-coverage.json regenerated by tools/spec_engine_coverage.py (42,180/42,180 fields, 41/41 inventory checks; --check green) Spec-engine and touched-stage suites pass on this merge of origin/main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Sep 26, 2026
The native input_coverage_profile froze the release coverage manifest at 05d254a (163 required inputs). Main since requires 167: the SPM independence role (#959) and the reported receipts receives_snap, receives_tanf and receives_wic (#978). The profile's anti-rot test failed on every lane. The historical roster is re-extracted in manifest order with the manifest digest re-pinned. Because the identifiers encode their counts, they move to us_release_167_v1, us_national_cd_165_v1 and us_native_national_cd_163_v1. The derivation rules are unchanged: national/CD omits block/tract, and native also omits the prior-year family. The survey-enrichment coverage count moves 161 -> 165 with the national/CD profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
ASEC bases can lose the Census relationship fields needed to classify minor-only SPM units. Restore
is_spm_independent_minor_rolethrough an explicit source stage that joins the pinned complete ASEC person files by income year and PERIDNUM, using the existing reviewed derivation. The base builder runs it before support cloning; releases from older ASEC bases rederive and reconcile it.The adapter persists the engine-declared source input without manufacturing a default. Source mismatches, missing observations, stale provenance and unresolved units refuse the build. Gate details now survive JSON/checkpoint serialization; existing nonconstant role columns cannot bypass source checks.
Targets
main(its original base, #948, merged). This does not qualify ACS-origin rows: prepared stacked pools without a sourced role fail before calibration. The ACS source/partition work and national/CD release qualification remain separate requirements.Re-level on main (22–23 September)
Three merges of
origin/main(f69389ee0at4d773a478,0d3c40a03ate6dede2e4,a222b904cate66d6f6c8), then two fixes for contracts main gained while this branch was stacked.Textual conflicts and resolutions:
release_gate_preflight.py/spm_composition.py: this branch moved the SPM composition check intospm_composition.pyso build stages can import it; us: name the SPM composition defect in seconds, and refuse it by name in the release #948's review round hardened it in place. The check stays inspm_composition.pywith main's hardened bodies (every shared definition is AST-identical to main's except the remedy text, which now names this stage); the preflight module re-exports from there.us-f0-coverage.json, the loader golden vector, the pool tool's bundlespec_sha256): re-derived at the merged tree, not picked from either side.generate_us_bundle_from_constants.py --checkgivesspec_sha256=1c46dda9…;spec_engine_coverage.py --checkgives 42,180/42,180 fields and 41/41 inventory checks.is_spm_independent_minor_role(this PR) andreceives_wic/receives_snap/receives_tanf(Local-area publish refused: the donor must be a published release, so fold the reported-receipt columns into the national build #978 option 1) are all required with no reviewed exclusion: 167 required, 7 reviewed exclusions, 174 total. Regenerating the manifest gives no diff.PROGRESS-spm-composition.md: both sides' notes kept, with a dated note.Fixes for main's newer contracts (
121ea4119,ec5d701e6,4ef635715):frame_serializer_registry, e7bccc3): main forbids a bareDataFrame.to_hdfsink in production code. The stage's scratch parent H5 forderive_spm_role_sourceis now written by_write_role_projection(pd.HDFStoreplusput_frame_table, fixed format). It is registered asspm_role_derivation_projectionand has a nullable-boolean round-trip adapter, so the registry now lists ten serializers. The derivation still reads the projection withpd.read_hdf. The re-level did not changespm_role_source.py. The archive reader below changed it later, in47976be6c.person.get(NATIVE_SPM_ROLE)failed closed because the scanner does not resolve imported constants. It is now an explicit column subscript. The multispine pool tool's runtime import graph is re-pinned from 70 to 73 modules, addingspm_independence_role,spm_role_sourceandspm_composition, all three already classified and scanned. Both failures were already present at the pre-merge head96761c635: a scan of that tree shows the same offender and 73 modules.microcosm-graph): CIengine-us (3.14, us-am)onec5d701e6failedtest_h3_us_post_transfer_parity. The pinnedus_post_transfer.jsoncarriesmultispine_pool's remaining-stage manifest and engine-input projection/defaults digests, which this PR re-pins when the pool projection learns the role is a required source input. It was regenerated withtools/graph_us_post_transfer_fixture.py. Only those three digests change;sources/,expected.csv,NORMALIZATION.mdandPRODUCED_BY.txtare byte-identical.Archive reader (
47976be6c) and producer identityThe base stage passes the official Census archives (
asecpub23/24/25csv.zip) through--asec-education-source, which has always accepted either the archive or its extracted CSV.spm_role_source._load_sourceread only a CSV, so it would have refused those archives at the CSV byte-length check.47976be6cadds_read_archive_member, which does the following:pd.read_csvcall the CSV path uses;The pins are the existing
ASEC_EDUCATION_ASSISTANCE_ARCHIVESvalues. The extracted-CSV path is unchanged statement for statement, and everything after the read is shared and unchanged. In a local review run on all three vintages, the archive and the CSV gave equal frames and equal source checks.Consequence:
spm_role_source.pyis insource_enrichment.PRODUCER_SOURCE_FILES. From a checkout with this change,_check_producer_source_identityrefuses to certify or publish a native-SPM-role candidate recorded before it. Such a candidate must be re-validated from a checkout at its recorded producer commit. This is not a new requirement forpopulace-us-2024-spm-20260915: it recorded743683b0e, andsource_enrichment.pyandcontract.pyhave both changed onmainsince. Its 2026-09-15 recertification already ran certify, validate and publish from a clean worktree detached at743683b0e. Seedocs/us-spm-role-stage.md§2.Follow-up on 23 September (behaviour-neutral)
spm_role_source.pyis byte-identical tomain.SPM_COMPOSITION_REMEDYno longer offers the head/spouse fallback as a remedy. A release now requires the source role; only the message text changed.docs/us-spm-role-for-a-fresh-base.mdanddocs/us-spm-role-stage.md§4 record what this PR changed.origin/mainatb78f5a88e(Preflight a new-lineage US release without a selection source #988) with no conflicts. The mergedrelease_gate_preflight.pyis main's file, less the definitions this PR moved tospm_composition.py(AST-checked).packages/andtools/since47976be6chas two sources. The first is Preflight a new-lineage US release without a selection source #988's three files, identical to main's change. The second is docstring and comment edits plus theSPM_COMPOSITION_REMEDYstring: an AST comparison with docstrings stripped shows no other difference inspm_role_source.py,spm_independence_role.pyorspm_composition.py.Validation
Re-level (local, policyengine-us 2.2.1, Python 3.14):
ec5d701e6);test_acceptance_h_parity.pypasses at4ef635715.ruff check .is clean.ruff format --checkpasses on every changed file excepttest_spec_engine_field_usage.pyandtest_us_bundle_core_contracts.py, whose drift is identical onorigin/mainand outside this PR's hunks. CI lint runsruff checkonly.tools/ci_test_groups.py --verifypasses, and the generated-variable audit--checkis current.Follow-up (23 September, merged tree at the new head):
source_enrichment,h5_enrichment, spine blindness, serializer registry, release input coverage, the base builder, the multispine pool tool, the spec-engine seed, coverage and inventory tests, bundle pins, and the fiscal-refresh SPM and gate subset.ruff check .is clean, andruff format --checkpasses on the changed Python files.ci_test_groups.py --verify, the bundle--check(spec_sha256=1c46dda9…), the generated-variable audit--checkandspec_engine_coverage.py --check(42,180/42,180) all pass.Original (pre-re-level):
docs/us-spm-role-stage.md.Aggregate receipts and reproducible proof scripts are committed under
experiments/; population files are not.🤖 Generated with Claude Code