Skip to content

Add feed-derived NZ reference ageing factors (G8-r2a) - #1382

Merged
MaxGhenis merged 13 commits into
mainfrom
nzhub/g8r2a-factors
Oct 29, 2026
Merged

MaxGhenis merged 13 commits into
mainfrom
nzhub/g8r2a-factors

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Targets main. #1375 merged on 2026-10-29 at 6af97bf, and the UK hold on compile-hash changes ended at 12:00Z that day. Landing record: see Landing (2026-10-29) below.

G8-r2a: feed-derived ageing factors for the NZ reference profiles. The PR adds one factor operation, projected_period_average_ratio@1 (reference_factors.py), and wires its selected factors through the shared resolve_reference_profiles, the composition preflight and targets.compile@1 / takeup.compare@1.

Commits:

  • a30c377: the operation, the NZ declarations and the wiring.
  • 1d5fff4: fixes from review round 1 and the UK hub's notes.
  • 4f77038: compact companion records in receipts.
  • d28817f: malformed receipts fail with a validation error again (from Belgium's review), plus a trust-boundary docstring.
  • 2a64132: run entry checks factor currency, not validation (the wall-clock fix below), with the d28817f review's findings 1–4 and Belgium's two asks.
  • 495db83 (fix4): prepare_transport_build runs the currency check itself, so library callers that start a new run refuse an expired factor too; the warm-store replay test now compiles a factor-bound target surface.
  • c4ddcb4: merges Allocate Census residuals through shared reference profiles #1375's head (840d933) and regenerates the NZ country-spec golden.
  • c28c0f7: merges main (fddbc70) through update-branch.
  • 64daa4f and b157bb9: import the reference-factor fixtures through test_support, as CI does, and sort them as ruff does.

What changes

  • Exact operands. load_ledger_consumer_artifact(..., lexical_floats=False) still returns builtin floats by default. Two callers opt in, and a static test pins both call sites:

    • compile replay, when selected factors exist;
    • the NZ ageing generator.

    With lexical_floats=True, floats load as LexicalFloat, a slotted float subclass that keeps its JSON token. The factor operation proves each token reproduces the float before using the exact decimal. A plain-float operand refuses.

  • Exact factors. Each factor is the ratio of projected period averages in exact rationals. A certified enclosure brackets the root; exact roots get a zero-width enclosure. The factor is rounded once (half-even) to six decimals and carried as p/10**6. Schedules are bounded (frequency 1–12, digits 0–18), so exact receipts stay serializable.

  • NZ declarations (nz/reference_aging.json, generated by tools/generate_nz_reference_aging.py):

    Factor Low Central High
    IRD TY2024 → TY2027 1.115056 1.117109 1.119158
    P1 TY2026 → TY2027 1.026690 1.028580 1.030467
    • The observed TY2026/TY2024 ratio, 1.086069, is a diagnostic only and never enters a profile.
    • Treasury's year ending 30 June 2027 maps to Chronicle period 2026; selecting 2027 refuses.
    • Each receipt binds the seven-field Chronicle feed identity: source commit, facts and manifest hashes, artifact schema version, consumer fact schema versions and hash, and row count. Prose is excluded.
    • Declarations are valid through 2026-11-03 (valid_until). A September-quarter observation re-anchors the projection, and the old factor refuses until it is re-declared.
    • Validation never reads the clock. It checks declared_on <= as_of and valid_from <= as_of <= valid_until, so the same inputs validate the same way on any date. Whether a declaration is still current is checked once, at run entry (see Transport).
  • Resolver. resolve_reference_profiles(..., factor_context=None).

    • Selected factors produce a version-2 receipt. Receipts without factors stay version 1, byte for byte, pinned by digest.
    • v2 receipts carry factor_companions: per roster name, its role (money or count), factor, profile and source value semantics. Its hash and transformed_names join the selection identity.
    • Each money companion must carry exactly one linear_combination weight equal to the verified factor. Count companions keep their original operation and operands.
    • Roles come from the bound record, never from the action label.
    • These checks assume a trusted selection identity, as when composition resolves a fresh receipt. A forger who also replaces the selection identity is outside this layer. Selected factors are anchored separately, by replay against the authenticated feed.
    • The NZ default receipt is 1.67 MB canonical.
    • The 237 pairs (rows 8–244) are explicit rosters. A non-empty interval_table_ids refuses until r2b.
  • Transport.

    • Composition checks selected factors against the chronicle_feed.json pin, without feed I/O.
    • Activation resolves each distinct scenario selection once.
    • Compilation replays every selected factor against the loaded artifact and compares the full receipt. Without a pin, the declared commit must be in the hash-bound manifest.
    • reference_factors joins _COMPILE_MODULES.
    • Run entry. For --resume auto|forbid, the CLI checks every factor that the default and scenario profile views select against today's UTC date, before preparation can run CREATE. require_current_factors(selected, *, today) has no default for today. The views come from compose.reference_profile_views, the walk activation also uses, so a spec without scenarios checks its default view alone. --resume require replays a stored graph and is exempt. No kernel reads the clock.
  • Scenarios.

  • Still parked. All 474 IRD rows stay parked: ageing is satisfied, but B4 measurement and coverage closure are outstanding. No donor normalization is declared.

Shared-code impact (UK, Belgium)

  • Loader: the default path is unchanged (builtin float). UK and Belgian code never opt in. The NZ opt-in load adds 40,247 bytes, tokens included, across 525 floats, with canonical bytes identical.
  • ledger_targets._strict_scalar_equal: LexicalFloat compares as float. bool, int and float stay distinct.
  • Compile hashes: _COMPILE_MODULES gains reference_factors, so compile-node implementation hashes move for every country. This is a cache bust; it is the reason for the 10/29 hold.
  • UK target kernels: UK_TARGET_COMPILE_MODULE_NAMES gains ledger_artifact (7cd1d26; see Landing), and this PR edits ledger_targets.py, already listed. Both UK target kernels' implementation hashes move, which re-keys every UK target node once.
  • Transport hunks, cumulative against 10ee4a5:
    • compose.py: 71–74, 392–393, 400–402, 404–437 and 579.
    • cli.py: 17–20, 31, 40, 69, 644–665, 667–671 and 692.
    • target_kernels.py: 56, 75, 77, 280, 296, 402–407, 409–432 and 524.

Known edges (no current path)

  • These apply only to opt-in loads:
    • yaml.safe_dump rejects LexicalFloat (Belgium hub tested this).
    • marshal rejects it (ValueError), and so does yaml.safe_dump (RepresenterError); both were reproduced by the d28817f review. orjson rejects it too, as the UK hub reported; that one was not reproduced, because orjson is not installed in the review environment.
    • Its pickle bytes differ from a plain float's.
  • Source text: the reviews reproduced all seven values independently from the pinned Chronicle feed. The original QES, PREFU and Treasury workbooks are not in this checkout, so the feed's operands and metadata are verified, but the source documents themselves are not.
  • Factor views and compiled outputs ignore pin prose. Compile-node params still carry the full pin and re-key on prose edits; that behaviour predates this PR.

Invariants and checks

Test IDs are relative to packages/microcosm-build/tests/engine_free/shared/.

  1. Lexical exactness and opt-in. The default load gives builtin floats; the opt-in load gives tokens; canonical bytes are identical both ways. Tests: test_ledger_artifact.py::test_default_float_loading_returns_exact_builtin_float, ::test_real_nz_lexical_opt_in_preserves_canonical_bytes and ::test_lexical_float_opt_in_is_limited_to_factor_consumers; test_transport_target_kernels.py::test_no_factor_compilation_is_identical_with_or_without_lexical_loading.
  2. Single certified rounding, including exact-root ties. Tests: test_reference_factors.py::test_certified_rounding_and_root_bounds_match_independent_fraction_oracle (with the explicit (32, 1, 0, 2, 4) example) and ::test_independent_oracle_refines_ambiguous_rounding.
  3. Exact NZ values. All six factors and the diagnostic match; 1.030466 refuses; the generator reproduces every receipt. Tests: test_nz_reference_aging.py::test_all_six_factors_and_observed_diagnostic_are_exact and ::test_generated_aging_resource_reproduces_pinned_feed_and_all_receipts.
  4. Re-anchoring. Tests: test_reference_factors.py::test_observed_preferred_requires_redeclaration_and_reanchors_projection and test_nz_reference_aging.py::test_september_observation_reanchors_and_requires_reviewed_redeclaration.
  5. Application bound to role. Rehashed label and companion forgeries refuse at the resolver and at compile, while the selection identity is unchanged. Tests: test_reference_profiles.py::test_rehashed_factor_application_cannot_bypass_companion_role, ::test_rehashed_forged_companion_role_requires_the_bound_selection_identity, ::test_rehashed_action_label_cannot_change_money_into_a_count and ::test_count_preserves_original_weighted_operation_and_refuses_changed_weight; test_transport_target_kernels.py::test_compile_replay_refuses_rehashed_factor_application_tampering.
  6. Money scaled once, counts invariant. Tests: test_reference_profiles.py::test_factor_scales_money_once_keeps_counts_and_measurement_prerequisite and test_nz_target_references.py::test_resolved_money_uses_the_verified_factor_once_and_count_values_are_unchanged.
  7. Authenticated replay. Forged self-consistent factors, changed identities and different feeds refuse. Tests: the factor-replay and pin tests in test_transport_target_kernels.py.
  8. Strict equality. Tests: test_ledger_targets.py::test_strict_scalar_equal_treats_lexical_floats_as_floats and ::test_float_dimension_pin_compiles_loaded_fact.
  9. Receipt size and v1 stability. Tests: test_reference_profiles.py, covering the compact shape under 2.6 MB and the pinned v1 digest.
  10. Parking and locality. Tests: test_nz_target_references.py::test_ageing_profiles_have_237_explicit_companions_and_keep_b4_parked and ::test_v4_changes_only_the_residual_knob_and_prior_variants_keep_their_meaning; test_reference_profiles.py::test_factor_evidence_and_unselected_profile_locality.
  11. RuleSpec pin scan. Only the seven Chronicle identity fields are exempt; extra fields are still scanned. Test: test_nz_spec_package.py::TestRulespecPin::test_chronicle_identity_scope_preserves_rulespec_mentions.
  12. Schedule bounds. Test: test_reference_factors.py::test_unsupported_exact_receipt_schedule_refuses_at_declaration.
  13. Validation is clock-free. The same declarations and feed validate identically on any date. Tests: test_nz_reference_aging.py::test_packaged_factor_context_validation_ignores_the_wall_clock and test_reference_profiles.py::test_nz_default_factor_resolution_validates_after_declaration_expiry; the hub reruns test_nz_reference_aging.py and test_reference_profiles.py with the clock at 2026-11-05, and the whole test_transport_composed_graph.py with the CLI clock at 2100-01-01.
  14. New runs refuse stale factors; replay never does. Currency boundaries are inclusive. Tests: test_nz_reference_aging.py::test_factor_currency_uses_inclusive_declared_boundaries, test_reference_factors.py::test_currency_check_without_selected_factors_is_a_noop, and in test_transport_composed_graph.py: ::test_cli_main_refuses_expired_factors_before_create, ::test_cli_factor_currency_decision_allows_historical_replay and ::test_cli_require_replays_a_warm_store_after_selected_factors_expire (a warm store replays at 2100 with no kernel call, while auto refuses and writes nothing).
  15. One date, one walk. The run date is the UTC date, and run entry checks exactly the views activation resolves, refusing the same malformed scenarios with the same error. Tests: test_transport_composed_graph.py::test_cli_run_date_is_the_utc_date and ::test_cli_currency_check_walks_the_views_activation_walks.
  16. Malformed resolutions refuse as ValueError. Tests: test_reference_profiles.py::test_malformed_resolution_shapes_refuse_as_value_errors, ::test_unnamed_unresolved_factor_rows_refuse_as_value_errors and ::test_duplicate_unresolved_factor_companion_refuses.

Verification (lane-reported unless marked hub)

  • a30c377: 24 files, 2,832 passed and 5 skipped. Eleven mutants killed. The hub re-ran seven files with the same counts and re-ran skip_compile_replay itself.
  • 1d5fff4: 24 files, 3,024 passed and 5 skipped. Six mutants killed. The hub re-ran four files with the same counts.
  • 4f77038: 15 files, 2,508 passed and 5 skipped. Three mutants killed. The hub re-ran three files with the same counts.
  • d28817f (hub): test_reference_profiles.py 74, test_transport_target_kernels.py 52 and test_nz_target_references.py 50 passed. Two mutants killed: reverting either fix turns its new test red with KeyError.
  • 2a64132 (hub, one file per process, normal priority): 15 files, 2,077 passed and 5 skipped. Clock runs at 2026-11-05: test_nz_reference_aging.py 18 and test_reference_profiles.py 97 passed. Whole CLI test file with the CLI clock at 2100-01-01: 163 passed. Mutants killed: 8 of 8, each for the intended reason. Receipts, effective intervals and factor contexts are byte-identical to d28817f (8 payloads, re-checked on the committed tree).
  • Skips: the 5 skips are UK feed cases that need a pinned UK feed.
  • Checks: ruff, ci_test_plan.py verify, graph_acceptance_burndown.py --verify, git diff --check and both generators' --check passed at each head.

Independent review

  • Sol round 1 (a30c377): REQUEST_CHANGES.

    • Two blocking findings: the role bypass and the flaky oracle. Both are fixed in 1d5fff4.
    • Three non-blocking: the scan exemption and the schedule bound are fixed; the table label and release date are corrected above.
    • It independently recomputed all seven declared values from the pinned artifact.
  • UK hub (a30c377): SAFE WITH NOTES. All notes are addressed by the opt-in loader and strict-equality fix; a delta check is pending.

  • Belgium hub (a30c377): transport cleared, and BE outputs byte-identical. A delta check is pending.

  • Sol delta review of 1d5fff4 and 4f77038: APPROVE_WITH_NITS at 4f77038. It ran 2,356 tests (5 expected skips) and observed 215 mutation failures across all 84 changed test functions, with no survivors. Its three nits are addressed above: the trust-boundary qualification, the source-text limit and the orjson attribution.

  • Belgium hub Sol review (4f77038): BE-INERT. Its latent finding Re-base epic: frame.place, sparse solve, target registry, gates + rotated holdout, US stage plan, wheels CI #5 is fixed in d28817f.

  • UK hub delta check (4f77038): safe for UK outputs; 112 passed and 5 skipped.

  • Opus delta review of d28817f (job 20261024-101711): APPROVE_WITH_NITS, Needs Max: no.

    • Mutants M1 and M2 are killed.
    • Findings 1 and 2 are test gaps: M3, M6 and M7 survived.
    • Finding 3: the ValueError contract is incomplete. Finding 4: v2 accepts one unnamed unresolved row.
    • All of these fail closed or are covered upstream. Fix round 3 addresses findings 1–4.
    • Finding 5 corrects the serializer wording below. Finding 6: the Belgium and UK check artifacts are in the NZ hub's review folders, not in this checkout.
  • Belgium hub, fix round 3 transport hunks (before commit): OK to commit once two asks were done. Ask 1 (defect): run entry walked scenarios without activation's guard, so a spec with reference profiles and no scenarios would refuse at the CLI only; run entry and activation now share compose.reference_profile_views. Ask 2: an end-to-end replay test through main(... --resume require) on a warm store, which the currency_check_on_replay mutant targets. Its nit, the UTC date, is taken (cli._today()). Its second round (10/24 about 6:10pm ET) OK'd the compose.py and cli.py hunks as they stand. Its optional nit is taken: the replay test's is_file() check proved nothing, because the cold run had already written the export. Under require the CLI reads back the stored export and never rewrites it, so the test now compares the export's bytes before and after the replay. Belgium re-clears BE (five dumps and its suites) at the pushed head.

  • Sol delta review of 2a64132 (job 20261024-180703): REQUEST_CHANGES. fix4 (495db83) addresses both findings:

    • P2 (blocking): the run-entry currency check guarded only cli.main, so the exported prepare_transport_build → execute_transport_graph route skipped it. The fix runs the check first in prepare_transport_build, before activation, the registry, the CREATE inventory and CREATE; require stays exempt. Execution and composition stay clock-free. The hub's tests pass (16 currency tests), and three mutants are killed. Belgium OK'd the cli.py hunk on 2026-10-24 (about 9:45pm ET): BE declares no reference profiles, so the check reads nothing, and BE still refuses at activation.
    • P3 (coverage): the warm-store replay test warmed no factor-bound compiler node. 495db83 adds a test-local factor-bound targets.compile@1 node, and the test spies the factor replay (test-only).
    • Otherwise the review confirms several points: factor validation is clock-free; require never computes missing work; Part B refusals are ValueError; and the valid receipt bytes and pinned digests are unchanged.
  • Sol delta review of fix4 (c4ddcb4, job 20261025-151022): REQUEST_CHANGES for incomplete verification only. P3 resolved and P2 partly resolved; M1 and M3 killed; M6 interrupted and M7 not run.

  • Hub controls (c4ddcb4, 2026-10-26): M2, M4, M5, M6 and M7 all killed for their stated reasons; the positive control passed 14 of 14.

  • Sol continuation review (c4ddcb4, job 20261025-212859): APPROVE_WITH_NITS, Needs Max: no. It ran M6 and M7 itself (both killed) and audited the hub's controls. Two non-blocking nits:

  • Delta review of the CI import fix (2026-10-29, job 20261029-083505): APPROVE, no findings, across Add feed-derived NZ reference ageing factors (G8-r2a) #1382, Add exact income-band intervals and boundary diagnostics #1394, Normalize donor money with exact declared factors #1395, Add receipt contract v2 and selection-aware reference preflight #1397 and Reconcile regional Census family-type counts under d1932 #1398. Merging b157bb9 into main differs from merging c4ddcb4 only in the fixture import lines of the two test files.

Landing (2026-10-29)

  • The NZ hub retargeted this PR to main after Allocate Census residuals through shared reference profiles #1375 landed, so it ran CI for the first time. Stacked PRs never ran it.
  • CI's engine-free job first failed at collection. test_reference_factors.py and test_reference_profiles.py imported microcosm_build.reference_factors as a top-level module. The hub's local runs had put test_support on PYTHONPATH, which hid the bad path; CI imports it only as test_support.microcosm_build. 64daa4f fixes the import and b157bb9 sorts it; the hub's runner no longer adds test_support to PYTHONPATH.
  • CI then failed test_uk_target_compile_modules.py::test_every_direct_import_of_a_listed_module_is_listed_or_excluded: 1d5fff4 made ledger_targets, a UK target compile module, import LexicalFloat from ledger_artifact. b36c702 first added ledger_artifact to EXCLUDED_DIRECT_IMPORTS.
  • 504e7d3 merges main after Allocate Census residuals through shared reference profiles #1375's landing. It changes no file, but Allocate Census residuals through shared reference profiles #1375 landed as 6af97bf, so this branch had two merge bases with main; GitHub marked the PR conflicting and ran no CI until this merge gave it one.
  • The Sol delta review of the exclusion (job 20261029-095750) requested changes. Both UK target kernels decode their facts inside run, through full_targets → load_ledger_consumer_artifact → _load_fact_rows, and source_hash covers only the modules a kernel passes it. Changing only the decoder's default parser moved a national target (325,054,437,305.83 → 325,054,437,306.58) while both UK implementation hashes stayed put. The gap predates this PR, but the exclusion would have exempted it on the record.
  • 7cd1d26 lists ledger_artifact in UK_TARGET_COMPILE_MODULE_NAMES instead.
    • Its one microcosm.build import, chronicle_epoch, stays excluded. That entry's reason now also names ledger_artifact's uses, the consumer schema gate and the epoch labels; none of them moves a fact value.
    • The listing adds no cache invalidation of its own. This PR already edits ledger_targets.py, a listed module, so it re-keys every UK target node either way.
    • The same decoder mutant now moves the UK module hash (ac59fb39 → bdcbffe8); against the previous list it didn't (dc3d8764 both times).
    • These pass at 7cd1d26: the UK compile-module tests (6), test_uk_solve_scope.py (126), test_ledger_artifact.py (55), test_chronicle_epoch.py (74) and test_transport_target_kernels.py (52).
  • Delta review of the listing across all five heads (Opus 5.5 in session, since every Codex lane was held): APPROVE_WITH_NITS, no blocking findings; Needs Max: no. At 7cd1d26 the decoder mutant moves both UK target kernels' implementation hashes (full ed1942ce → b5469cfd, national a61c1262 → 5b374180) and turns a decoded 1.5 into 2.5; at 504e7d3 every hash stays put. Dropping ledger_artifact from the tuple turns the coverage guard red. Each stacked PR's merge changes only target_compile_modules.py (blob 642a1e912 on all five heads). The four test files (259 passed, 2 skipped for want of an NZ Chronicle artifact), ruff, the plan check and the burndown pass at Add feed-derived NZ reference ageing factors (G8-r2a) #1382, Add receipt contract v2 and selection-aware reference preflight #1397 and Reconcile regional Census family-type counts under d1932 #1398. Two nits: pin ledger_artifact in both kernel-hash tests, so the listing doesn't rest on ledger_targets' incidental LexicalFloat import (a follow-up PR after the stack lands); and 7cd1d26's message credits the re-key to ledger_artifact.py as well, when only the ledger_targets.py edit was hashed. Two gaps that predate this PR, which the follow-up triages: chronicle_epoch's epoch labels reach node artifacts without moving a kernel hash, and other modules on full_targets' path go unhashed.
  • CI stops at the first failure, so about 358 of the 22,000-plus engine-free tests never ran in the failed rounds. They sit in the 29 files after the failing one in CI's list; the hub ran all 29 (711 tests) with CI's import path at the two stack tips, Add receipt contract v2 and selection-aware reference preflight #1397 and Reconcile regional Census family-type counts under d1932 #1398: 705 passed and 6 skipped at each.
  • CI at 7cd1d26: every job passed (gh pr checks exit 0; run 40896742895): engine-free, engine-uk and engine-us on Python 3.13 and 3.14, integration-uk, lint, select-countries and wheels.
  • BE re-clear: Belgium's hub is away until 2026-10-31, so the NZ hub ran Belgium's own dump_be.py and its three BE suites at 7cd1d26 against main at 846b48a, alone and merged with Belgium's open Rewrite the Belgian country package for donor transport #1369 (dc157a7). The BE dump matches main's byte for byte; with Rewrite the Belgian country package for donor transport #1369 merged in, it matches main plus Rewrite the Belgian country package for donor transport #1369. The BE golden comparison holds in both, and all three suites pass with Rewrite the Belgian country package for donor transport #1369 merged in.
  • Every NZ factor declares valid_until 2026-11-03. From 2026-11-04, new NZ transport runs refuse until the NZ hub re-declares the factors from Stats NZ's next quarterly release; require replay stays exempt.

Wall-clock fix (found by the hub 2026-10-24; fixed in 2a64132)

  • The bug. At d28817f, reference_factors.py:236 compared date.today() with each declaration's valid_until inside validation, and every NZ factor declares valid_until 2026-11-03. With the clock patched, test_nz_reference_aging.py passed 13 on the real clock and at 2026-11-03 but failed 11 at 2026-11-05, and test_reference_profiles.py failed 1. Merged as it was, it would have failed main's CI for every PR from 2026-11-04.
  • The fix. Validation is deterministic, and currency is checked once at the transport CLI entry with an explicit UTC today (Transport, above). Replay is exempt, as the Belgium hub asked.
  • Mutants killed (hub): the clock back in validation; the currency check on replay; no currency check; run entry walking different views from activation; the local date in place of UTC; and the d28817f review's M3, M6 and M7.

Open scope

🤖 Generated with Claude Code

MaxGhenis and others added 5 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>
MaxGhenis and others added 2 commits October 25, 2026 10:54
…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>
#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>
MaxGhenis added a commit that referenced this pull request Oct 26, 2026
#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>
MaxGhenis added a commit that referenced this pull request Oct 26, 2026
#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>
MaxGhenis added a commit that referenced this pull request Oct 26, 2026
#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 #1398 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>
MaxGhenis added a commit that referenced this pull request Oct 26, 2026
#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>
MaxGhenis added a commit that referenced this pull request Oct 26, 2026
The region-99 support note in the generator, calibration_bindings.json and
the calibration-targets doc said G8-m2 (#1393) was not in this stack's base.
The merge of #1394's head brought in #1382's fix4 head, which carries #1375's
merge of main, so #1393 (4e31e42) is now an ancestor. Drop the dependency
clause; the support statement itself is unchanged. calibration_bindings.json
is regenerated with the generator, and the NZ golden follows its hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis changed the base branch from nzhub/g8r1b-residuals 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 added a commit that referenced this pull request Oct 29, 2026
Resolves the one conflict in test_reference_profiles.py: keep this
branch's two fixtures and #1382's test_support import path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
MaxGhenis added a commit that referenced this pull request Oct 29, 2026
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>
MaxGhenis and others added 2 commits October 29, 2026 10:14
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>
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>
MaxGhenis added a commit that referenced this pull request Oct 29, 2026
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>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 29, 2026 16:39
@MaxGhenis
MaxGhenis merged commit 0c7870c into main Oct 29, 2026
10 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged by the NZ hub (session local_e407a527) at 7cd1d26 with --merge. Gates: gh pr checks exit 0; MERGEABLE; not draft; no CHANGES_REQUESTED review. Reviewed head: 7cd1d26. Independent delta review of the ledger_artifact listing at 7cd1d26: APPROVE_WITH_NITS (Opus 5.5 in session; every Codex lane was held), jobs/review-microcosm-fix5list/out-opus.md. BE re-clear clean at this head (jobs/be-reclear-1029e). Follow-up owed: pin ledger_artifact in both UK kernel-hash tests.

@MaxGhenis
MaxGhenis deleted the nzhub/g8r2a-factors 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