Skip to content

G8-r2c C: measure.combine@1 sums declared signed cent components - #1461

Merged
MaxGhenis merged 17 commits into
mainfrom
nzhub/g8r2c-c-measure
Nov 3, 2026
Merged

MaxGhenis merged 17 commits into
mainfrom
nzhub/g8r2c-c-measure

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

G8-r2c shared hunk C (#1183): measure.combine@1. C holds B's new head b20948d9 (#1459) and lands right after B. Until B lands, C's diff against main shows B's hunks as well as C's.

Head: acfa6b943db08e99e0464a7ba7ee92e54b5f3bc2, after the fix round below.

Landing note. Belgium's hub measured a tree holding hunks A, B, C and D, and both transport.quantile_map@2 and measure.combine@1 read differently there than in this PR's table. So when A and D land after B and C, they move those two rows again. This body quotes no @2 value from that measurement, because F3 (on B) changed @2's hash after it.

Fix round

The review in #1183 comment 6590677926 (APPROVE_WITH_NITS) owed C two tests and a body update. Every file:line is at acfa6b94; the test file is packages/microcosm-build/tests/engine_free/shared/test_transport_measure_kernels.py.

Fix Commit Where Red under its mutant Shared hunk touched
F1: unresolved records stay out of the receipt's masses 6adaf7cb new test_the_receipt_masses_count_resolved_records_only (:209-217), beside the weights test (:179). Five records, {WAGES: [0, None, -5, None, 7]}, weights 1, 1.25, 1.5, 1.75 and 2; it asserts below_zero == {"n": 1, "weighted": 1.5} and at_zero == {"n": 1, "weighted": 1.0}. The code under test is T/measure_kernels.py:250-251 and :264-265 C-X2 (:251 → zero = [i for i in range(size) if measures[i] in (0, None)]) and its twin (:250 → negative = [i for i in range(size) if measures[i] is None or measures[i] < 0]): that test, 1 failed under each C's test file only
F2: measurement_id's type check cdcb985a new test_a_measurement_id_that_is_not_a_non_empty_string_is_refused (:285-300), parametrized over 123, "", True and params={"measurement_id": None}. Each asserts TypeError and the exact message "measure.combine@1 parameter 'measurement_id' must be a non-empty string." (T/graph_inputs.py:85-91). The code under test is T/measure_kernels.py:309-313 C-X1 (:310 → context.params["measurement_id"]): all four cases, 4 failed C's test file only
Merge B's new head acfa6b94 git merge --no-ff b20948d9, which brings in F3's one word at B/tail_estimators.py:25 — B's B/tail_estimators.py:25, by the merge
F4: this body this body the first lines, "Landing note", "Implementation hashes", "Tests" and the line refs into the test file Text only none

The merge check. I stripped the +/- lines from git diff -U0 c9fa6639 10694ec0 and from git diff -U0 b20948d9 acfa6b94 and compared them with diff. The only difference is the two new tests' lines, so C's head differs from B's by C's hunks, C's earlier fixes and F1 and F2, and nothing else.

Does measurement_id enter the node's key? Yes (Read). The kernel reads it from Node.params (microcosm/graph/decl.py:492). Node.normative() (decl.py:621-636) projects every field except DESCRIPTIVE_FIELDS, which is {"description", "citation"} (decl.py:82), so it keeps params. node_key hashes normative(node) as its second part (microcosm/graph/keys.py:288-290). So two nodes that differ only in measurement_id get different keys. No test here checks that.

Merge-up

  • Head: 10694ec0a2960cb104cce5a35154c6eaabb60ba9 (was c29e4871).
  • Merged: B's new head c9fa6639, in merge commit 10694ec0, with git merge --no-ff. That brings in w4d fcdb47395293955347ec2697feef1a736a76cc23 (the G8-w4 fix round), B's two test fixes for that merge, and B's review fixes; G8-r2c B: transport.quantile_map@2 with a fitted grouped Pareto top band #1459's "Merge-up" section lists them. Nothing was rebased or force-pushed.
  • What the merge changed. Git auto-merged T/compose.py; nothing conflicted.
  • Checked. git diff c9fa6639 10694ec0 touches only C's files: T/measure_kernels.py, T/compose.py:193, T/registry.py:53, :536 and :547, test_transport_measure_kernels.py and test_transport_population_kernels.py:143 and :151. So C's head differs from B's by C's hunks and C's review fixes, and nothing else.

Review fixes, earlier round

The review (#1183 comment 6585875953, APPROVE_WITH_NITS) owed C two test fixes and one text change. Every file:line is at this head.

Fix Commit Where Red under its mutant
Review fix 1: test the receipt's weighted fields ae7c3933 test_the_receipt_weights_each_record_by_its_frame_weight, test_transport_measure_kernels.py:179. Five records with frame weights 1, 1.25, 1.5, 1.75 and 2; it checks each component's weighted_signed_total, below_zero.weighted and at_zero.weighted against sums written out by hand C-N1 (T/measure_kernels.py:244-247) and C-N2 (:264-265): that test
Review fix 2: test four node-shape refusals ae7c3933 test_node_shape_refusals (:362 at acfa6b94, :333 at ae7c3933) now refuses a masked slice, outputs other than the declaration's three (one extra, one renamed) and a rewrite output; test_malformed_declarations_are_refused (:330 at acfa6b94) refuses a floor of 2^63 and of −2^63 − 1 (:314-315) C-N3 (:339), C-N4 (:315-316) and C-N5 (:331-335): test_node_shape_refusals. C-N6 (:140-141): both new floor cases
Review fix 3: state the universe rule as R2C-14 this body "Judgment calls" below Text only

The change

measure.combine@1, in the new T/measure_kernels.py (T/ is packages/microcosm-build/src/microcosm/build/transport/), sums declared signed integer-cent components on one entity into one measure.

  • Declared, not coded. The measure parameter is a canonical JSON declaration with measure_sha256. It names the entity, each component column and its sign (±1), the add-back columns, the floor, the scale and the three output column names (measure, unit, universe_status). validate_measure_declaration (T/measure_kernels.py:88-161) checks its shape. The module holds no column name, floor, scale or float literal, so NZ's floor 0 and scale 100 will live only in NZ's declaration (R2C-10).
  • The arithmetic (combine_measure, :177-274), in Python integers per record:
    • x = Σ sign_k · c_k + Σ over add-backs of max(0, −sign_j · c_j). An add-back component then counts as max(0, r), so its losses aren't deducted (design §3's rental rule).
    • Status included where x > 0 and excluded where x ≤ 0, whatever floor the declaration names (R2C-14). Where any component is null, the status is unresolved with a null measure and a NaN unit, never zero. The three labels are UNIVERSE_STATES, imported from B/reference_intervals.py:79, which r2b's classifier reads.
    • A measure outside int64 is refused as int64_overflow (:217-221).
  • The outputs (run, :295-384):
    • the measure in cents, as Int64, or as int64 when the node declares it and no record is unresolved (otherwise refused, :352-356);
    • the unit column, max(floor, x) / scale, by Python's correctly rounded int / int;
    • the universe status, as string.
  • The receipt has, per component, its signed weighted and unweighted totals and its null count, plus each add-back's total, Σ x, the status counts, the count and weighted mass at x < 0 and at x = 0, the floor, the scale and the output names. Before anything is written, the kernel checks that Σ components + add-backs = Σ x exactly.
  • measurement_id. The node may declare it. The kernel copies it into receipt["measurement_id"] and computes nothing from it. Hunk A's composition proof reads the node parameter.
  • Registration. T/compose.py SKELETON_KERNELS and T/registry.py.

Invariants, and the test that checks each

All tests are in packages/microcosm-build/tests/engine_free/shared/test_transport_measure_kernels.py.

Invariant Test
Σ components + add-back = x exactly, per record and in the receipt test_property_components_plus_add_back_equal_the_measure (Hypothesis)
The universe statuses partition the records: unresolved iff a component is null, included iff x > 0 test_property_the_universe_statuses_partition_the_records (Hypothesis)
The output doesn't depend on component order test_property_the_output_does_not_depend_on_component_order (Hypothesis)
unit = max(floor, x) / scale for any declared floor and scale, correctly rounded test_property_the_unit_is_max_floor_x_over_scale (Hypothesis), test_the_floor_and_scale_come_from_the_declaration
The measure equals an independent Python-integer sum test_property_differential_against_an_independent_integer_sum (Hypothesis differential)
Losses net; a negative sign subtracts; the add-back drops an add-back component's losses test_losses_net_against_gains, test_a_negative_sign_subtracts, test_the_add_back_ignores_an_add_back_components_losses
x ≤ 0 sits outside the positive universe test_records_at_or_below_zero_sit_outside_the_positive_universe
An int64 overflow is refused by name test_an_int64_overflow_is_refused_by_name; each property test also checks that a refusal happens only when the independent sum leaves int64
A null input leaves the record unresolved test_a_null_component_leaves_the_record_unresolved_not_zero
The measurement id is carried and changes no output test_the_measurement_id_is_carried_not_used
The receipt's weighted totals and masses use each record's frame weight test_the_receipt_weights_each_record_by_its_frame_weight
The receipt's x < 0 and x = 0 masses count resolved records only test_the_receipt_masses_count_resolved_records_only
A measurement_id that isn't a non-empty string is refused test_a_measurement_id_that_is_not_a_non_empty_string_is_refused
The node must slice whole rows, write exactly the declaration's three outputs and rewrite none of them test_node_shape_refusals
The declared floor must fit int64 test_malformed_declarations_are_refused

Mutants

All 18 turn tests red; the last three (C-X1, C-X2 and its twin) are this round's, run at cdcb985a. The first 9 ran at c29e4871. C-N1 to C-N6 are the review's (#1183 comment 6585875953); I ran each against test_transport_measure_kernels.py as ae7c3933 commits it, and each one turns only the test named red. Their file:line refs are at this head; T/measure_kernels.py is unchanged since c29e4871.

Mutant Red tests (a selection)
Treat a null component as zero test_a_null_component_leaves_the_record_unresolved_not_zero and 5 property tests
Drop the add-back test_the_add_back_ignores_an_add_back_components_losses, test_property_differential_against_an_independent_integer_sum
Drop the overflow refusal test_an_int64_overflow_is_refused_by_name
Count x = 0 as included test_records_at_or_below_zero_sit_outside_the_positive_universe, test_property_the_universe_statuses_partition_the_records
Hardcode floor 0 and scale 100 test_the_floor_and_scale_come_from_the_declaration, test_property_the_unit_is_max_floor_x_over_scale
Ignore the declared sign test_a_negative_sign_subtracts and 6 more
Drop the measurement id test_the_measurement_id_is_carried_not_used
Drop the kernel from SKELETON_KERNELS test_measure_combine_is_registered_in_the_skeleton
Drop its registration test_measure_combine_is_registered_in_the_skeleton
C-N1: weighted_signed_total ignores the weights (T/measure_kernels.py:244-247) test_the_receipt_weights_each_record_by_its_frame_weight
C-N2: below_zero.weighted sums the x = 0 set (:264-265) test_the_receipt_weights_each_record_by_its_frame_weight
C-N3: allow a rewrite output (:339) test_node_shape_refusals
C-N4: drop the whole-row slice check (:315-316) test_node_shape_refusals
C-N5: drop the check that the outputs equal the declaration (:331-335) test_node_shape_refusals
C-N6: drop the floor's int64 range check (:140-141) test_malformed_declarations_are_refused, both new floor cases
C-X1: skip measurement_id's type check (:310 → context.params["measurement_id"]) test_a_measurement_id_that_is_not_a_non_empty_string_is_refused, all four cases
C-X2: the x = 0 mass takes unresolved records (:251) test_the_receipt_masses_count_resolved_records_only
C-X2's twin: the x < 0 mass takes unresolved records (:250) test_the_receipt_masses_count_resolved_records_only

Shared hunks (file:line at head acfa6b94)

C's own hunks, which git diff b20948d9 acfa6b94 shows:

  • New T/measure_kernels.py:1-393.
  • T/compose.py:193 (SKELETON_KERNELS).
  • T/registry.py:53 (import), :536 (docstring count), :547 (registration).
  • New test packages/microcosm-build/tests/engine_free/shared/test_transport_measure_kernels.py:1-575 (:1-546 before F1 and F2 added 29 lines).
  • packages/microcosm-build/tests/engine_free/shared/test_transport_population_kernels.py:143 and :151: the registration pin now lists measure.combine@1 and says sixteen.

B's hunks sit at these lines at this head: B/tail_estimators.py:1-375, T/quantile_kernels.py:1-158, T/compose.py:184, :220-221, :848-851, :947-957, :1020 and :1637, T/registry.py:57 and :543, test_transport_quantile_map_v2.py:1-806 and test_transport_population_kernels.py:157. F3 moved none of these lines. git diff fcdb4739 acfa6b94 shows B's and C's hunks and nothing else.

Judgment calls

  • Universe threshold: R2C-14. The hub ruled on this in Activate the NZ graph against pinned Chronicle facts (NZ graph G8) #1183 comment 6585963115 (R2C-14). measure.combine@1 marks a record included at x > 0, excluded at x ≤ 0 and unresolved on any null component, whatever floor the declaration names. The floor bounds only the unit column, max(floor, x)/scale. Design §3 sets the x > 0 rule and names no floor; R2C-10 moved the floor into the declaration and left the universe alone. NZ declares floor 0, so the two readings agree for NZ. A country that needs zero or loss records inside the universe asks for a declared universe field, which takes a new ruling.
  • Status labels. The kernel imports r2b's UNIVERSE_STATES rather than spelling them again, so its hash digests B/reference_intervals.py. Hunk D edits that file, so D will move measure.combine@1's hash too.
  • Measure dtype. Int64 carries unresolved records as null. The kernel accepts int64 only when none is unresolved, so a null never turns into a zero.

Implementation hashes

I computed implementation_hash() for every kernel the engine-free toy registry installs: make_composed_fixture in test_support/microcosm_build/transport_composed.py, whose build_transport_registry call installs the population, geography, target, gate, take-up, reporting and evidence kernels plus the three simulate.* rule kernels for its toy engines. That's 43 kernels at main and at fcdb4739, 44 at B and 45 at C. The earlier tables left out the three simulate.* rows; none of them moves. For 312dc4d6 I installed microcosm-emitter-interface==0.7.0, from its uv.lock, into a scratch target on PYTHONPATH, not into the venv. Each worktree's src directories came first on PYTHONPATH, and I printed microcosm.build.__path__ to confirm it.

Against the intake heads, only transport.quantile_map@2 moves (F3, through the merge of B). F1 and F2 add tests only, so measure.combine@1 stays at 8636ac082235b979. Hunk D edits B/reference_intervals.py, which this kernel digests, so D will move it (see "Landing note").

gates.battery@1 moves on main, not here. Its hash digests the source closure of its gate bindings (binding_identity.describe, T/gate_kernels.py:740, hashed at :771-775), and three main merges after fcdb4739 changed modules in that closure: #1410's a6bcd1ab (B/staging.py, B/staging_storage.py, B/us_runtime/worker_identity.py), #1428's e226192a (B/source_manifest.py) and #1051's 4b1b1b10 (B/us_runtime/multispine_pool.py). I found them by diffing the closure's per-module digests at fcdb4739 and 312dc4d6 and running git log --first-parent --oneline fcdb4739..312dc4d6 on those five files.

Kernel main 312dc4d6 w4d fcdb4739 B c9fa6639 B b20948d9 C 10694ec0 C acfa6b94 Why
calibrate.ordered_adam@1 0d4577effa147ebe 0d4577effa147ebe 0d4577effa147ebe 0d4577effa147ebe 0d4577effa147ebe 0d4577effa147ebe unchanged
calibration.prepare@1 c13e76497b8d2dfb c13e76497b8d2dfb c13e76497b8d2dfb c13e76497b8d2dfb c13e76497b8d2dfb c13e76497b8d2dfb unchanged
concepts.encode@1 7815fae8ed573af6 7815fae8ed573af6 7815fae8ed573af6 7815fae8ed573af6 7815fae8ed573af6 7815fae8ed573af6 unchanged
concepts.encode_groups@1 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab unchanged
diagnostics.calibration@1 d38f8fad075a9ace d38f8fad075a9ace d38f8fad075a9ace d38f8fad075a9ace d38f8fad075a9ace d38f8fad075a9ace unchanged
export.prepare@1 af31fa6e1690b90c af31fa6e1690b90c af31fa6e1690b90c af31fa6e1690b90c af31fa6e1690b90c af31fa6e1690b90c unchanged
export.readback@1 96be393c760ca768 96be393c760ca768 96be393c760ca768 96be393c760ca768 96be393c760ca768 96be393c760ca768 unchanged
gates.battery@1 5893a7fbe1976856 29f0505b6a0093f8 29f0505b6a0093f8 29f0505b6a0093f8 29f0505b6a0093f8 29f0505b6a0093f8 main's own move: #1410's a6bcd1ab, #1428's e226192a and #1051's 4b1b1b10 changed first-party modules in the battery's binding source closure. B and C move nothing
geography.assign_atomic@1 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 unchanged
geography.derive@1 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 unchanged
geography.gate@1 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 unchanged
geography.person_location@1 3efcbe3741d04497 3efcbe3741d04497 3efcbe3741d04497 3efcbe3741d04497 3efcbe3741d04497 3efcbe3741d04497 unchanged
geography.support_from_facts@1 60273ea8388098b8 60273ea8388098b8 60273ea8388098b8 60273ea8388098b8 60273ea8388098b8 60273ea8388098b8 unchanged
geography.support_import@1 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 d60f7ec388903678 unchanged
measure.combine@1 — — — — 8636ac082235b979 8636ac082235b979 new in C; unchanged by this round
money.annual_transfers@1 c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe unchanged
money.minor_units@1 c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe c8a067cd750ed2fe unchanged
money.normalize@1 f10fbeeaf87467b6 f10fbeeaf87467b6 f10fbeeaf87467b6 f10fbeeaf87467b6 f10fbeeaf87467b6 f10fbeeaf87467b6 unchanged
reporting.carry_income_decile@1 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 unchanged
reporting.income_decile@1 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 unchanged
reporting.receipt_group@1 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 80335f0183c27410 unchanged
simulate.counterfactual@1 d54638a3d73a25f1 d54638a3d73a25f1 d54638a3d73a25f1 d54638a3d73a25f1 d54638a3d73a25f1 d54638a3d73a25f1 unchanged
simulate.rules_by_ref@1 5806634ec3d9c944 5806634ec3d9c944 5806634ec3d9c944 5806634ec3d9c944 5806634ec3d9c944 5806634ec3d9c944 unchanged
simulate.solve_zero@1 45fd2ffdc8cdcabe 45fd2ffdc8cdcabe 45fd2ffdc8cdcabe 45fd2ffdc8cdcabe 45fd2ffdc8cdcabe 45fd2ffdc8cdcabe unchanged
takeup.assign@1 53caf4ae91fc4acb 53caf4ae91fc4acb 53caf4ae91fc4acb 53caf4ae91fc4acb 53caf4ae91fc4acb 53caf4ae91fc4acb unchanged
takeup.bands@1 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 unchanged
takeup.cell_diagnostics@1 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 1ceca3ba7c7c9b92 unchanged
takeup.compare@1 a318a19b0d147ec9 a318a19b0d147ec9 a318a19b0d147ec9 a318a19b0d147ec9 a318a19b0d147ec9 a318a19b0d147ec9 unchanged
takeup.gap@1 3e15ad5e2903c31c 3e15ad5e2903c31c 3e15ad5e2903c31c 3e15ad5e2903c31c 3e15ad5e2903c31c 3e15ad5e2903c31c unchanged
targets.compile@1 f3d372c40418665b f3d372c40418665b f3d372c40418665b f3d372c40418665b f3d372c40418665b f3d372c40418665b unchanged
targets.problem@1 aed111cadf163873 aed111cadf163873 aed111cadf163873 aed111cadf163873 aed111cadf163873 aed111cadf163873 unchanged
transport.boundary@1 fc9d10670f6a092f fc9d10670f6a092f fc9d10670f6a092f fc9d10670f6a092f fc9d10670f6a092f fc9d10670f6a092f unchanged
transport.create@1 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 unchanged
transport.create@2 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 82dc0880aefba156 unchanged
transport.currency@1 9c3457ddb908aa96 9c3457ddb908aa96 9c3457ddb908aa96 9c3457ddb908aa96 9c3457ddb908aa96 9c3457ddb908aa96 unchanged
transport.donor_ranges@1 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 unchanged
transport.donor_receipt@1 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 62def362139a52e4 unchanged
transport.input_closure@1 23134112061495b7 23134112061495b7 23134112061495b7 23134112061495b7 23134112061495b7 23134112061495b7 unchanged
transport.package@1 e86be552287a6eb3 e86be552287a6eb3 e86be552287a6eb3 e86be552287a6eb3 e86be552287a6eb3 e86be552287a6eb3 unchanged
transport.quantile_map@1 0966a4eb8a492216 0966a4eb8a492216 0966a4eb8a492216 0966a4eb8a492216 0966a4eb8a492216 0966a4eb8a492216 unchanged
transport.quantile_map@2 — — aa0fd5e4e532264a 40b17ba56c16b60f aa0fd5e4e532264a 40b17ba56c16b60f new in B. F3's one word in B/tail_estimators.py:25 moved it from aa0fd5e4e532264a, because @2 digests tail_estimators
transport.scenario_override@1 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab 062f88411ad605ab unchanged
transport.support_bounds@1 2303c60a7ec6135c 2303c60a7ec6135c 2303c60a7ec6135c 2303c60a7ec6135c 2303c60a7ec6135c 2303c60a7ec6135c unchanged
transport.unit_attributes@1 f50ec74a1bfe35bb f50ec74a1bfe35bb f50ec74a1bfe35bb f50ec74a1bfe35bb f50ec74a1bfe35bb f50ec74a1bfe35bb unchanged
transport.weights_audit@1 1f3d475013456127 1f3d475013456127 1f3d475013456127 1f3d475013456127 1f3d475013456127 1f3d475013456127 unchanged

Tests

Closed list at head acfa6b94, with -n 4 and no --maxfail. All files are in packages/microcosm-build/tests/engine_free/shared/.

File Passed Skipped Failed
test_transport_quantile_map_v2.py 38 0 0
test_transport_measure_kernels.py 37 0 0
test_transport_population_kernels.py 32 0 0
test_transport_graph.py 15 0 0
test_transport_evidence_kernels.py 151 0 0
test_country_spec.py 143 0 0
Total 416 0 0

The run reported 416 passed, with no skip and no xfail in its JUnit XML. test_transport_measure_kernels.py went from 32 to 37: F1 adds one test and F2 adds four cases. test_transport_graph.py's guards against a float literal or a country name in any T/*.py pass. At this head, python tools/ci_test_plan.py verify prints verification=ok, and uv run --no-sync ruff check . prints "All checks passed!".

At 10694ec0, the previous round ran a wider list of 53 files: 4156 passed and 86 skipped, each skip for a pinned NZ feed, pinned geography bytes, the Axiom dense engine or the real donor. This round didn't rerun those files.

https://claude.ai/code/session_01LwQq6FfhfP7EBWSvpa8Wv4


Generated by Claude Code

grouped_pareto_mle@1, in the new microcosm.build.tail_estimators, fits a
Pareto alpha by maximum likelihood to contiguous anchor bands and the open
top. It bisects the declared bracket for the declared iteration count,
rounds once to six decimals half-even, carries alpha as p/10**6, and
refuses a bracket without a sign change and an alpha at or below one by
name. Its receipt records the anchors, their fact references and counts,
the bracket, the iterations, l' at the final bracket, alpha and the
implied top-band mean alpha*c/(alpha - 1).

transport.quantile_map@2, in the new transport/quantile_kernels.py, reads
the anchor counts from the same compiled band surface _surface_bands
reads, then delegates to quantile_map@1's own run with the fitted alpha as
a literal. The new module keeps @1's source hash, and every registered
kernel's implementation hash is unchanged. Composition lists @2 beside @1
in SKELETON_KERNELS, the money producer sets and _SUPPORT_PRODUCERS
(quantile_mapped), and the registry installs it with the population
kernels.

Invariants: l' is strictly decreasing, so the bisection finds the one
root; exact Pareto expected counts recover alpha within 1e-6; scaling the
counts leaves alpha unchanged; moving mass upward lowers alpha; @2's map
is bitwise @1's at the fitted alpha; Belgium's RSZ counts give 3.348955.
Hypothesis tests check each, and each of 13 mutants turns a test red.
The bare-band-list support test is xfail(strict) until the w4 fix round's
E1 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
The new transport/measure_kernels.py sums declared signed integer-cent
components on one entity in Python integers, adds back the losses of each
declared add-back component (so it counts max(0, r)), and writes three
columns the declaration names: the measure in cents, a unit column equal
to max(floor, x) / scale, and a universe status. The declaration document
holds the entity, components, signs, add-backs, floor, scale and output
names, so the kernel holds no column name, floor or scale of its own.

A null component leaves the record unresolved (null measure, NaN unit),
never zero; x <= 0 is excluded from the positive universe; a measure
outside int64 is refused by name. An optional measurement_id is carried
into the receipt for hunk A's composition proof. Composition lists the
kernel in SKELETON_KERNELS and the registry installs it; no existing
kernel's implementation hash moves.

Invariants: components plus add-backs equal x exactly; the statuses
partition the records; the output does not depend on component order; the
unit column equals max(floor, x) / scale for any declared floor and scale;
the measure equals an independent Python-integer sum. Hypothesis tests
check each, and each of 9 mutants turns a test red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
ruff's isort rule wants the measure kernels' import in order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
register_transport_population_kernels installs fifteen kernels now that
transport.quantile_map@2 joins them. The docstring said thirteen on the
base, while the test pinned fourteen; both now say fifteen. The pin
lists the refs, not hashes, so no implementation hash moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
Brings B's fifteen-kernel registration pin (a401e22) into C. The merge
changes only test_transport_population_kernels.py; the next commit counts
measure.combine@1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
register_transport_population_kernels installs sixteen kernels with
measure.combine@1. The pin lists refs, not hashes, so no implementation
hash moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N2tyHSTU4AFqsmoXwaTAXH
This merges the G8-w4 fix round's w4d head
fcdb473 into G8-r2c hunk B. Git found no
conflicts, so T/compose.py carries both sides' additions and the fix
round's text in the code it owns.

Invariant: the merge adds B's hunks to w4d and nothing else. The changed
lines of `git diff fcdb473 HEAD` equal those of `git diff d81280b
a401e22`, line for line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since w4c, quantile_support returns the frozen QuantileSupport dataclass, which has no __iter__, so the old tuple unpack in test_the_w4_support_producer_reads_v2_params_with_an_open_top raised TypeError after the merge. The test now binds the dataclass, compares support.positive with a tuple of tuples, asserts that a bare band list feeds both signs (support.negative == support.positive), and passes both fields to quantile_bounds.

The fix round's E1 makes a bare band list's support read on both signs, so test_v2_support_bounds_both_signs_for_a_bare_band_list XPASSed strictly. This removes its xfail mark; the assertion is unchanged.

Checked: test_transport_quantile_map_v2.py passes, 37 of 37.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_property_one_band_and_the_top_match_the_closed_form returned early when the closed-form alpha fell outside 1.01 to 63, and test_property_v2_is_bitwise_v1_at_the_fitted_alpha returned early when the estimator refused. Hypothesis counted each return as a passing example. Each now calls hypothesis.assume, so Hypothesis replaces the draw and its health check would report a filter that rejects too much. test_property_the_score_changes_sign_once_on_the_bracket had the same guard, and it calls assume too.

Checked with --hypothesis-show-statistics and a temporary hypothesis.event at each final assertion (not committed). Before, 30 of 60 counted examples reached the closed-form assertion and 44 of 60 reached the differential. After, all 60 of 60 reach it in each of the three properties (109, 14 and 7 invalid draws replaced), and no health check fired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review's mutant B-M1 (half-up in place of half-even in _round_half_even, B/tail_estimators.py:166) left every test green, because no test rounds a value lying exactly on a tie. test_six_decimal_rounding_is_half_even_at_an_exact_tie rounds Fraction("1.0078125") to 1.007812 and Fraction("1.0078135") to 1.007814, and two values just off the tie to their nearer neighbour.

Checked: with the condition changed to remainder >= half, the new test fails and the other 37 tests in the file pass; with the code restored, all 38 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the r2c bracket [1 + 2**-20, 64] the rounded alpha is at least 1.000001, so alpha_not_above_one cannot fire, and heavy-tail data whose root lies at or below one is refused as bracket_without_sign_change. The module docstring now says so. The code doesn't change; both cases still refuse, so R2C-9 holds.

The docstring is part of the module source that transport.quantile_map@2's implementation_hash digests, so that new row's hash moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review's mutants C-N1 to C-N6 left every test in test_transport_measure_kernels.py green. This adds the tests that catch each one.

Review fix 1: test_the_receipt_weights_each_record_by_its_frame_weight runs five records with frame weights 1, 1.25, 1.5, 1.75 and 2, and checks each component's weighted_signed_total, below_zero.weighted and at_zero.weighted against sums written out by hand. C-N1 (weighted_signed_total ignores the weights, T/measure_kernels.py:244-247) and C-N2 (below_zero.weighted sums the x = 0 set, :264-265) each turn it red.

Review fix 2: test_node_shape_refusals now also refuses a masked slice, outputs other than the declaration's three (an extra one and a renamed one) and a rewrite output; test_malformed_declarations_are_refused refuses a floor of 2**63 and of -2**63 - 1. C-N3 (allow a rewrite output, :339), C-N4 (drop the whole-row check, :315-316) and C-N5 (drop the outputs check, :331-335) turn test_node_shape_refusals red, and C-N6 (drop the floor's int64 check, :140-141) turns both new floor cases red.

Checked: with the kernel unchanged the file passes, 32 of 32; each mutant fails only the test named above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This brings G8-r2c hunk B's merge-up into hunk C: the w4 fix round's
w4d head fcdb473, B's two test fixes for that merge, and B's review
fixes. Git auto-merged T/compose.py; nothing conflicted.

Invariant: C's head differs from B's head by C's own hunks and C's
review fixes only. `git diff c9fa663 HEAD` touches T/measure_kernels.py,
T/compose.py's SKELETON_KERNELS line, T/registry.py's import, count and
registration, test_transport_measure_kernels.py and the registration
pin in test_transport_population_kernels.py, and nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis changed the base branch from nzhub/g8r2c-b-pareto-map to main November 2, 2026 23:11
The tail estimator module docstring said the receipt names the
bracket_without_sign_change reason. A refusal writes no receipt:
TailEstimatorRefusalError raises from grouped_pareto_alpha before
transport.quantile_map@2 delegates to @1, and its message and code carry
the reason. This changes that one word and nothing else; the replacement
has the same length, so no line moves.

The change moves transport.quantile_map@2's implementation hash, because
that hash digests tail_estimators. I recomputed implementation_hash() for
all 44 kernels the engine-free toy registry installs, before and after;
only transport.quantile_map@2 moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FfhfP7EBWSvpa8Wv4
measure.combine@1 builds below_zero and at_zero from the resolved records
only (measure_kernels.py:250-251), but no test asserted those masses on a
frame with an unresolved record, so the review's mutant C-X2 (count every
record whose measure is 0 or None as at zero) kept all tests green.

The new test runs a five-record frame, {WAGES: [0, None, -5, None, 7]},
whose _frame weights are 1, 1.25, 1.5, 1.75 and 2, and asserts
below_zero == {n: 1, weighted: 1.5} and at_zero == {n: 1, weighted: 1.0}.
It sits beside the weights test rather than extending it, because a sixth
record there would change every weight that test checks by hand.

I ran C-X2 and its twin on line 250 (count None as below zero); the new
test fails under each and passes on the unmutated source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FfhfP7EBWSvpa8Wv4
measure.combine@1 reads measurement_id through string_param
(measure_kernels.py:309-313), which refuses anything but a non-empty
string. No test reached that check, so the review's mutant C-X1 (read
context.params["measurement_id"] directly) kept every test green.
R2C-13 makes this the key hunk A's proof reads.

The new parametrized test passes 123, "", True and an explicit null and
asserts a TypeError whose message is exactly "measure.combine@1 parameter
'measurement_id' must be a non-empty string." (graph_inputs.py:85-91).
I ran C-X1; all four cases fail under it and pass on the unmutated
source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FfhfP7EBWSvpa8Wv4
B's head b20948d carries F3, the one-word docstring fix in
tail_estimators.py. The merge brings that line into C and nothing else.
I checked that the changed lines of git diff -U0 b20948d HEAD equal
those of git diff -U0 c9fa663 10694ec plus the F1 and F2 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FfhfP7EBWSvpa8Wv4
@MaxGhenis
MaxGhenis marked this pull request as ready for review November 3, 2026 01:02
@MaxGhenis
MaxGhenis merged commit ea8f409 into main Nov 3, 2026
10 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged by the NZ hub (session local_e407a527) at acfa6b9 with --merge. Gates: gh pr checks exit 0; MERGEABLE; not draft; no CHANGES_REQUESTED review. Reviewed head: acfa6b9. Reviews on #1183: 6590677926 (APPROVE_WITH_NITS; fixes landed at acfa6b9) and delta review 6591604580 (APPROVE_WITH_NITS; the nit on this body is fixed). Belgium's hub OK'd this head. C lands after B, because its CI tests main + B + C. A and D's landing moves transport.quantile_map@2 and measure.combine@1 again: Belgium's hub measured 76c81605aa416dc9 and d609832882ffc113 on a tree holding A to D.

MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
Brings in #1459 and #1461 (ea8f409), which landed after the first merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Jmpqc2fSZAM219btF
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.

2 participants