Skip to content

Recertify a published US release for a newer policyengine-us without a rebuild - #1366

Merged
MaxGhenis merged 12 commits into
mainfrom
compat-recertification
Oct 23, 2026
Merged

MaxGhenis merged 12 commits into
mainfrom
compat-recertification

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Ready at 446b3fe. CI is green. Sol (GPT-6.1) agreed at round 5 and again on the round-6 delta, and the owner approved 446b3fe. This adds a compatibility_recertification release type, so a published, certified Populace US release can carry a measured compatible_model_packages claim for a newer policyengine-us without a rebuild. The target today is 2.60.4, the newest on PyPI. The child republishes the H5 and every other parent artifact byte for byte and transforms no data.

Why: PolicyBench v2 needs a policyengine.py bundle certified with 2.60.x (Max, 2026-10-23, policybench#240). bundle.py certify-data refuses a model version the release was neither built with nor claims. The Route A owner (Microcosm non-dynamics work) agreed to this route; it reviews and merges.

Design

The code is in microcosm/data/compatibility_recertification.py and tools/build_us_compatibility_recertification.py.

  • Parent identity comes from the parent's published manifests, not from code constants.
    • The child carries parent_release_manifest.json and parent_build_manifest.json byte for byte, with their SHA-256 in the report.
    • Validation rebuilds the parent from the carried bytes and replays today's release contract on it.
    • publish_release refuses before any Hub mutation unless the parent tag serves exactly those manifest bytes. It also checks, with get_paths_info, that every carried artifact sits at exactly one location in the parent tag: the LFS sha256 or the git blob sha1.
  • National parents only (SUPPORTED_PARENT_ROLES = ("national_default",)). A local-area or non-calibration parent is refused. The parent mechanism itself does not depend on the role.
  • A reviewed register of engine column changes, in both directions (RECERTIFICATION_COLUMN_CHANGES). The real parent file measures:
    • Ignored under 2.60.4: first_home_mortgage_interest and second_home_mortgage_interest (PE-US #9605). They are recorded as known ignored columns, not as mappings, so the H5 stays byte-identical. filing_status_input is already a stored-input register entry, and 2.2.1 ignores it too.

    • Newly read under 2.60.4:

      • tax_unit_role_input (PE-US #9617): heads and spouses follow stored roles, not age.
      • parent_1_id and parent_2_id (PE-US #9406): Medicaid MAGI composition.

      No existing gate sees this direction, and a column the register doesn't name refuses.

  • The tested runtime is three wheels: policyengine-us 2.60.4, policyengine-core 3.33.3 and spm-calculator 1.0.0.
    • policyengine.py 6.10.1 cannot be imported under 2.60.4. Importing its US package builds PolicyEngineUSLatest, and that refuses a bundled manifest not certified for the installed model.
    • So the receipt records wrapper_loader: not_attested with that reason. policyengine.py's own certify-data proves the wrapper downstream.
  • Runtime identity, checked before the first engine import:
    • Every installed file of each package, YAML included, must match its wheel, and the wheel must match the PyPI digest.
    • The package trees must be complete with no symlinks. Each package's spec origin and search path must be its installed root.
    • After the run, every loaded engine module is checked by package name and by file origin against that inventory. A namespace package (a directory without __init__.py, such as policyengine_us.parameters.gov) has no file and runs no code. It passes only with a NamespaceLoader and a search path inside the tree.
    • Bytecode is isolated to a fresh pycache_prefix. The tool bootstraps it; other entry points need PYTHONPYCACHEPREFIX.
  • The country-loader replay under the tested wheels: every stored column the engine defines must come back unchanged through the loader. PyTables bool columns, stored as uint8 with a _dtype attribute, compare as bool.
  • Drift. The same household-batched scorer runs three times: under 2.2.1, under 2.60.4, and under 2.60.4 with the newly read inputs withheld. The third run isolates the role-driven move in income tax, EITC and CTC.
    • It scores the 18 exact critical rows (with the release tool's SOI semantics: positive parts, the nonrefundable-CTC rule and the itemizer mask), the scorable Table 1.4 rows, and headline aggregates: income tax, CTC, EITC, Social Security, SNAP amounts and units, SSI, Medicaid enrollment and spending, Medicare IRMAA and cost, state income tax and AGI.
    • Medicaid is scored in whole-state batches, which is exact because its two aggregates are sum_by_state.
    • Score documents are bound to the H5 digest, the parent's calibration diagnostics, the SPM selection, full household coverage, the verified runtime and the producer commit. Rows recompute from the embedded documents.
  • Never latest.json. The publisher refuses to point the default at a recertification child, as it does for the reported-receipt child. The child republishes its parent's data under a wider claim and is reached by its release id, so it always publishes with --no-latest --tag-only.
  • Claim. The parent's entries are kept. Two publisher_claim entries are added: the model, and an exact pin of the tested Core. Both are attributed to the declarer, Max. The grammar is ==T, or >=L,<U with built_with < L <= T < U <= next minor. claim_measurement holds the measured version, the baseline and the drift summary.

Status

  • Release type, validator, certify step, column-change register, loader replay, drift scoring and claim
  • Tool subcommands fetch-parent, build and score.
    • python -m microcosm.data.compatibility_recertification validates a candidate. With --certify it runs the loader replay and writes the certified child.
    • microcosm-publish-release runs preflight and publication.
  • Wiring:
    • the contract.validate_release_dir dispatch;
    • release.prepare_release accepting the wheels;
    • the publish-time parent check before any Hub mutation;
    • the latest.json refusal;
    • publish_cli printing the claim measurement.
  • Tests: 58 test functions, 89 cases engine-free with parity.
    • Engine-free:
      • Hypothesis byte flips;
      • tamper and parent refusals, including placement, traversal, key collisions and reconstruction containment;
      • the claim-grammar matrix;
      • drift bindings and the decomposition property;
      • FakeHub publication refusals;
      • fake installed runtimes: a patched YAML, an unrecorded module, source hidden in __pycache__, a sourceless pyc, a symlink, an overlay package, an engine imported before authentication;
      • bytecode isolation status.
    • engine_workflow/us: real-engine scorer batch-size invariance and the counterfactual, passing under 2.2.1 and 2.60.4.
    • microcosm-build parity for the copied _signed_component and US_POPULATION_AGGREGATE_VARIABLES.
  • The runbook docs/us-compatibility-recertification.md, plus separate pointer paragraphs in agent-guide and README. The owner OK'd keeping them apart from Put the release contract on paper and freeze C1 and C5 as golden fixtures (M0) #1285; whichever merges second rebases.
  • Sol (GPT-6.1) review: r1 through r4 REQUEST_CHANGES, every finding fixed; r5 AGREE at 9ee1385. Its two low notes are fixed in 142b3cb.
  • Owner review (Microcosm non-dynamics work): approved 142b3cb.
    • It checked the r5 delta and the shared paths (release.py prepare and publish, the contract.py dispatch, publish_cli); each recertification branch is gated by release_type.
    • It checked describe_parent, the reconstruction, the validator, the claim grammar and require_parent_publication.
    • It checked the runbook's policyengine.py claims against policyengine.py main.
    • Its nonblocking latest.json note is taken in 446b3fe.
  • Real-runtime smoke of the trust checks, unstubbed, in fresh processes with a fresh PYTHONPYCACHEPREFIX.
    • It found that at 142b3cb the by-name pass refused namespace packages, which would have failed every real replay and score. This is fixed in 446b3fe.
    • At 446b3fe, the scorer-order check passes under 2.2.1 (12,509 modules verified) and 2.60.4 (13,856), with every wheel's PyPI digest verified.
    • The unstubbed loader replay plus a Microsimulation passes under 2.60.4.
  • Sol r6 delta review (142b3cb..446b3fe): AGREE, no findings. It checked the NamespaceLoader class on Python 3.13 and 3.14, _NamespacePath recalculation, and the refusal order. The owner approved the delta to 446b3fe.
  • CI green at 446b3fe (run 40032858472)
  • Full rehearsal on the published populace-us-2024-0fb05b6-4b57d15a287c-20260930T150401Z, run once from merged main. Producer identity is per commit, and microcosm merges with merge commits. The rehearsal covers build, replay, three scores (one at a time), certify and preflight. It is armed as a launchd one-shot that starts after the merge, once the host reservation allows.
    • Done at 7f202d7: the build validates as pending with parent_contract = passed_with_stored_input_evidence_replaced.
    • Done at 7f202d7: the real-file replay passes (194 columns).
    • Done at 7f202d7: the 2.2.1 baseline reproduces every critical row's build final_estimate to about 1e-14 relative.
    • Pending: the 2.60.4 score, held by the host reservation.
  • Dry run on the d1386 parent once it is published. The drift goes to Max (cos add-decision), who declares the claim before publication.

Invariants (held by tests)

  • Byte preservation: every parent artifact reaches the child with the parent's digest. One changed byte in any carried file refuses (Hypothesis).
  • Identity: the report's parent block is a function of the carried manifests and files alone.
  • Additivity: the child's compatibility lists are the parent's entries plus the claim, never fewer. built_with_* and built_at stay the parent's.
  • Drift arithmetic: difference = tested - baseline, and newly_read_inputs_effect + other_engine_changes_effect = difference (Hypothesis).
  • Runtime identity: any patched, added, hidden or symlinked file under an engine tree refuses, and so does an engine module loaded from outside it.
  • Classification: stored_column_changes.ignored is exactly the stored-input rule's refusals, and ignored ∩ read = ∅ (Hypothesis).
  • Differential:
    • the copied release-tool rules equal the tool's (Hypothesis over _signed_component);
    • scorer totals are equal at batch size 1 and whole-file (real engines).
  • Publication: a child whose parent tag does not serve the carried manifest bytes, or serves an artifact at two locations, publishes nothing (the FakeHub event log stays empty).

Limitations

  • National parents only. The routed ACS-local release (buildo-acs-local-766b7c7-20261017T030317Z) is built with 2.2.1 and claims nothing. So policyengine.py certification on 2.60.4 would need --drop-regional-release, which regresses state and CD coverage, until a local-area child or a 2.60.x ACS-local build exists.
  • The rehearsal parent predates Bind the stored-input verdict into US release evidence (#1031 follow-ups) #1034 stored-input evidence. The child substitutes its own two-engine stored-input verdict, and only when that evidence is entirely absent. The substitution is recorded.
  • Trusted startup is assumed: the interpreter, stdlib, site configuration, import hooks and a fresh cache prefix. The runbook says so.

axiom: n/a: data release infrastructure

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 23, 2026 07:44
Data module only: parent identity from the parent's published manifest,
the reviewed register of engine column changes (both directions), the
loader replay under the tested wheels, drift metric definitions and
combination, the validator, certification and the publish-time parent
check. Builder tool, contract/release wiring, tests and docs follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sh-time parent check

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 8 commits October 23, 2026 13:57
…he counterfactual

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bind every score and the loader receipt to every installed byte of the
  four packages (parameter YAML included), proven against the wheels, and
  the wheels against PyPI's published digests; drop install-path-dependent
  direct_url from the identity.
- Refuse a nonpositive batch size; require each score to cover every
  household of the H5 in both passes.
- Authenticate artifact placement against the parent tag at publication
  (LFS sha256 or git blob id), so a root artifact moved into the release
  directory cannot publish at the wrong path.
- Restrict model claims to ==T or >=L,<U within the tested minor series,
  above the built-with version.
- Bind scores to the carried diagnostics digest and the parent's SPM
  selection.
- Refuse parent artifact keys that collide with the child's evidence keys.
- Narrow the stored-input substitution to evidence-free parents whose other
  gate fields agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- policyengine.py certifies its bundled data release against the installed
  country model at import: policyengine 6.10.1 under policyengine-us 2.60.4
  raises before any loader exists (found by the real-file rehearsal). The
  replay now proves country, Core and calculator, and records the wrapper
  loader as not attested; the downstream pe.py certification proves it.
- Runtime proof: files on disk under each package must equal the recorded
  inventory (no unrecorded modules); imports must resolve to the installed
  package; the engine compiles from source into an isolated, empty
  bytecode cache, and every loaded module is checked against the verified
  tree after the run.
- Placement: refuse parent paths the publisher would not write (releases/,
  unclean paths); refuse a parent tag that publishes an artifact at both
  locations; check offline that the publisher's upload plan covers every
  carried artifact.
- Stored-input substitution compares gate_evidence blocks as written.
- Docs: carry what the manifest declares, not every file on the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bytecode trust moves to a bootstrap: the tool sets a fresh
  sys.pycache_prefix before its first Microcosm import, other entry points
  start with PYTHONPYCACHEPREFIX, and the replay and scorer refuse a process
  in which any Microcosm or engine module ran from other bytecode. Score
  documents must record isolated bytecode.
- Runtime trees: only .pyc inside __pycache__ is excluded, so source hidden
  there or a sourceless .pyc beside the sources is unrecorded and refused;
  symlinks in a package tree are refused; loaded engine modules are matched
  by file, covering Core's synthetic variable-module names.
- Parent paths with '..' are refused, and the builder checks containment
  before placing any file.
- Docs: three wheels, wrapper status, the bytecode requirement per step.
- The loader replay compares a PyTables bool column (stored as uint8) as
  bool; the real-file rehearsal found the strict dtype comparison refused
  is_related_to_head_or_spouse though its values were unchanged.

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

- An import must resolve to the installed package as written: the spec's
  origin and submodule search locations may not pass through a symlink or
  lie elsewhere, and every loaded module of the three engine packages is
  checked by name (file and __path__ under its tree, unlinked) as well as
  by file.
- The replay authenticates the runtime before its first engine import, and
  a process that imported the engine before any authentication is refused,
  since Core executes every variable source when the system is built.
- Parent reconstruction validates the parent id and carried file names and
  contains every write in staging; the validator stops at a parent block
  the carried bytes do not describe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sol r5 AGREE notes: fetch-parent now validates the US release-id grammar
before it makes any directory, and the runbook describes the loaded-module
check by package name and by file origin, including search paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real-runtime smoke of the trust checks (both engines, unstubbed) found that
verify_loaded_modules refused every namespace package: policyengine-us imports
parameter and reform directories without __init__.py, which Python gives no
file and a NamespaceLoader. Such a module runs no code, so it is now checked by
its search path alone, which must lie inside the installed tree as written. A
fileless module without a namespace loader is still refused. The smoke on the
fixed code passes under 2.2.1 and 2.60.4, and the loader replay passes
unstubbed under 2.60.4.

The publisher now refuses latest.json for a recertification child, as it does
for the reported-receipt child (owner review, nonblocking note): it republishes
its parent's data under a wider claim and is reached by its release id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 23, 2026 23:30
@MaxGhenis
MaxGhenis merged commit dec45a4 into main Oct 23, 2026
10 checks passed
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