Repository navigation
Conversation
The local lane left takes_up_snap_if_eligible and takes_up_tanf_if_eligible missing on every ACS SPM unit, and the reviewed-null fill gave them the engine default (universal take-up) in both the materialize pass and the consumer export. - New us_runtime.acs_local_take_up fills only missing ACS-row cells: receives_snap reporters take SNAP up, non-reporters draw to the snap_take_up stage's national FNS rate; TANF uses the take-up contract's seeded Bernoulli rate. Draws use take_up._stable_unit_draws. - The release tool runs it before both fill_reviewed_nulls calls, records the seed and an assignment digest in run_identity.json, and the consumer export must reproduce that digest; calibrate refuses a pre-#1019 checkpoint. - fill_reviewed_nulls refuses NaN in either take-up column. - New acs_local_take_up_signal gate is a finalize hard failure: completeness and non-constancy on both spines, rate band and receipt anchor on the ACS spine only (donor cells are graded by the donor release); package requires it passing and bound to the packaged bytes. - Other ACS take-up flags stay a reviewed limitation (#1022); TODO left in multispine_pool.seed_multispine_pool_inputs, which default-fills SNAP the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review) Both measures are informational and never graded. - acs_local_take_up: the TANF program receipt gains receipt_crosstab, a weighted 2x2 of transferred receives_tanf by assigned takes_up_tanf_if_eligible on ACS units, with the weighted share of receives_tanf units that drew False. The TANF draw stays independent of receipt; recording the table leaves the assignment digest unchanged. - Release tool do_qa: the chunked engine probe now reads the SNAP variables in the same engine pass as SSI and writes snap_take_up_among_eligible to spine_qa.json: take-up and positive-benefit rates among is_snap_eligible SPM units, overall, by household type (elderly or disabled, with children, childless adults, with earnings; overlapping), by spine and type, and by state. - Tests: hand-checked cross-tab cells and share; a two-chunk do_qa run with a stub engine checks every rate; an empty eligible set reports None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The multispine pool's seed stage (the successor builder) still gives a missing takes_up_snap_if_eligible cell the engine default, universal take-up, because its spine-blind take-up contract has no SNAP seed. Its output is now refused before release. - h5_io: us_multispine_pool_defaulted_take_up_failures reads the seed stage's receipt (stage_receipts.seed.programs.<variable>.defaulted_rows) for US_MULTISPINE_POOL_RELEASE_BLOCKING_DEFAULTED_TAKE_UP (takes_up_snap_if_eligible). A positive count, a programs block without the variable, or a malformed count fails. It reads receipt counts only, so it stays spine-blind; a seed receipt with no programs block (not written by the seed stage) is not evaluated. - Both release loaders (load_simulation_ready_us_multispine_pool, used by the fiscal refresh release builder, and load_authenticated_us_multispine_pool_for_release, used by --base-h5 and release preflight) refuse such a pool. The scoring-only loader still loads it as evidence. - The multispine_pool TODO(microcosm#1019) now points at the gate; the changelog fragment says so. - Tests: both release loaders refuse a pool with 3 defaulted cells and load it at 0; the scoring loader keeps it; the receipt reader's missing, malformed and negative cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review) This reverts commit f8da490. Refusing pool releases with engine-defaulted SNAP take-up would block the national multispine pool's release loaders, including the fiscal refresh release builder, for every pool with ACS rows. That is a decision for the pool lane's owners, not this local-lane PR. The ACS local lane is already gated: its take-up stage seeds SNAP on ACS rows and finalize/package refuse a release without a passing take-up receipt. The pool lane's missing SNAP seed stays tracked by the TODO(microcosm#1019) in multispine_pool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1051 had fallen 755 commits behind main and conflicted with it. Two files conflicted; both sides are kept. tools/build_us_acs_local_release.py (7 blocks): - Module docstring: main's sparse state_cd materialize/calibrate text, plus the #1019 take-up seeding step. - Imports: both the acs_local_take_up and the microcosm.data.contract imports. - Helpers: #1019's _with_local_take_up/_recorded_take_up and main's CD-holdout and pro-rata population helpers. - do_materialize: take-up is seeded on the full staging frame, then main's development rung is drawn, so a rung keeps the flags the full build assigns. - run_identity.json: main's soi_mode, cd_holdout and sampling keys and the #1019 acs_local_take_up receipt. - do_calibrate: main's rewritten calibrate; the #1019 pre-solve refusal of a checkpoint without a take-up receipt is re-added to it. - Consumer export: re-derive and verify the take-up assignment, then re-draw main's development rung (the same order as materialize). tests/engine_free/us/test_us_acs_local_release_tool.py (1 block): - Main's new sparse-era finalize/package test, and the stack's _package_args_with_hours with its take_up_state parameter. Semantic fix: main's new do_calibrate penalty-settings test writes a run identity without the #1019 take-up receipt, which the stack refuses before solving. The test now records one, and also pins that a pre-#1019 identity is refused before any solve. Main replaced tools/ci_test_groups.py with tools/ci_test_plan.py; the stack's tests verify under `ci_test_plan.py verify`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on rate (#1051 review) The QA table counted one row per December-eligible SPM unit at its household weight and called the result the quantity FNS participation rates measure. FNS rates are among eligible people over a federal fiscal year, so it was not that. - Each cell now reports a person-weighted rate beside the unit rate: eligible persons who take up over eligible persons, each unit weighted by its household weight times its member count, as the frame weights people. The unit fields are renamed unit_* and the person fields are person_*, with eligible_persons alongside eligible_units. - spine_qa.json's key is now snap_december_eligible_snapshot, labelled "December-eligible snapshot (unit- and person-weighted)", with a not_fns_participation_rate statement. The definition says the receiving rate counts a positive annual benefit, a different window from the December eligibility. - The module and do_qa docstrings, the QA note, the acs_take_up_engine_defaults limitation and the changelog fragment say the same thing. The table stays informational and is never graded. - Tests: the existing QA test reads the new keys (person rates equal unit rates for one-person units), and a new test runs the review's example through do_qa: an eligible one-person unit that does not take up and an eligible four-person unit that does, at equal weights, give a unit rate of 0.5 and a person rate of 0.8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI run 38640801867 failed test_resume_refuses_weights_without_the_run_identity_stamp in main's test_us_acs_local_sparse_cd.py. That test drives do_calibrate on a stubbed checkpoint whose run identity has no ACS take-up receipt, so the stack's pre-solve take-up refusal fired before the run-identity stamp check the test targets. test_us_acs_local_target_loss_weights.py, which CI never reached after --maxfail=1, failed five tests for the same reason. The refusal order stays as it is: a checkpoint that predates #1019 must be re-materialized, which also invalidates any weights, so its refusal should come before the weights-stamp refusals. The fixtures change instead. - New _record_current_materialization(module, args, identity) in the release-tool tests records what a current materialize stage leaves (here the take-up receipt in run_identity.json). The stack's later layers extend this one helper as they add staging or identity requirements. - The sparse-CD resume test and the target-loss-weight calibrate_run fixture load it, the way the sparse-CD tests already load the batched-materialization fixtures, so each still reaches the refusal it tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Main moved 47 commits since the last merge (1cfbf7d): mostly UK data ports, the high-value homes build, NZ spec citations, the emitter's PyPI release (microcosm-emitter 0.2.1) and the Modal ACS multispine staging tool (#1267). No file overlaps this branch, and the merge had no conflicts. Checked after the merge: ruff check, tools/ci_test_plan.py verify, the release-tool test set (every test file that loads or drives tools/build_us_acs_local_release.py) and main's new test_us_modal_stage_acs_multispine_staging.py. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in 364 commits of main, including #1264 (microcosm#1263: the ACS SPM independence role, the package-stage SPM measurement gate, the interim drop of group-quarters children, the inherited Social Security erasure limitation and the live stored inputs) and microcosm#1335 (one label per US geography). tools/build_us_acs_local_release.py conflicted in three places: - After UnregisteredNullError both sides added a refusal: this branch's DeniedDefaultFillError and NEVER_DEFAULT_FILLED (take-up draws, microcosm#1019), main's DeclaredSourceInputNullError and its remedy. Both are kept. - fill_reviewed_nulls: the docstring names both refusals. The take-up refusal still runs first; main moved the CountryTaxBenefitSystem import under `if system is None`, so the branch's top-level import is dropped. - consumer_export.json records both "acs_local_take_up" and main's "live_stored_inputs". No other change was needed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The adapter should start the separately installed microcosm-emitter command through the interface's EmitterClient, with the telemetry, Orrery and Hugging Face modules, and keep the build running when the command is missing. These tests fail against the adapter that runs the pinned emitter in-process. Adds an in-memory EmitterClient for build tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
microcosm-build now depends on microcosm-emitter-interface (>=0.7.0,<1) instead of the exact microcosm-emitter==0.2.1 pin. The build adapter starts the separately installed microcosm-emitter command as the build's child process through the interface's EmitterClient, with the telemetry, Orrery and Hugging Face modules. The child inherits the build's environment, so it finds the same Hugging Face credential; its settings go on standard input. Telemetry requests keep their fields. A missing or failing emitter leaves the build running without it. Graph requests made without a build emitter start a short-lived emitter that runs only the Orrery module. uv.lock drops microcosm-emitter, alembic, mako and sqlalchemy and adds microcosm-emitter-interface 0.7.0; APPROVED_UV_LOCK_SHA256 is re-pinned. Test support replaces the emitter's in-process lifecycle with the documented request-to-event rules over an in-memory client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The version 1 and version 2 staging writers should hand their run files to the build's emitter client instead of uploading them with HfApi: one hand-over per cycle, the runs index merged by the emitter, read-back done by the emitter, delivery counts taken from its receipts, and three hand-overs in a row that the emitter does not accept pausing the run's hand-overs until its final state. The US and UK builds should pass their emitter's client to the writers, and the US build should hand over a failed state before its telemetry outcome shuts the emitter down. These tests fail against the writers that upload each file themselves. The in-memory client can share a fake Hub's mapping, so UK tests still see the run files and the staged dataset in one place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The version 1 and version 2 staging writers hand their run files to the build's emitter client (microcosm-emitter-interface's stage_run_files) instead of uploading each file with HfApi. The emitter copies the bytes, commits every pending path of a repository in one commit per cycle with the build's credential, merges the version 1 runs index itself, and reads the run back on request. - staging_storage: RunFileHandOver replaces BestEffortUploadSession. It never raises, counts the emitter's receipts, and pauses after three hand-overs in a row that the emitter does not accept. - Version 2 delivery counts are the emitter's as of each document's hand-over; read-back is the emitter's comparison. The final state is handed over once more after a pause; the emitter retries commits, so the writer no longer sleeps between attempts. - The US build and the UK rowwise and spine builds pass their emitter's client to the writers. The US build hands over a failed staging state before its telemetry outcome shuts the emitter down. Contract validation stays in Microcosm. Staged dataset bundles still upload directly (H2b). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image should install microcosm-emitter with `uv tool install`, outside the environment synced from uv.lock, check that the command runs, and name it in MICROCOSM_EMITTER_COMMAND for the stage tool. This test fails against the image that has only the synced environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image installs microcosm-emitter (>=0.7.0) with `uv tool install` into /usr/local/bin after syncing the build's environment from uv.lock, and fails to build unless `microcosm-emitter version` runs. The image's environment names the command in MICROCOSM_EMITTER_COMMAND, which the stage tool inherits. Each plan commit's image gets the newest emitter release when it is built; the stage check reports the installed version and refuses an image whose command does not run, and the runner records the version in its identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README, the graph publication and UK staging operations docs, the agent guide and the Modal runbook now say that builds start the separately installed microcosm-emitter through the interface, how to install and upgrade it, which settings reach it and how, and what the staging delivery counts mean now that the emitter commits the run files. Adds the changelog fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The emitter compares the Hub with the bytes it committed, so after the run's final hand-over was not accepted the Hub can match an older state and the read-back must still fail. This test fails: read-back passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verify_remote now fails, as READ_BACK_FAILED, when the emitter did not accept the run's current files, instead of asking the emitter to compare the Hub with an older committed state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Emitter 0.6.0 is the first release with the Hugging Face module that takes staging run files and its queue in events.handovers.sqlite3. An older emitter that accepts protocol 1 (0.4.0 to 0.5.1) starts without that module, so the build's staging hand-overs would all fail with only a warning each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adapter reads the started emitter's release from its hello answer (EmitterClient.emitter_version). Below 0.6.0, the first release with the Hugging Face module and its events.handovers.sqlite3 queue, it closes that emitter and prints the installed and required versions with the upgrade command; the build continues as it does without an emitter. The same check covers the short-lived Orrery emitter. 0.7.0 added only release publication, which builds do not use, so the Modal comment that called 0.7.0 the first release with the Hugging Face module is corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/modal_us_stage._emitter_version() runs the image's emitter path, /usr/local/bin/microcosm-emitter, so a test that reaches _runner_identity() or check_stage() ran whatever command a machine has there. The test puts a stand-in command at that path and checks that it never runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
load_app() now replaces tools/modal_us_stage._emitter_version() with the recorded answer of the image's emitter, so tests that reach _runner_identity() or check_stage() never run a command found at /usr/local/bin/microcosm-emitter on the test machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An emitter's telemetry module records the run's start when the emitter starts, before it can answer hello (every release from 0.4.0 to 0.7.0 initialises its modules before it opens its socket). An emitter the adapter starts and then stops after reading its version from hello has already recorded that start; with a credential, it delivers it to the collector when it is closed, so the build monitor shows a run that started and never finished. The tests name a stand-in command that answers `microcosm-emitter version` and check that an older release is never started, that a release from 0.6.0 on is started from the same command whose version was read, and that the short-lived Orrery emitter follows the same rule. Every test in the file now names a missing command by default, so none finds an emitter on PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adapter read the release from the started emitter's hello answer and closed an emitter older than 0.6.0. By then that emitter had already registered the run and recorded its start: its telemetry module does both when it initialises, before the host opens its socket. Closing it delivered that start to the collector with no later event, so the build monitor showed the run as running for six hours (no heartbeat ever arrived) and then as gone silent, never as failed. The adapter now runs `microcosm-emitter version` (every release since 0.4.0, the first with the command), which prints the same description as hello and starts no module, and starts the same command only from 0.6.0 on. An older emitter, or one with no usable answer, is never started; the warning says so and the build continues as it does without the command. A missing command is still reported by the interface. README, the Orrery publication notes, the Modal image comment and the changelog fragment say the build does not start an older emitter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_the_app_never_runs_an_emitter_installed_where_the_image_puts_it calls _runner_identity(), which also checks for /root/microcosm/uv.lock, the image clone's lock file. On a GitHub runner /root is unreadable, and Python 3.13's Path.exists() raises PermissionError there, so engine-free (3.13) failed on 6a69406 and on 9b8e42e with that error (1 failed, 5923 passed). It passes on macOS, which has no /root, and on Python 3.14, whose Path.exists() returns False. The test now points IMAGE_REPO_ROOT at an empty folder under its temporary directory, as the other tests that reach _runner_identity() do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both sides: the take-up refusal and main's ESI premium refusal in fill_reviewed_nulls; main's self-employment identity check runs before the take-up stage at materialize and export; the take-up checkpoint refusal runs before main's county solver profile at calibrate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The SPI income stage now moves the 2022-23 tape's interest and other income to HMRC's published 2023-24 Table 3.7 amounts band by band, with the tape banded by its own total income, before any draw. The OBR's savings income growth then carries both columns to the frame's year, the path the Table 3.7 band targets take. Before, each column moved by its engine index from 2022 to 2024: other income by GDP per head (x1.09), interest by ONS household interest (x2.27). SPI's own amounts grew x1.65 and x2.69 in the single year to 2023-24. The frame carried about 70 percent of the savings rows at design weights, and the solve met them by stretching the few households that carry them. On the tape, the £1m-and-over band's other income outweighs its interest, as in the published table; the two indices had flipped that split. - uk/hmrc_spi_table_3_7_savings_rows.json vendors the 26 published rows from the feed pin, declared in the vendoring register and the country package. - spi_income.rebase_spi_donor_savings scales the prepared tape. A band the tape carries none of is left as it is and named in the receipt. The forest and the band-donor resample both read the rebased tape. - _spi_income_uprating_factors grows the two columns from 2023-24 on hmrc_uprating.uk_obr_savings_income_level. Both columns now run on the projection's GDP per head from the frame's year on (SPI_HISTORY_INDEX_DIFFERS_FROM_PROJECTION). - Tests cover the rebase, the published rows, the OBR factor and the stage wiring, and the engine lockstep tests are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first spine built from 667fe53 stopped at uk_stage_uc_take_up. Single people's caseload ended 0.0568 of DWP's count further from it than before the redraw, against 0.0523 on PR C's spine and a tolerance of 0.055. The rebased SPI savings feed the UC reporter redraw's investment-income predictor. The few SPI benefit units whose report changes move the take-up stage's pool merging, which joins adjacent award bands until each pool rests on enough sources. Single people's £1,000 to £1,100 award band moved from a reachable pool into one whose DWP target exceeds the entitled units. The reachable pools still realize DWP's counts within 0.005. The tolerance goes to 0.06 (María's ruling of 2026-10-30), as PR A took it from 0.05 to 0.055 for the gilt reshuffle. Pool boundaries that move with small upstream changes are a follow-up on #1334. The gate's note, the committed-parameter test and the data contract's mirrored digests are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The national built from f3cb3dd fitted every SPI Table 3.7 interest and other-income band but one, the £1m-and-over other-income row included. It missed the £1m-and-over interest amount row outside the uk_target_fit band. By María's ruling of 2026-10-30, the row is held out of the objective as a measured distortion, as the ten HMRC £1m-and-over cells are, and measured on every evaluation. The register entry carries no figures for the band. How the rebase sits in the top bands at design weights is a follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G8-w4b: transport weights audit and its checked gate
G8-w4c: donor support roster, producers and checked gate
G8-w4d: wire the release-evidence producers into the NZ graph and toy
Place each ACS rent draw on its own household's head
G8-r2c B: transport.quantile_map@2 with a fitted grouped Pareto top band
G8-r2c C: measure.combine@1 sums declared signed cent components
* Pin five G8-w4 fixes that no test covered The G8-w4 review (#1448, comment 6586844221) ran mutants that undo five fixes, and every test stayed green. These tests turn each of those mutants red. They change no source file. C1 codec rule: test_a_rulespec_name_with_another_codec_is_uncovered pins that composition treats a source as the RuleSpec tree by its codec, so rulespec_nz with a toy codec comes out uncovered and fails the gate. It goes red when compose.py also skips any source whose name starts with rulespec. E2 with an aggregate: test_an_aggregate_total_is_checked_against_its_own_component pins that the support kernel checks a household total of 150 in component a against a's bands, so its three member rows count as outside. It goes red when the kernel checks aggregate totals against the pooled intervals. Gate empty-sources line: test_a_forged_derived_row_without_sources_fails feeds the support gate a forged derived row with sources [] and asserts its failure line. It goes red when gate_bindings.py drops that line. S1 person slice: test_relation_columns_on_another_entitys_slice_are_a_gap pins that the closure partition reads relation columns from the person slice only; the same columns on the household or family slice give a gap. It goes red when evidence_contract.py reads them from any slice. S1 unmapped root: test_a_relation_on_an_unmapped_root_entity_is_refused pins that engine_root_inputs refuses a relation whose root_entity entity_names does not map. It goes red when the refusal becomes continue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6KNfb66b2ZhYpeyLD8 * Pin the quantile map's refusal of mixed component codes transport.quantile_map@1 refuses an aggregate whose members carry two component codes (population_kernels.py:555-567), but no test pinned it. Under component bands, that check is the only guard: the kernel hands frame's quantile_map one entity group per component without the codes, so frame's own member check can't see them, and each component's map would rank a split household's partial total as a whole household. The support kernel's E2 fix judges each member's aggregate total by that member's own code, so it relies on the same refusal. test_aggregate_map_refuses_members_with_two_component_codes runs the household aggregate map with component_column "sex" under bare and under component bands. The toy donor's households mix sex codes, and both shapes refuse with the kernel's message. When each member takes its household's first sex flag, both shapes run. Mutant: with the kernel's check disabled, the component-band case raises nothing, and the bare-band case refuses with frame's "Members must repeat one entity component label." instead, so both mixed cases go red. The two controls pass. No source file changes, so no kernel hash moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run both component codes in the mixed-code pin's controls The review of #1471 found that _one_sex_per_household made every toy person male, so the "both shapes run" controls declared one code. A mutant that refuses any frame holding two codes at all (pd.Series(component).nunique(dropna=False) > 1) passed every test, and that mutant would refuse every real mixed-sex population. The helper now gives each household one flag, alternating across households, and the test asserts the frame holds two codes. So the controls pin "per household": no household mixes codes, the frame holds both, and both shapes run. Ran test_aggregate_map_refuses_members_with_two_component_codes: 4 passed. Under the over-strict mutant, [False-bare] and [False-component] go red with the kernel's message. Under the disabled check, [True-bare] and [True-component] go red as before. No source file changes, so no kernel hash moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Catch an under-strict mixed-code check with a one-household case Belgium's hub found a mutant that no test caught: `.all()` for `.any()` at the mixed-code check in `T/population_kernels.py`. Both toy households mix codes, so the two agree on every fixture. A check using `.all()` fires only when every household mixes codes, which a real population almost never does. The third `mixed` value gives the first household one sex flag and leaves the other household mixed. The existing case ids stay the same. The invariant: the kernel refuses when any one household mixes component codes. Under the `.all()` mutant, `[one_household-bare]` and `[one_household-component]` fail and the other four cases pass. With no mutant, the file gives 38 passed, and ruff check and format pass. The patch comes from Belgium's hub, sha256 25b21404d466e1e1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
G8-r2c A: summed-band money rows and the measurement proof
G8-r2c D: per-band support diagnostics and the measurement_prefit gate
Both tests pass NZ's packaged spec to prepare_transport_build and expect activation's "not activated" refusal. prepare_transport_build checks factor currency on today's UTC date before activation, and NZ's selected factors carry valid_until 2026-11-03. From 2026-11-04 the currency check refuses first, with a different message, so both tests fail on main and on every PR. Each test now pins cli._today to 2026-10-24, inside the factors' validity, as test_cli_main_refuses_the_packaged_spec_and_writes_nothing already does. With the clock forced to 2026-11-05 for the whole run, both tests failed before this change and pass after it. They also pass on today's clock. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Refuse calibration rows that measure a declared holdout A holdout_references row tests the calibrated model against a fact it never saw. Composition accepted a calibration, pre-calibration or mass row on the same measure, so only Belgium's package test kept a country from calibrating to its holdout. validate_transport_activation now refuses any row of another reference document it knows, mass included, whose measure equals a holdout row's measure on any entity, and names both rows and both documents. The pre-calibration stage check keys on the same measure, on any entity. Specs that declare no holdout_references rows keep row-level holdouts. The new test module checks the toy reproductions, the pre-calibration, mass and stage variants, every packaged spec's reference documents, a Hypothesis property against an independent oracle, and agreement with Belgium's _separation_violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kavn5bQZwSxfXaM75 * Give the toy holdouts measures no calibration row shares The new holdout separation guard refused two synthetic specs. The NZ toy inherits the composed toy's holdout on (person, receives_main), and its receipt strata add pre-calibration rows on receives_main. The reference selection tests gave every document, holdout included, person_count. The NZ toy's holdout rows now measure toy_main_eligible, a dedicated eligibility column no other reference document measures; no NZ toy node reads holdout_references. The reference selection tests give their holdout documents a holdout_indicator measure. The NZ toy graph and reference selection modules pass (83 tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kavn5bQZwSxfXaM75 * Skip non-text measures on both sides of the guard (B1) _holdout_separation_gaps tested a non-holdout row's measure for membership in the holdout dict without checking its type, so a list or dict measure raised TypeError out of validate_transport_activation and hid every other activation gap. The non-holdout side now skips a non-text measure with the same _is_text test the holdout side already applies. Invariant: a list or dict measure on a calibration, pre-calibration or holdout row of a spec that declares holdout_references raises no TypeError, and activation's outcome equals its outcome with the guard patched out. test_non_text_measure_keeps_activation_outcome pins it; its calibration and pre-calibration cases go red with TypeError when the line is reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FbNxkHFc5E9GDKFKe * Import Belgium's test module before any @given runs (R1) _be_test_module executed Belgium's test module on first use, and the generated-documents differential first used it inside its own @given. Belgium's module defines @given tests at import, so Hypothesis refused the nesting whenever that test ran first: alone with -k, by node id, or under pytest-xdist's default load distribution. The module now imports Belgium's module once at module level. The health check stays on and Belgium's file is unchanged. Invariant: the differential passes in any order. Checked with -k generated_documents, by node id, and the whole file under -n 4 with the default load distribution. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FbNxkHFc5E9GDKFKe * Test the guard's mass, parked-holdout and view branches (R2) Three branches of the guard had no test, so mutants M5, M6 and M4 survived. - test_unselected_mass_document_joins_the_guard drops node 0's mass params from the composed toy and adds a mass row on the holdout's measure; it kills M5, which deletes the unselected-mass branch. - test_parked_holdout_still_guards makes the holdout pending with its exemption; it kills M6, which counts only active holdouts. - test_profile_selected_views_reach_the_guard gives the toy a scenario with knobs so activation selects a view, and patches transport_resource so the view carries a row on the holdout's measure; it kills M4, which drops the separation_views append for selected views. No real profile can add a measure: resolve_reference_profiles rebuilds rows by name and never writes measure, so the docstring says the test pins only that views reach the guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FbNxkHFc5E9GDKFKe * Run the stage guard on every packaged country's stages (R3) Activation refuses every packaged spec today, so no test reached _precalibration_stages with a country's real stages. test_packaged_precalibration_stages_pass_the_stage_guard calls it on each packaged spec: AM, BE, UK and US return no stages and NZ returns income_tax_schedule_1 and main_benefits_rates with no refusal. test_nz_stage_output_on_a_holdout_measure_refuses_across_entities points NZ's family holdout at jobseeker_support_annual_gross, a person stage output, and expects the refusal; an entity-keyed stage guard lets it through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FbNxkHFc5E9GDKFKe * Describe what the packaged activation test shows (R4) Every packaged spec refuses activation on missing bindings, and activation joins the guard's gaps into that same refusal. The module docstring and test_packaged_activation_outcome_is_unchanged now say the refusal holds no guard line and the guard leaves it unchanged, list the new branch and non-text tests, and point to the stage-guard test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FbNxkHFc5E9GDKFKe --------- Co-authored-by: Claude <noreply@anthropic.com>
Add one person-level transform kind, CoresidentAdult, serialized as
{"kind": "coresident_adult", "min_age": <int>}. A person's value is true
when at least one other person row with the same household id has
age >= min_age. A null age never qualifies, and a person with a null
household id shares a household with nobody. It binds no country.
Fix _matched_ids so that a nullable integer id column holding a null no
longer reaches matching as float64, where ids beyond 2**53 round
together. It now returns such a column as Python ints beside pd.NA, and
null-free integer columns as int64, as before. Belgium's hub wrote that
fix; the NZ hub applied it unchanged.
Invariant: for an integer id column, _matched_ids never returns a float
array, so two distinct ids never compare equal and a null id never
matches a non-null id. A null person household id can still find a
household row whose own id is null in the same nullable dtype, as on
main.
Reviews: #1183 comments 6592486915 (the kind) and 6595462928 (the
_matched_ids fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nactive (#1463) generate_be_target_references.py writes 31 more precalibration rows and a new legacy_json resource, quantile_surfaces.json. Five active wage rows count RSZ/ONSS T17 employed workers by known-pay daily-wage class at the end of 2026-Q1, each summing its 24 age and sex cells. Twenty-six parked pension rows pin SFPD's group 1 amount bands of January 2025. They sum to the published 2,449,233 and refuse to compile until shared rank_only support lands. Each surface's band document matches the bands parameter of transport.quantile_map@1. No graph reads the surfaces, and Belgium stays inactive. The generator refuses an SFPD band that ends below its floor, so floors rise strictly and every surface band spans at least one cent. B3's artifact-free checks in the Belgian test file now accept several rosters and record sets on a base selector, with a declared member count. GPT-6.1 Sol reviewed ba34140 (Subfleet run 20261102-131234-microcosm1463-review), and an Opus 5.5 agent reviewed the floor check in 3df4596. 58102db adds the first-band test that review asked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The E-G review's fix 3. Only the Chronicle feed, which CI doesn't load, checked R2C-16's membership, so a coarse row that lost a member label passed every feed-free test. The new test reads the committed income_diagnostic_references.json: each coarse row's labels equal, in order, the labels of the native rows its metadata.native_rows names, its selector otherwise equals theirs, and each measure's rows tile 50 to 244 once. Without the feed, the review's b05 member drop turns this test red and no other in test_nz_target_references.py, test_nz_reference_intervals.py or test_nz_spec_package.py (1 failed, 1237 passed, 42 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The E-G review's fix 4. Only the feed checked the wage table's cents against their labels, so a $1 change to one upper_cent passed every feed-free test, even after the builder rewrote earnings_bands.json to match. The new test reads each band's committed label as the right-closed band (a - 1, b] dollars, so the cut below it is 100(a - 1) cents and its upper_cent is 100b. A label whose lower endpoint repeats the previous band's upper one starts at that cut, and the test pins those rows to 27, 57 and 87. Without the feed, the review's row_107 mutant turns this test red and no other in test_nz_transport_declarations.py or test_nz_earnings_bands.py (1 failed, 63 passed, 2 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Belgium's hub asked for this rename when it cleared E to G's shared test lines: since F, the test checks the coarse stand-in table, not the national one. Only the name changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stack last met main at D's head (91bf4c0). Since then main has landed eleven PRs whose content the stack lacks: #1432, #1442, #1410, #1428, #1051, #1444, #1471, #1479, #1472, #1477 and #1463. #1472 brings the holdout test whose pinned NZ stage set the next commit extends (R2C-26). git merge-tree resolves the merge without conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G's nz.precal.nz_super (R2C-2) adds the nz_super binding to NZ's precalibration stages. At main + G, this test failed on its pinned set alone, and the stage guard itself accepts the stage: test_packaged_precalibration_stages_pass_the_stage_guard[nz] Hub ruling R2C-26 lifts the freeze for this one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G's 714e10d adds twelve stand-in income band outputs to NZ's calibration_bindings.json, each an "interval" entry. The tests in test_calibration_columns.py call prepare_calibration_columns on that file without the effective interval document, so the check at packages/microcosm-build/src/microcosm/build/calibration_columns.py:106-107 refuses them with "Interval outputs need the effective interval document." Brief 214 counted 17 such failures at 891cb9f. The prepare kernel passes the document (packages/microcosm-build/src/microcosm/build/transport/target_kernels.py:679); these tests call the function directly. _bindings() now drops the interval entries, and a new test pins the refusal: NZ's packaged bindings declare interval outputs, and prepare_calibration_columns refuses them without the document. The file's interval tests still cover interval outputs on their own fixture. With the fix the file gives 106 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
Contributor
Author
|
Closing as included: #1478 landed this PR's commits on main in merge commit 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
G8-r2c (#1183): the hub's two fix rounds on G. Round 1 answers the E-G review (comment 6599546057, REQUEST_CHANGES at G
64ef20af). Round 2 fixes the failures that CI found when it first ran on a merge of main and the stack, in draft probes #1487 (this branch) and #1489 (this branch at20507ad8without--maxfail=1; it never merges). Draft, with the toy branch as its base (#1483,nzhub/g8r2c-g-toyat85d55af). Rulings R2C-26 and R2C-27 (#1183 comment 6600262214) cover round 1. Paths:B/ispackages/microcosm-build/src/microcosm/build/,T/isB/transport/,N/isB/nz/, and tests sit inpackages/microcosm-build/tests/engine_free/shared/.WIP. Plan: brief 213 reviews the toy commits and both rounds together, and Belgium's hub clears the shared lines below. After that the hub fast-forwards G (#1478) to this head, retargets #1478 to main and lands it on green CI. This PR then closes, since #1478 carries its commits.
Commits
Round 1
06760993test_nz_target_references.py:test_each_coarse_row_selects_its_native_rows_committed_labels, the feed-free check of R2C-16's membership0506c9d6test_nz_transport_declarations.py:test_each_wage_cut_follows_from_its_committed_label, the feed-free check of each wage cut against its label044440a7test_transport_composed_graph.py:test_declared_scenarios_scale_national_cuts_with_their_own_factorbecomestest_declared_scenarios_scale_the_standin_cuts_with_their_own_factord1d723534a36edcf): eleven PRs the stack lacked, #1472's holdout test among them. Its tree equalsgit merge-tree --write-tree(52aafc74)20507ad8test_transport_holdout_separation.py: NZ's expected S5 bindings gainnz_super(R2C-26)Fix 2 is hub commit
85d55afon #1483. Fix 5 is R2C-27. Fix 6 changes PR bodies only.Round 2
microcosm's CI runs only on PRs into main, so the stack had none until the hub opened the probes. At
20507ad8, #1487 passed lint, wheels, select-countries, integration-uk and both engine-uk and engine-us legs. Both engine-free legs stopped at their first failure (--maxfail=1), intest_calibration_columns.py, after 10,231 passed. #1489 ran every engine-free test at20507ad8without--maxfail=1. Each leg, Python 3.13 and 3.14, failed the same 20 tests, with 24,863 passed, 286 skipped and 3 xfailed. Seventeen sit intest_calibration_columns.py, all from one cause, andf43e8fbbfixes them. The other three, intest_ci_integration_job.py, read the CI workflow itself. They fail only on #1489, whose own commit drops--maxfail=1and switches off the US, UK and integration jobs.f43e8fbbtest_calibration_columns.py: 17 tests fail. 9 raiseInterval outputs need the effective interval document.(B/calibration_columns.py:106-107), 7 expect another refusal and fail "Regex pattern did not match", and 1 Hypothesis test readsrefusedwhere it expectsprepared. Brief 214 counted the 17 at891cb9f1, and #1489 lists the same 17 at20507ad8_bindings()drops the 12 entries of NZ's packaged bindings that carryinterval(G's stand-in band indicators b01 to b12), since these tests pass no interval document; the 257 other entries stay. Newtest_packaged_interval_outputs_need_the_effective_interval_documentpins the refusal on the packaged bindings. With the change the whole file passes (106), so the 12 interval entries cause all 17.Evidence
All runs used the hub's venv (Python 3.14.7, pytest 8.4.2) with
-n 0. Each mutant reproduces the review's.expected_member_count−1test_nz_target_references.py,test_nz_reference_intervals.py,test_nz_spec_package.pyrow_107upper_cent+$1; the builder rewritesearnings_bands.jsonand its--checkagreestest_nz_transport_declarations.py,test_nz_earnings_bands.pynz.v.VSI.reviewonnz.v.VSI, after the stand-in in the row list; on it, a copy of the stand-in that marks every outputrewriteand dropsmeasurement_id; prepare, compile and problem move onto it.tools/build_nz_entitlement_graph.pywrites it, and its--checkagreestest_nz_transport_declarations.py,test_transport_entitlement_graph.pyat85d55aftest_every_calibration_node_has_its_own_standin_as_the_only_writerfails: it findsnz.v.VSI.review_rewritebeside the stand-in among the writers ofperson.ird_taxable_income_proxy_centsinnz.v.VSI.calibrate's lineage. Peak RSS 1.69 GBWithout a mutant, at this head, reading the feed: 1388 passed, none skipped or failed, in 391 s at peak RSS 1.97 GB. The run covers
test_transport_holdout_separation.py(41),test_nz_transport_declarations.py(62),test_nz_target_references.py(61),test_nz_earnings_bands.py(4),test_nz_reference_intervals.py(16),test_nz_spec_package.py(1203) and the renamed test (1).Round 2, in the same venv at
f43e8fbb, one file at a time:test_calibration_columns.py106 passed (30 s, peak RSS 1.46 GB). Three more files that read NZ's packaged resources outside G's closed list:test_be_target_references.py156 passed and 32 skipped (they needBE_CHRONICLE_ARTIFACT),test_spec_source_citations.py138 passed, andtest_spec_engine_reemission.py7 passed with 1 failure that comes from the hub's venv, not the code: its editable install puts a second microcosm checkout on the namespace path, andworker_identity._module_source_indexrefuses the differingcountry_spec.py. CI passed that file at20507ad8. #1487 reruns the full CI atf43e8fbb.Shared files
8e90b9bb380ec92aover3e5f5edb..64ef20af), the same lines in the same order, plus the rename's two. Main's own edits to three of the files moved the context, so the patch-id over4a36edcf..20507ad8reads3025914a344ef3b3.test_transport_holdout_separation.pychanges one line (R2C-26).test_calibration_columns.pyatf43e8fbb::38(the loader becomes_packaged_bindings()),:47-60(_bindings()drops the interval entries) and:101-107(the new refusal test).Notes
bands_sha256is the SHA-256 of the whole file, so any edit moves the node key, but it authenticates nothing (T/population_kernels.py:13-15). CI's tests hold the chain from labels to edges.🤖 Generated with Claude Code