Skip to content

Add receipt contract v2 and selection-aware reference preflight - #1397

Merged
MaxGhenis merged 49 commits into
mainfrom
nzhub/g8w1-receipts
Oct 29, 2026
Merged

MaxGhenis merged 49 commits into
mainfrom
nzhub/g8w1-receipts

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

G8-w1 for Microcosm NZ v0: receipt contract v2 and selection-aware reference preflight. Targets main and lands after #1395. The commit message has the full description.

What changes

  • Receipt contract v2 (transport/receipt_contract.py). It is opt-in: a contract without contract_version runs the v1 validator and assignment bodies unchanged.
    • v2 is closed at every level.
    • It adds disjoint strata, each with its own rate, and knob-selected assignment profiles (draw key suffix and exclusion order).
    • exclude_columns keeps v1's semantics, and it runs before group exclusions.
    • Overlap and weighted Hall feasibility diagnostics never refuse.
    • Each stratum records its realized weighted count beside its target.
    • The reserved unit fields (entitlement_entity, payee, draw_unit, projection) admit only "person", and every read goes through them.
  • Shared calibration predicates (calibration_predicates.py): preparation and receipt strata use the same validated predicate evaluation.
  • Reference selection (transport/reference_selection.py): opt-in per-resource exemptions for parked references. They are validated against the full effective document before any row is filtered. A spec without a selection resource compiles as before.

Invariants

Invariant 1 covers v1 contracts, 2–7 cover v2 contracts, and 8 covers specs with or without a selection resource. Each holds for every input of its kind, not just the examples:

  1. v1 is unchanged on success. For a valid contract without contract_version, the flags and audits are byte-identical to the pre-v2 code. Refusals are in the notes below: one fault refuses as before, but two at once may refuse on a different one first. test_transport_receipt_v2.py freezes the baseline assignment and compares toy and Hypothesis-generated v1 cases.
  2. Strata are disjoint on the data. Overlapping filters refuse, and nobody outside the strata is assigned.
  3. Mutual exclusion. Within an exclusion group, no unit gets two outputs: each output's pool drops units already assigned to an earlier member.
  4. Assignment is bounded. Assigned ⊆ judgments ∧ payment > 0 ∧ ¬exclude_columns ∧ stratum.
  5. Draws are deterministic, and draw keys are injective across (program, suffix) pairs, including adversarial delimiters.
  6. Expected mass. For every completed v2 assignment, each stratum's rate × remaining eligible mass equals its target within a relative 1e-12. Each stratum's audit records the check. A failed check, or a target above the remaining mass, refuses with the audit attached (the failed-check refusal is new in 5108046).
  7. Diagnostics never gate. The overlap and Hall diagnostics only report.
    • Since 5108046 the Hall check is an exact max-flow, not a loop over every subset. Since 2f2f704 it runs Dinic on integer capacities, so at most one routing phase per demand carries flow.
    • One limit remains, as before: a reported cut whose total exceeds the float range can't be written as a float.
  8. No selection, no change. Without a reference_selection resource, node parameters, surfaces and keys match the legacy path. A differential test runs against the reference_selection_legacy.py.txt fixture.

Notes

  • v1 refusal order (from Belgium's review): on v1, _document(receipt_contract) now runs before the v1-only require_params and before _whole_tables.
    • A v1 node with a single fault refuses exactly as before v2. The review's kernel differential found that 14 of 15 single-fault kinds match.
      • The fifteenth isn't a v1 node: a contract carrying a contract_version key, whatever its value, now refuses with the v2 message ("Receipt v2 contract has missing or unknown fields.").
    • With two faults at once, 22 of the 91 pairs get a different first refusal:
      • a bad contract document plus a v2-only parameter;
      • a bad contract document plus missing tables, node sources or an undeclared artifact alias;
      • a contract_version key plus any other fault;
      • a missing contract or sha plus a v2-only parameter, which now refuses as "missing required parameter(s)" rather than "unknown parameter(s)";
      • an unknown parameter plus a v2-only parameter, where the message now names only the unknown one.
    • Every refusal stays a ValueError, except a non-string contract, which is a TypeError before and after.
  • Cache keys change for every country.
    • The new modules join the implementation hashes of takeup.assign@1, calibration.prepare@1 and the target compilers, and target_kernels.py itself changes.
    • Since 5108046, reference_profiles and reference_selection also join the hashes of the five kernels that decode the target surface but hashed neither: takeup.assign@1, targets.problem@1, the gate battery, diagnostics.calibration and geography support.
    • In all, 18 of the 31 registered kernel implementation hashes moved at 5108046 (Belgium's count), not only those five. Nothing pins them. 2f2f704 moves 2 of the 31 again: takeup.assign@1 and calibration.prepare@1.
    • Under the UK hub's Budget-window rule, this PR does not merge before 2026-10-29 12:00Z (8am ET).
  • Belgium transport protocol. Belgium cleared the src hunks in the hub's hunk file c2, unconditionally.
    • The c2 file's sha256 is 11b7eadc3e6951c1…. Its file order (modified files, then new ones) differs from git diff's, so a fresh diff hashes differently.
    • Applying c2 to 4f333f2 gives the same eight src blobs as 2f220f7, d8425ae and f5e75f8: calibration_columns.py c41ee074, calibration_predicates.py 13d38162, column_kernels.py bfa7eadf, compose.py a4184747, receipt_contract.py 9227d880, reference_selection.py f8334344, takeup_kernels.py b1d110a2 and target_kernels.py 2b95a01d.
    • The S5 (Let transport specs declare pre-calibration stages #1392) merge check gives 0 conflicts against 9959390.

Hub validation (10/25–26, serial -n 0)

  • At 2f220f7, 13 of 14 files passed. Those are the receipt v2 files, the column, takeup, target and money kernels, the composed graph, the transport graph, reference profiles, the NZ spec package, the country spec, the spec-engine bundles and the UK target references.

  • test_transport_reference_selection.py failed 3 tests there, all fixture faults:

    • one comparison node passed a single entity where the kernel needs a person entity plus a group entity;
    • one fixture graph had a CREATE node with no sources.

    The build lane had not run the file, and draft CI doesn't run. d8425ae fixes both fixtures, and the file now passes: 33 tests.

  • Mutants: all 18 are RED and restored byte for byte. One of them, legacy_selection_path_changed, was red at 2f220f7 only because its test already failed. Rerun at d8425ae, it fails on the frozen-params assertion, so that kill is now real. A 19th mutant, which skips the takeup.compare exemption check, makes the direct-comparison test fail.

  • Static checks pass: the CI test plan, the burndown, git diff --check, and Ruff check and format.

  • At f5e75f8, after the fix4 merge: test_country_spec.py 136, test_nz_spec_package.py 1,201, test_transport_reference_selection.py 33, test_transport_money_kernels.py 40 and test_transport_composed_graph.py 172 pass.

  • At 5108046 and 84c2082, one file per process at -n 0: test_transport_receipt_v2.py 97, test_transport_receipt_v2_kernel.py 14, test_transport_reference_selection.py 51, test_calibration_columns.py 91, test_transport_column_kernels.py 30, test_transport_takeup_kernels.py 38, test_transport_target_kernels.py 67, test_transport_composed_graph.py 172, test_transport_graph.py 15, test_reference_profiles.py 123, test_nz_spec_package.py 1,201, test_country_spec.py 136, test_spec_engine_country_bundles.py 21, test_uk_target_references.py 30 (5 skipped), test_transport_money_kernels.py 40, test_transport_gate_kernels.py 31, test_transport_terminal_kernels.py 18, test_gate_battery_contract_pins.py 20, test_gate_battery_replay.py 2, test_nz_joint_geography.py 22 (11 skipped) and test_transport_codecs_geography.py 22 pass.

    • The run used the uncommitted tree on f5e75f8. git diff f5e75f8b 84c2082b is byte-identical to it (sha256 1ca0bcc5…).
    • The two refusals the hub added (an unresolved assignment knob and a missing entity table) each have a test and a mutant that fails it.
  • At 2f2f704, one file per process at -n 0: the same 21 files pass, with test_transport_receipt_v2.py at 117 and test_transport_reference_selection.py at 60; the other 19 match the 84c2082 counts above.

    • 16 mutants each fail a test that guards their line, and each file is restored byte for byte. Review r4's pattern-index mutant fails the new witness.
    • The plan ran on the uncommitted tree; the commit adds only that witness test (src sha256 20baf6fa… against 97c08e0 either way).

Status

  • Review, round 1 (Claude Opus 5.5, at d8425ae; Codex was at capacity): APPROVE_WITH_NITS, nothing blocking.
    • v1 is unchanged on success: 4,500 generated contracts against the base commit's own code gave 0 differences. v2 and reference selection are dormant, since no packaged spec declares them.
    • Fixed in this body: the Belgium hash (F1; see Notes) and the refusal-order classes (F5).
    • Fixed in code at 5108046 (a GPT-6.1 Sol build lane, plus two hub additions):
      • The Hall diagnostic is an exact rational max-flow (F2). A Hypothesis test checks it against an exact brute force. The 0.11 s for 24 demands on 20,000 people came from a fixture with only 6 distinct patterns; round 2 (F-A) found the general case unbounded.
      • A failed numerical check refuses (F6).
      • The two surface-decode guards and the resolution filter have tests (F3).
      • reference_selection and reference_profiles join the implementation hashes of the kernels that decode the surface (F4).
      • Activation validates binding domains, and validation refuses exclusion cycles (F7).
      • takeup.assign's node key reads only its knob (F8).
      • The two property tests catch their mutants on every run (F9).
      • Four v2 edge cases are handled (F10).
      • The lane's 17 mutants and the hub's 2 are all RED. v1 still matches the base on 1,500 mixed and 3,000 valid contracts, and calibration.prepare on 300 cases.
    • Belgium OK'd the src hunks (sha256 0a6d4d16…) before the commit. It found no pinned hashes or keys in B1's golden or S5's tests. Merged with S5 91d46e1 and B1 dc157a7, 1,955 of 1,955 tests passed.
    • 84c2082, test-only: it suppresses Hypothesis's too_slow check on NZ's hold-out property test, which failed once on a stalled host.
    • Hub provenance (F11): reruns no longer overwrite earlier logs, and each record names its HEAD.
  • The fix4 merge: f5e75f8 merges Normalize donor money with exact declared factors #1395's head (db02ae8), which carries Add feed-derived NZ reference ageing factors (G8-r2a) #1382's fix4 (Belgium OK'd it 10/26). It merged clean, the merged transport/cli.py is c4ddcb4's blob, and the NZ golden regenerates byte for byte. The batch delta review (GPT-6.1 Sol, at db02ae8) approved the batch with no findings. It rebuilt this merge with git merge-tree, and the result matches exactly.
  • Review, round 2 (Claude Opus 5.5, at 84c2082): REQUEST_CHANGES.
    • F-A, blocking: the Hall max-flow was still unbounded with distinct patterns. On the hub, 84c2082 took 16.5 s for 12 demands on 20,000 people, and 24 demands were killed at 300 s.
    • F-B, blocking: Belgium's OK had become conditional on its F10 follow-up.
    • F-C: _node raised a bare KeyError: 'assignment_knob' for a malformed v2 node contract.
    • F-D: activation raised AttributeError for a non-mapping input_domains[entity].
    • Everything else checked out: v1 unchanged, the predicates refactor, the F4 hash closure, and 13 of 13 mutants.
  • 97c08e0 (F-B): Belgium's f10-followup.diff, committed unchanged (diff sha256 de15bc7a…).
    • The receipt unit check now reads the [BE] dispatch seam and matches ids through person_household_id-style membership columns.
    • Hub run at -n 0: test_transport_receipt_v2.py 99, test_transport_receipt_v2_kernel.py 14. Belgium's r1 OK is unconditional.
  • 2f2f704: the round-2 fix (F-A, F-C, F-D; Belgium's (a), (b), (c)). It was committed after three hard-tier reviews and Belgium's OK.
    • Hall: each eligibility pattern's weights are summed as exact integers over one power-of-two denominator, 65,536 people at a time. Capacities are integers over the lcm of that denominator and the targets' denominators. Dinic replaces Edmonds–Karp. People eligible for no demand are skipped, as at 84c2082. The record and the reported set are unchanged.

      • Same host, 84c2082 → 2f2f704: 20,000 people × 12 demands, 16.5 s → 0.027 s. 4,000 × 24, 44.3 s → 0.029 s. 20,000 × 24, killed at 300 s → 0.17 s. Belgium's 100,000 × 4 shape, 0.10 s → 0.044 s.
      • Exhaustive oracle: 25,000 small and 1,200 medium cases, checked against brute force over every demand subset, give 0 failures. All 1,643 disagreements with 84c2082 are its own NumPy-integer target overflows.
    • _node validates a node's own v2 contract before it reads assignment_knob.

    • Activation and calibration inputs: a non-mapping input_domains[entity] gets the existing "needs a domain" ValueError.

    • Reviews r3a and r3b (GPT-6.1 Sol, two lenses, on the first draft): REQUEST_CHANGES. They found:

      • a test fixture that refused before its guard;
      • medium cases that were only checked differentially;
      • four wrong implementations that passed every Hall test;
      • NaN and infinite weights on ineligible people, which now raised;
      • a theorem that needed a short-flow qualifier;
      • several claims to tighten.

      All are fixed: the four witnesses are now tests, the oracle covers the medium cases, and the claims are restated.

    • Review r4 (GPT-6.1 Sol, on the fixed tree): APPROVE_WITH_NITS. It verified 281 tests, 26,200 oracle cases and all 16 mutants. Its one nit (P3) was a fifth wrong implementation, with pattern labels restarting at each chunk. test_hall_keeps_pattern_indices_aligned_across_chunks now kills it.

    • Belgium OK'd the src (sha256 20baf6fa… against 97c08e0). The commit merges cleanly with S5 6197d74 and B1 dc157a7. Receipt v2, reference selection and receipt v2 kernel pass 190/190 at -n 2.

  • Landing (2026-10-29). The NZ hub retargeted this PR to main on 2026-10-29, so it ran CI for the first time; stacked PRs never ran it.

🤖 Generated with Claude Code

MaxGhenis and others added 22 commits October 24, 2026 02:38
G8-r2a. Adds the projected_period_average_ratio@1 factor operation
(reference_factors.py) and wires its selected factors into the shared
reference-profile resolver, composition preflight and target compilation.

- Operands are the authenticated Ledger JSON tokens (LexicalFloat keeps the
  token on a slotted float subclass; integers stay int). Factors are exact
  rationals with a certified root enclosure, rounded once (half-even) to six
  decimals and carried as p/10**6.
- reference_aging.json declares the six operational factors (IRD TY2024
  low/central/high 1.115056/1.117109/1.119158; P1 1.026690/1.028580/1.030467)
  and the 1.086069 diagnostic, each with a receipt bound to the seven-field
  Chronicle feed identity. Factors need re-declaration after the September
  QES observation (validity ends 2026-11-03).
- The resolver takes factor_context; selected factors enter a version-2
  selection identity, money companions take one common linear_combination
  weight, counts are unchanged, and non-empty interval rosters refuse until
  r2b.
- Composition checks selected factors against the declared feed pin;
  compilation replays every selected factor against the loaded artifact and
  refuses a self-consistent forged factor or an unauthenticated source commit.
  reference_factors joins _COMPILE_MODULES.
- S0-S5 and V3 use the central factor, V1 low, V2 high, V4 central plus the
  small-household residual. All 474 IRD rows stay parked on B4 measurement.

Tests: 24 engine-free files, 2,832 passed and 5 skipped (UK feed not
configured), one file per run. Eleven mutants killed, each by a named test.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes the #1382 review (Sol r1) and the UK hub's loader notes.

- Factor application: each transformed row's role (money or count) now
  comes from the selected profile's companion roster, not the rehashable
  action label. Money rows must carry exactly the verified singleton
  linear_combination; count rows keep their original operation, operands
  and kind. Both reviewer tampering cases refuse at the resolver and at
  compilation.
- Lexical floats: load_ledger_consumer_artifact(lexical_floats=False) by
  default returns builtin floats again. Only compile replay with selected
  factors and the NZ ageing generator opt in; a static test pins those
  call sites.
- _strict_scalar_equal treats LexicalFloat as float; bool, int and float
  stay distinct.
- Oracle: exact roots return a zero-width enclosure and ambiguous bounds
  are refined; the (32, 1, 0, 2, 4) tie is an explicit example.
- RuleSpec scan: only the seven Chronicle identity fields are exempt.
- Factor schedules: frequency 1-12 and digits 0-18, so exact receipts stay
  serializable.

Tests: 24 engine-free files, 3,024 passed and 5 skipped (UK feed not
configured), one file per run; six new mutants killed.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1d5fff4 embedded the selected declarations and the whole base reference
document in every v2 resolution receipt, which travels in each compile and
prepare node's params: the NZ default receipt grew to 4.07 MB canonical.
Those documents were trusted only through hashes in the selection identity,
so a compact record bound the same way is as strong.

- v2 receipts carry factor_companions: per roster name, its role, factor,
  profile and source value semantics (474 names, 392 KB).
  factor_companions_sha256 and transformed_names join the v2 selection
  identity.
- Roles still come from the bound record, never the action label; money
  operands and count semantics are checked as before.
- The NZ default receipt is now 1.67 MB canonical, capped by a test at
  2.6 MB. v1 receipt bytes are unchanged and pinned.

Tests: 15 files, 2,508 passed and 5 skipped; three mutants killed.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Belgium's Sol review of 4f77038 found two latent regressions in
validate_reference_resolution: a receipt without transformed_names raised
KeyError, and an unnamed unresolved row raised KeyError where base
accepted it. transformed_names is now checked as a list of strings, and
roster-name bookkeeping runs only with factor evidence. The docstring
states the trust boundary: checks assume a trusted selection identity;
selected factors are anchored by replay against the authenticated feed.

The resolver property test suppresses only too_slow: an integer draw took
31 s under host load in review.

Tests: test_legacy_receipt_without_transformed_names_refuses_as_a_value_error
and test_unnamed_unresolved_rows_still_validate_without_factor_evidence;
each goes red (KeyError) with its fix reverted. test_reference_profiles.py
74 passed, test_transport_target_kernels.py 52, test_nz_target_references.py
50.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Factor validation compared date.today() with each declaration's validity
window. Every NZ factor declares valid_until 2026-11-03, so main's CI would
have failed for every PR from 2026-11-04. Validation is now a pure function
of declaration and feed: declared_on <= as_of and valid_from <= as_of <=
valid_until. require_current_factors(selected, *, today) has no default for
today. The transport CLI calls it once, for --resume auto|forbid, with
today's UTC date, before preparation can run CREATE. --resume require
replays a stored graph and is exempt. No kernel reads the clock.

The CLI checks every factor the default and scenario profile views
select, through compose.reference_profile_views, the walk activation now
shares, so a spec without scenarios checks its default view alone
(Belgium's review). An end-to-end test replays a warm store under
--resume require after the factor expires: rc 0, no kernel runs, and the
stored export's bytes unchanged.

From the d28817f review (findings 1-4): resolution transformations must
map names to objects, resolved and factor rows need nonempty names, and a
v2 unresolved duplicate companion refuses. Unresolved unnamed v1 rows still
validate. The expired-factor mutation in test_reference_factors now expires
before its as_of, so the retained as_of check refuses it.

Tests (hub, one file per process, normal priority): 15 files, 2,077
passed and 5 skipped (the UK feed cases). Clock runs at 2026-11-05:
test_nz_reference_aging 18 and test_reference_profiles 97 passed. Whole
CLI test file with the CLI clock at 2100-01-01: 163 passed. Mutants
killed, 8 of 8: the clock back in validation, the currency check on
replay, no currency check, run entry walking other views than
activation, the local date, and review mutants M3, M6 and M7. Receipts,
effective intervals and factor contexts are byte-identical to d28817f
(8 payloads, re-checked on this tree).

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a shared reference_intervals module: per-table declared semantics
(band closure, nil band, negative-income policy) with no defaults, exact
rational cuts on signed integer-cent inputs, classification, scaling and
diagnostics. Interval tables scale once with their companions' selected
factor, keeping fractional-cent cuts exact.

Bind interval outputs as their own kind in calibration_columns, beside
predicate outputs, and leave roster preparation untouched. Preparation
receives the exact effective interval companion, recomputes indicators
from each variant's cuts and records input fingerprints; the problem
kernel re-derives them and adds fixed-weight boundary diagnostics to its
metadata, never to targets.

Declare NZ's 237 national and 183 age bands, checked against every feed
label, and scale the national table in all three ageing profiles. All 474
IRD and 235 P1 rows stay parked. fix3's expiry test now passes the
interval document NZ's profiles require.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address the r2b review (GPT-6.1 Sol, 75c2768).

- Remove the validator guard that refused positive_band_closure "left"
  with a nil band and a zero first lower cut. Zero is nil under either
  closure (_position checks nil before any cut), so the first positive
  band is 0 < z < upper and nothing belongs to two bands. The refusal
  forced a one-cent stand-in cut, which moves membership after scaling:
  at factor two, one cent fell below the first cut.
- Replace the refusal test with acceptance tests: classification at the
  unscaled and twice-scaled cuts, and boundary diagnostics with zero in
  nil under the declared and alternative closures. Drop the two first=1
  workarounds, and let the Hypothesis table generator draw the
  combination.
- Give the composed-graph interval config's Owned output its required
  dtype; without it the whole test file failed collection.
- Correct the changelog's refusal sentence.

Verified: test_reference_intervals 107, test_nz_reference_intervals 14,
test_transport_target_kernels 67 and test_transport_composed_graph 166
passed. Restoring the guard, and checking nil after the cuts, each turn
the three new tests red with the control green. All 12 r2b mutants were
killed with the dtype fix in place; left_closed_cut and default_closure,
whose test file this changes, were rerun after it and still kill. ruff,
format and git diff --check are clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a pure money_normalization module and a money.normalize@1 transport
kernel. A declaration names a cited decimal-text or feed-replayed FX
rate, its quote direction, half-even rounding, the donor money period,
and per-family factors or reasoned identities. Both rounding stages and
the receipt sums use exact fractions; non-finite inputs refuse and every
integer companion is checked against int64 bounds. The kernel replays
its factors against the authenticated feed and refuses a donor period
that differs from the one CREATE records.

CREATE records the donor H5's _time_period as donor_time_period in its
metadata and receipt in every country. DonorBank reads it and refuses a
value that is not a one-row integer series.

Producer checks are opt-in. With a money_normalization resource,
activation names every consumed money column without exactly one
producer, and compose refuses a second normalization along structural
population inheritance after each graph pass of the ancestry guard. A
test checks that, for a spec without the resource, composition and
activation are byte-identical with the hooks removed.
reference_factors gains the additive money_normalization_input purpose.

NZ declares generated calendar-2024 donor factors with the existing
factors' validity and explicit per-profile selection. No NZ graph node
reads the declaration yet (G8-w3). Ageing non-wage money by AWE growth
is disclosed as provisional.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hub's validation after rebasing onto #1394 found three failures the
lane's pre-rebase runs could not see.

- money_normalization.json cited local files by line number, which
  test_nz_spec_package refuses. The donor-period evidence now names the
  symbols (the PERIOD constant, _dataset_from_frame,
  PolicyEngineUSEngine._write_and_verify). A new test parses both files
  and checks each cited fact against the code. Mutants that change
  PERIOD, pass a literal time_period, or drop the int cast each fail it.
- The rulespec-pin plant edited currency_bridge.json's source record,
  which w2a moved into money_normalization.json. The plant now edits the
  IRS rate's citation there, so the planted revision is caught again.
- The v2 receipt digests were produced before #1394 added the interval
  document. The test now passes reference_intervals.json and pins the
  digests that #1394's 75c2768 produces; they are unchanged at f700082
  and at w2a.

The country-spec golden was regenerated for the new
money_normalization.json bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the #1395 round-1 review (findings m1, m4 and n4; m2 in part).

- A money declaration now names the target_period its factors age money to,
  as it already names the donor period. check_money_factor refuses a factor
  whose target_period differs, so no two families can age money to
  different periods. The declaration's sha256 already binds it into the
  kernel receipt. NZ declares 2027 (tax year 2026-27), the target of its
  donor factors and of every reference-profile factor.
- A concept dropped in transport can't be listed as normalized; it used to
  validate and only fail at run time. Quantile-mapped and pending rows may
  still name one.
- NZ's scenarios' default aging_index must be the money declaration's
  central profile, and the two must list the same profiles.
- The mixed-income disclosure now says the projection after June 2026 uses
  Treasury PREFU 2026 AWE growth (2.7%, 0.5 points lower and higher in the
  low and high profiles), not only Stats NZ QES.

The general two-step rounding property is renamed for what it asserts: at
most floor(1 + f/2) cents, so one cent for every factor below 2.

In-memory mutants that drop each new guard (the DROP refusal, the
target-period check, the target-period validation) each fail their test.
The NZ country-spec golden is regenerated for the new declaration bytes.

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

prepare_transport_build now runs the run-entry currency check itself, after it
validates the resume policy and before activation. Every library caller that
starts a new run (auto or forbid) therefore refuses an expired selected
factor, not only the CLI; require still reaches stored work. main drops its
own call, and execute_transport_graph stays clock-free: its docstring says a
caller that composes a graph itself owns the check. New tests cover library
callers under auto and forbid for the default and scenario views, the require
exemption, and resume validation before the currency check.

The warm-store replay test now compiles a factor-bound target surface. A
test-local targets.compile@1 node on nz.open reads the factor references and
the pinned factor feed, and is a CLI terminal endpoint, so the warm run
executes it. The test spies the factor replay and checks it ran once against
the facts of the pinned feed. After the factor expires, require runs no
kernel or replay, keeps the export bytes and serves the compiler from the
store under the same key; auto refuses on the factor before preparation work.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A receipt contract that declares contract_version takes the v2 validator and
assignment; one without it runs the v1 bodies unchanged. v2 contracts are
closed at contract, profile, program and stratum level, and add:
- strata whose filters must be disjoint on the data, each with its own rate
  and the rest of the population not assigned;
- an assignment profile chosen by a knob, giving the draw key suffix and the
  exclusion order;
- exclude_columns with v1's semantics (distinct, non-null booleans), applied
  after the judgments and payment screen and before group exclusions;
- per-group overlap and weighted Hall feasibility diagnostics, which never
  refuse; a target above its remaining eligible mass still refuses, with the
  audit attached;
- per stratum, the realized weighted count beside the target, and a check
  that rate times eligible mass reproduces the target to a relative 1e-12.
The four reserved unit fields admit only "person". Judgments and exclusions
read the entitlement_entity table, payments the payee table and draws the
draw_unit table; strata resolve, and flags are written, at projection.
Activation checks that the default profile is the central knob's value and
that each stratum filter names a calibration_bindings predicate. A v1 node
still refuses v2's optional parameters as unknown.

Predicate validation and evaluation move to calibration_predicates, shared by
calibration preparation and receipt strata, so strata validate their raw
inputs rather than trust prepared columns.

Reference selection gains opt-in per-resource exemptions (schema_version 1,
each naming its activation status and a reason). An exempted placeholder no
longer counts as an activation gap. Activation refuses an exempt reference
that some node selects; activation and compilation both refuse an unknown or
active exemption and a stale declared status, checked against the full
effective document before any row is filtered, and a compiled surface
refuses to carry an exempt reference. Compose passes a selection only to
target compilers whose resource has exemptions, so specs without one compile
as before.

The new modules join the implementation hashes of takeup.assign,
calibration.prepare and the target compilers, so their cache keys change for
every country.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1375 merged main (#1393), which added atomic_geography_support.json and
changed country_package.json, so the NZ golden conflicted. The golden was
regenerated from the merged tree with the test's own summary function. Keys
only one side changed keep that side's values; the fingerprint, the
resource list and the country_package.json hash, which both sides changed,
are the merged tree's.

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

Three tests in test_transport_reference_selection.py failed at 2f220f7 for
fixture reasons, not because of the code they test:

- test_direct_comparison_request_for_exempt_row_refuses gave takeup.compare@1
  `entities=("person",)`. entities_param (since G5b) needs the person entity
  and at least one group entity, so the kernel refused on the parameter shape
  before it reached the exempt-reference check. It now passes
  ("person", "household") and reaches the refusal it asserts.
- test_legacy_selection_path_changed_mutant_frozen_surface_params_and_keys[xx]
  and [be] built a CREATE node with no sources, which Node refuses
  ("a CREATE node needs sources"). The fixture graph now declares one source,
  `seed`, and the CREATE node reads it. The node keys compared are those of
  the target node, which both sides build the same way.

The file was owed by the build lane and not run before; draft CI doesn't run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1382's fix moved the factor-currency check into prepare_transport_build in
build/transport/cli.py, after the resume check and before activation
validation. This carries that change, and #1375's merge of main beneath it,
up the NZ stack. The merged cli.py is c4ddcb4's blob; no #1394 commit
touches it.

The NZ golden conflicted: both sides changed the fingerprint, the resource
list and the country_package.json hash. It was regenerated from the merged
tree with the test's own summary function. Keys only one side changed keep
that side's values; the three both-changed keys are the merged tree's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zero is nil only when the table declares a nil band (_position checks
nil_present first). Without one, a zero record with a zero first cut falls
below the first cut under right closure and into the first band under left
closure, so the unqualified sentence overstated it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1394 now carries #1382's head (c4ddcb4), whose fix moved the
factor-currency check into prepare_transport_build in build/transport/cli.py.
The merged cli.py is c4ddcb4's blob; no #1395 commit touches it.

The NZ golden conflicted: both sides changed the fingerprint, the resource
list and the country_package.json hash. It was regenerated from the merged
tree with the test's own summary function. Keys only one side changed keep
that side's values; the three both-changed keys are the merged tree's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The module docstring said every applied factor ages money to the declared
target period. That holds for money.normalize@1, which checks each selected
factor through money_factor_inputs; a direct caller of
normalize_money_tables supplies its factors unchecked. Say so.

The AST test's comment said it checks each cited fact. It checks the cited
constant and call expressions, so an in-memory reassignment of the writer's
period would still pass. Say what it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1395 now carries #1382's head (c4ddcb4), whose fix moved the
factor-currency check into prepare_transport_build in build/transport/cli.py,
and #1395's two wording fixes. The merged cli.py is c4ddcb4's blob; no #1397
commit touches it. The merge is clean, and the NZ golden it takes from #1395
regenerates byte for byte from the merged tree.

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

The first review of this PR (Opus 5.5 at d8425ae, APPROVE_WITH_NITS) found
nothing blocking. This commit fixes its code findings.

The weighted Hall diagnostic checked every subset of a group's demands, so
its cost doubled with each demand. The review measured 5.2 s for 16 demands
on 20,000 people and estimated hours for 24, all before any assignment ran.
It now computes an exact max-flow over the same network (Edmonds-Karp in
rationals). Each demand's capacity is (1 - r) times its target, which is the
old rule: a subset violates when its supply is below (1 - r) times its
target. When the plan is infeasible, it reports one set, the sink side of a
minimum cut, which is the most deficient set, with the same record keys. A
Hypothesis test checks feasibility and the reported deficiency against an
exact brute force over up to 8 demands, a test pins the tolerance boundary,
and a 24-demand case on 20,000 people runs with subset enumeration patched
to raise (0.11 s in the lane's run).

A failed expected-mass check now refuses with the audit attached, as a
target above the remaining mass already did; before, the audit recorded
passed: false and the assignment completed. v2 also refuses non-finite
weights, and weight sums that overflow, with ValueError. v1 is unchanged,
including its existing OverflowError on an overflowing sum. One limit
remains, as before: a reported cut whose total is above the float range
cannot be written as a float.

Tests now cover the two surface-decode guards (an active reference marked
exempt, and an exempt reference inside a compiled surface) and the
resolution filter for an exempt row transformed by a profile dimension.

decode_target_surface reads reference_profiles and reference_selection, but
implementation hashes are not transitive, and five kernels that decode the
surface did not hash either module: takeup.assign@1, targets.problem@1, the
gate battery, diagnostics.calibration and geography support. They now do,
and a test checks all ten surface consumers. Their cache keys move for
every country. A search found no golden or test that pins them.

Activation now checks each calibration input's domain, through the same
function preparation uses, when v2 strata are declared, and contract
validation refuses cyclic exclusion groups; both refused only at run time
before. The test fixture's unsupported "integer" domain is now
nonnegative_integer.

The v2 takeup.assign node's profile_selection now carries only the
assignment knob, so an unrelated knob no longer changes its key. An
assignment knob missing from the resolved knobs refuses with ValueError.

The draw-key property test now draws adversarial delimiters, and the
exclusion property test is derandomized with a guaranteed overlap, so each
fails under its mutant on every run.

In the v2 pure function, an output named like a structural id column
refuses, as the kernel's column check does; supplied entity tables must
match the person argument's length and ids, and a missing one refuses;
NumPy real targets are accepted and booleans still refuse. The v2 kernel
refuses calibration_bindings without its sha256, and the reverse.

v1 still matches the base commit on 1,500 mixed and 3,000 valid generated
contracts, and calibration.prepare on 300 cases. Belgium OK'd the src hunks
(sha256 0a6d4d16) before this commit.

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-10-26 a review run of test_nz_spec_package.py failed one test with
Hypothesis's too_slow health check: drawing one of two strings took 36.9 s
while the shared host's load average was about 300. The strategy is cheap,
so the failure measured the host, not the test. The hold-out property test
now suppresses too_slow, keeps its 300 examples and has no deadline, as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Belgium's follow-up to review r1 (its f10-followup.diff, committed unchanged;
sha256 of the diff de15bc7a87dc17f9a7f15b8e0e072006cb20808f89e2cc9e63a64252d3817559).

At 84c2082 the entity-table check compared each raw unit table's
`{entity}_id` with `person["{entity}_id"]`, before `receipt_dispatch_tables`.
That broke Belgium's 10/24 widen-only condition for its declared-unit
extension: with `payee` widened to "household", a person-aligned household view
raised `KeyError: 'household_id'`, and a native household table broadcast at the
seam was refused as misaligned. Belgium's independent Sol review
(subfleet run 20261026-052510-nz1397-r1-review) found it, and Belgium
reproduced both cases.

`_unit_views_align` now checks what the [BE] dispatch seam returns for each row.
It matches each unit's ids through the person table's membership column
(`person_id` for person, `person_{entity}_id` otherwise). Every unit field is
still held at "person" today, so the check still compares the person table with
itself; only the widened case changes.

Test: test_declared_unit_extension_only_widens_values[view, seam] widens
`payee` to household both ways and requires the assignment to go through.

Hub run on this tree, one file per process at -n 0: test_transport_receipt_v2.py
99 passed (the 2 new cases among them; 2 passed verbose on their own) and
test_transport_receipt_v2_kernel.py 14 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 26, 2026 13:54
…gaps

Answers review r2 (Claude Opus 5.5, REQUEST_CHANGES at 84c2082) F-A, F-C
and F-D, Belgium's items (a), (b) and (c) on r1, and reviews r3a and r3b
(GPT-6.1 Sol, both REQUEST_CHANGES on this change's first draft). Review r4
(GPT-6.1 Sol) approved this tree with one test nit, added below.

F-A, Belgium (c): hall_diagnostics was unbounded in practice. With
independent random eligibility masks, almost every person has a pattern of
their own, and Edmonds-Karp over Fractions ran for minutes. The 0.11 s figure
in 5108046's message came from a fixture with 6 distinct patterns. Now:
- each weight is a float, so it is an exact multiple of one power-of-two
  denominator; each pattern's weights are summed as integers over it, 65,536
  people at a time (_HALL_WEIGHT_CHUNK), not one Fraction add per person;
- people eligible for no demand join no supply, and their weights are never
  read, as at 84c2082;
- every capacity is an integer over the lcm of that denominator and the scaled
  targets' denominators (a Fraction target need not be dyadic);
- Dinic (BFS levels, then a blocking flow with current-arc pointers) replaces
  Edmonds-Karp. A shortest augmenting path alternates supplies with distinct
  demands, so its length is 2j + 1 for j distinct demands, and at most
  len(demands) phases route flow;
- targets are normalised with Fraction(int(n), int(d)). At 84c2082 a NumPy
  integer target kept NumPy parts inside the Fraction, and their products
  overflowed or wrapped. Production passes float(value), so only direct calls
  reached this.
When the flow falls short, no supply-to-demand arc is saturated, since each
can carry the whole requirement. The demands the source cannot reach in the
final residual graph then maximise (1 - r) * T - N; that set is the same for
every maximum flow and is the union of every maximising set, so the reported
set does not depend on the algorithm. One-dimensional weights whose length
differs from the masks still raise ValueError, now with its own message;
0-D and 2-D weights, which raised TypeError at 84c2082, raise that
ValueError too. A NaN or infinite weight on an eligible person, or a NaN or
infinite target, raises the same exception and message as before.

Timings, the r2 reviewer's script (time_hall_worst.py, seed 0, c = 0.066,
independent masks), old and new on the hub (18 CPUs, shared, load1 75-93):

  people x demands (distinct patterns)   84c2082          this commit
  20,000 x 10 (1,024)                    0.64 s             0.016 s
  20,000 x 12 (4,066)                    16.5 s             0.027 s
  4,000 x 24 (4,000)                     44.3 s             0.029 s
  20,000 x 24 (19,987)                   killed at 300 s    0.17 s
  100,000 x 4 (16), Belgium's shape      0.10 s             0.044 s

Five adversarial families (banded, chain, nested, strata, skewed), 8/16/24
demands, seven shares around the threshold: worst 0.038 s at 20,000 people
and 0.157 s at 100,000, never more than 2 routing phases, 159 MB peak RSS.
In their sandboxes, reviews r3a and r3b measured 2.72 s and 3.05 s for
20,000 x 24; under macOS background QoS the hub takes 2.35 s.

Differential and exhaustive oracle (diff_hall_v2.py): against 84c2082's
hall_diagnostics and an exact brute force over every demand subset, 25,000
small cases and 1,200 medium cases (50-300 people, 6-11 demands) give 0
oracle failures. The only disagreements with 84c2082 (1,643) are its own
NumPy-integer overflows.

F-C, Belgium (b): compose._node read assignment_knob from a node's own v2
receipt contract without validating it, so a document lacking the field raised
a bare KeyError. It now runs validate_receipt_contract on a mapping v2
document first and re-raises as ValueError("Node '<id>' receipt_contract:
..."). v1 and valid v2 documents take the same path as before. A json
resource selection is still refused unless it is an object (a selected null
reads as not activated), as at the base. A literal or already-resolved
parameter holding non-object JSON now reaches the kernel's run-time refusal
("must encode a JSON object"), as on the base 4f333f2; from 2f220f7 to
97c08e0, _node raised AttributeError on it.

F-D, Belgium (a): validate_calibration_inputs raised AttributeError when
input_domains[entity] was not a mapping. It now reads as no domain and raises
the existing "Calibration input {entity}.{name} needs a domain." at the same
point.

Tests:
- the Hall property test adds subnormal, smallest-normal, 1e-300, 1/3 and
  1e300 capacities, and checks the reported set is the union of every
  maximising set;
- test_hall_distinct_patterns_at_scale[0.066-True, 1.0-False]: 20,000 people,
  24 demands, independent masks;
- test_hall_targets_of_every_real_type_agree: int, float, Fraction, np.int64,
  np.int32 and np.float64 targets;
- test_hall_rational_targets_are_exact: a non-dyadic boundary target, and
  capacities 4/3 and 1/5 that need the lcm;
- test_hall_refuses_weights_that_do_not_align_with_masks;
- review r3a's witnesses for four wrong implementations that passed every
  earlier Hall test:
  test_hall_keeps_targets_exact_below_float_resolution (targets
  1/s +- 2**-200 share one float),
  test_hall_reroutes_flow_along_reverse_arcs, and
  test_hall_sums_weights_across_chunks[1.25-True, 2.0-False] (65,537 people,
  one weight in each chunk);
- test_hall_ignores_weights_of_people_eligible_for_nothing[nan, inf, -inf];
- review r4's witness, test_hall_keeps_pattern_indices_aligned_across_chunks:
  two patterns across the chunk boundary, so pattern labels that restart at
  each chunk now fail;
- test_v2_activation_lists_non_mapping_entity_domains[3],
  test_calibration_inputs_refuse_non_mapping_entity_domains[2] and
  test_v2_assignment_node_validates_its_own_receipt_contract[valid_contract,
  literal_not_an_object, drop_assignment_knob, unknown_field].

Hub run on this tree, one file per process at -n 0: test_transport_receipt_v2
117, receipt_v2_kernel 14, reference_selection 60, calibration_columns 91,
column_kernels 30, takeup_kernels 38, target_kernels 67, composed_graph 172,
transport_graph 15, reference_profiles 123, nz_spec_package 1,201,
country_spec 136, spec_engine_country_bundles 21, uk_target_references 30
(5 skipped), money_kernels 40, gate_kernels 31, terminal_kernels 18,
gate_battery_contract_pins 20, gate_battery_replay 2, nz_joint_geography 22
(11 skipped) and codecs_geography 22 pass. Sixteen mutants each fail a test
that guards their line, and each file is restored byte for byte: lcm to max
(on a value case as well as the empty one), no int() target normalisation,
the reported set flipped, the bottleneck as max, chunk scaling dropped, the
eligible-mass complement, the alignment check, targets rounded through float,
no reverse-arc updates, only the first chunk, a per-chunk denominator,
ineligible people's weights read, the (a) guard, the (b) validation and the
(b) isinstance guard. Review r4's pattern-index mutant fails the new witness.

Record corrections from Belgium and reviews r2 and r3: 18 of 31 kernel
implementation hashes moved at 5108046, not the five named there; nothing
pins them. 97c08e0's message said only the widened case changes; two more
do. A person table without person_id now raises ValueError in place of
KeyError, and an explicit tables={"person": None} raises TypeError in place of
ValueError; valid tables are accepted and refused as before.
This commit moves 2 of the 31 registered kernel implementation hashes:
takeup.assign@1 and calibration.prepare@1 (Belgium's kernel_hashes.py,
97c08e0 against this tree).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis changed the base branch from nzhub/g8w2a-money to main October 29, 2026 12:12
MaxGhenis and others added 2 commits October 29, 2026 08:12
Two tests imported microcosm_build.reference_factors as a top-level
module. The hub's local runs put test_support on PYTHONPATH, so they
passed there, but CI imports it as test_support.microcosm_build and
stopped engine-free at collection once this stack first ran against
main. Every other test in the repo uses the test_support prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 7 commits October 29, 2026 08:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`resolve_dotted_symbol` (shared, from #1407) imports the longest module
prefix of a dotted name and walks the rest with `getattr`. For
`pkg.mod.Class.method`, `import_module` fails one level up: it reports
`pkg.mod.Class` as the missing module (`'pkg.mod' is not a package`).
The resolver only skipped an import error that named the exact prefix it
tried, so it re-raised instead of falling back to `pkg.mod`.

This PR's `nz/money_normalization.json` cites
`microcosm.frame.adapters.policyengine_us.PolicyEngineUSEngine._write_and_verify`,
the first citation in any package that reaches through a class.
`test_every_dotted_microcosm_symbol_resolves[nz]` fails on it: in CI on
#1397, and locally on this branch with the old check restored.

The resolver now also skips an error that names a parent of the prefix,
as the NZ package test's own resolver does, and still re-raises a missing
dependency of a module that exists. A citation whose only importable
prefix is the top-level package still raises ModuleNotFoundError, as the
existing `microcosm.no_such_shard...` case requires. A new test resolves an
enum member and a method through their classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings f09ad72 into this branch: resolve_dotted_symbol now falls back past a parent that is a class, so this PR's PolicyEngineUSEngine._write_and_verify citation (inherited from #1395) resolves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 12 commits October 29, 2026 09:55
1d5fff4 made `ledger_targets` import `LexicalFloat` from `ledger_artifact`,
and `ledger_targets` is a UK target compile module. CI's
`test_every_direct_import_of_a_listed_module_is_listed_or_excluded` then
failed: every direct import of a listed module must be listed or excluded.

The import feeds one type check: `_strict_scalar_equal` reads an opt-in
lexical float as `float`. Only the transport factor compiler opts in, which
`test_lexical_float_opt_in_is_limited_to_factor_consumers` pins; the UK
kernels load plain floats, so no UK value is ever a `LexicalFloat`, and the
loaded facts are node inputs hashed by content. That matches the existing
exclusions for imports that never move a value, and it keeps the UK kernels'
hashed module set as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings b36c702 into this branch: ledger_artifact joins EXCLUDED_DIRECT_IMPORTS with its reason, so test_every_direct_import_of_a_listed_module_is_listed_or_excluded passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings b36c702 (via #1394) into this branch: ledger_artifact joins EXCLUDED_DIRECT_IMPORTS with its reason, so test_every_direct_import_of_a_listed_module_is_listed_or_excluded passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings b36c702 (via #1395) into this branch: ledger_artifact joins EXCLUDED_DIRECT_IMPORTS with its reason, so test_every_direct_import_of_a_listed_module_is_listed_or_excluded passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds #1375 as 6af97bf/846b48a9, so this branch had two merge bases with main (fddbc70 and 840d933). Git merges them cleanly, but GitHub marked the PR conflicting and ran no CI. This merge gives one merge base; its tree equals `git merge-tree --write-tree origin/main b36c702`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds #1375 as 6af97bf/846b48a9, so this branch had two merge bases with main (fddbc70 and 840d933). Git merges them cleanly, but GitHub marked the PR conflicting and ran no CI. This merge gives one merge base; its tree equals `git merge-tree --write-tree origin/main 6d1252a`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds #1375 as 6af97bf/846b48a9, so this branch had two merge bases with main (fddbc70 and 840d933). Git merges them cleanly, but GitHub marked the PR conflicting and ran no CI. This merge gives one merge base; its tree equals `git merge-tree --write-tree origin/main e35612f`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now holds #1375 as 6af97bf/846b48a9, so this branch had two merge bases with main (fddbc70 and 840d933). Git merges them cleanly, but GitHub marked the PR conflicting and ran no CI. This merge gives one merge base; its tree equals `git merge-tree --write-tree origin/main d1b513c`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both UK target kernels decode their ledger facts inside run, through
full_targets -> load_ledger_consumer_artifact -> _load_fact_rows. The
decoded floats feed listed compilers, but source keys hash the raw file
bytes and source_hash covers only the modules the kernels pass it, so an
edit to the decoder left both UK implementation hashes unchanged (the
delta review moved a national target by changing only the default
decimal parser). List the module instead of excluding it; its one
microcosm.build import, chronicle_epoch, stays excluded, and its reason
now names ledger_artifact's schema gate and epoch labels too.

This adds no cache invalidation of its own: this branch already edits
ledger_targets.py and ledger_artifact.py, so it re-keys every UK target
node either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings 7cd1d26 (via #1382) into this branch: ledger_artifact joins UK_TARGET_COMPILE_MODULE_NAMES instead of EXCLUDED_DIRECT_IMPORTS, so an edit to the fact decoder re-keys both UK target kernels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings 7cd1d26 (via #1394) into this branch: ledger_artifact joins UK_TARGET_COMPILE_MODULE_NAMES instead of EXCLUDED_DIRECT_IMPORTS, so an edit to the fact decoder re-keys both UK target kernels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings 7cd1d26 (via #1395) into this branch: ledger_artifact joins UK_TARGET_COMPILE_MODULE_NAMES instead of EXCLUDED_DIRECT_IMPORTS, so an edit to the fact decoder re-keys both UK target kernels.

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

Copy link
Copy Markdown
Contributor Author

Merged by the NZ hub (session local_e407a527) at d67f9c1 with --merge. Gates: gh pr checks exit 0; MERGEABLE; not draft; no CHANGES_REQUESTED review. Reviewed head: d67f9c1. Independent delta review of the ledger_artifact listing across all five stack heads: APPROVE_WITH_NITS (Opus 5.5 in session), jobs/review-microcosm-fix5list/out-opus.md. BE re-clear clean at this head (jobs/be-reclear-1029e).

MaxGhenis added a commit that referenced this pull request Oct 29, 2026
#1395 (money normalization) and this branch each add one resource to
nz/country_package.json, so the NZ golden spec summary conflicted. Git
merged country_package.json and the two shared test files cleanly: the
package lists both resources in sorted order, and each test file's diff
against main equals this branch's own change.

tests/golden/nz_country_spec.json is regenerated from the merged spec
with the golden test's own _loaded_spec_summary and canonical_json_bytes.
Against main it changes exactly the resource hashes this branch changes
(calibration_bindings, family_reconciliation, reference_inventory,
reference_profiles, target_references), the resource list, the
fingerprint, and country_package.json's hash, the one merged file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 29, 2026
Merging main (#1395 and #1397) into this branch moved two pinned
digests. Each test passes at main and at this branch's previous head
0a8568b, and fails only on the combination, because each pins output
over NZ data both lines touched.

test_prepare_matches_pinned_legacy_output_digest hashes
prepare_calibration_columns over nz/calibration_bindings.json, which
this branch extends; #1397 pinned it from the pre-refactor
implementation at 4f333f2. On the merged bindings the pre-refactor
implementation, main's and this head's all give 1b22f4e1, and on main's
bindings the pre-refactor implementation still gives the old pin
dca0e085, so the refactor stays byte-equivalent.

test_nz_factor_receipts_are_byte_identical_after_additive_factors pins
the v2 receipts before #1395's donor factors were added, over
target_references.json and reference_profiles.json, which this branch
changes. Its three receipts at 0a8568b (this branch's data, no donor
factors) equal the merged tree's (b0f8648d, 8989fa75, e246f615), so the
donor factors still leave them byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis deleted the nzhub/g8w1-receipts branch October 29, 2026 19:06
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