Repository navigation
Encode person_role bindings and their closure defaults on the person table (S2b PR 2) - #1480
Conversation
A person_role binding feeds an engine entity with one row per person in a role, such as Axiom's Child. `encode` deferred every binding with a group rule, and `encode_groups` deferred person_role, so no step ever wrote these inputs. Belgium's 35 Child bindings (27 identities on age and 8 predicates on education enrolment, across 11 modules) had no column. `is_executable` now admits person_role. `encode` writes each such binding on the person table through its existing line, one value per person, and `encode_groups` skips it. Validation already requires a person_role binding to read person concepts. Bindings on entities without built units stay deferred. Two tests pinned the old deferral and now pin the new one. A new property checks each person's own value for an identity and a predicate, that neither encoder defers them, and that adding them changes no other binding's output. The row-by-row reference differential now counts Belgium's 35 bindings. No decoder changes in any packaged mapping, and the NZ, US and UK mappings hold no person_role binding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0165b3d to
6bebc5e
Compare
encode writes each person_role binding to the person table, beside the engine's Person bindings. _check_shared_names compared shared names per engine entity, so it admitted a Child binding and a Person binding, or two person_role entities, that share a name but compute it differently, and encode kept whichever came last. It now compares per table written: executable bindings per concept entity, group bindings per engine entity as before. Bindings that compute a shared column alike still pass. A test refuses an age identity and an enrolment predicate that share a person column, in both orders. A property checks that bindings writing one person column pass, in any order, exactly when they compute it alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
concepts.encode@1 gathered closure defaults only for the engine entities in the mapping's entity_correspondence, so a defaulted root on a person_role entity such as Axiom's Child reached no table. It now adds the whole mapping's person_role entities to the person table's, beside their bindings. It merges every entity's defaults for one table before it applies them, so two that agree share one column and two that disagree refuse. A selected path may now name a module that the closure lists and the mapping doesn't bind, since a module may default every input it reads. Validation refuses an engine entity that mixes person_role with other group rules: its rows are persons in a role or built units, not both. Tests cover a Child default that reaches the person table, agreeing and disagreeing Person and Child defaults in both orders, a Child default on an encoded column, and a module that only defaults. A property checks that, without person_role defaults, the kernel writes what the old gather wrote. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6bebc5e to
f905863
Compare
|
S2b PR 2 review (brief 6036): APPROVE Reviewed head FindingsP3-1. A graph can still put a P3-2. The differential property tests only the non- P3-3. The path rule is worded loosely in two docstrings. The module docstring and P3-4. Claims I tried to break
MutantsOver 7 files, 624 tests:
No mutant survived. Other claims confirmed
Not runCI; the engine-us and engine-uk suites; the full engine-free suite (704 files); the US worker pool itself, since I only computed the identity; NZ output bytes before vs after (the NZ hub's ask 6); and any Generated by Claude Code |
…ather Applies the four optional P3s from the S2b PR 2 review (brief 6036): - P3-1: concepts.encode@1 writes a person_role entity's bindings and defaults on the person table, but concepts.encode_groups@1 and transport.scenario_override@1 still took one as their engine_entity and wrote its closure defaults on a unit table too. _group_inputs now refuses it. test_group_kernels_refuse_a_person_role_entity checks both kernels, with Family on the same mapping as the control. - P3-2: the differential property holds no defaulted Child root, so it checks only the gather's non-Child part. test_person_and_child_defaults_merge_on_the_person_table_or_refuse draws Person and Child defaults and the gather order. Agreeing defaults share one person column; ones that differ in type or value refuse. - P3-3: the module docstring and _full_mapping now state the path rule exactly. With a closure, each selected path names a module the closure lists; without one, a module the mapping binds. - P3-4: a comment in ConceptsEncodeKernel.run records that only person_role bindings add an entity to the person table's defaults. Only column_kernels.py and its tests change, so the kernels that move are the ones that already move from main, and the US worker identity stays put. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
S2b PR 2 delta review (brief 6038), draft Reviewing head
Generated by Claude Code |
|
S2b PR 2 delta review, restart (brief 6038R): APPROVE Restart of brief 6038, finishing its review of head Merges into current main. FindingsP3-1. The merge property pins the type check only now and then. Only P3-2. No test pins the guard's whole-mapping read. P3-3 (6038's P3, re-run; I agree). The guard covers The commit message's claims
MutantsOn
Not run
Generated by Claude Code |
G landed on main as f8f543a after this PR's last CI run, which tested 8a4e7a4 merged into main 79a533c. GitHub's merge ref still has 79a533c as its first parent, so this merge starts CI on main as it now stands. G and this PR change no file in common, and git merge-tree writes tree 5c0a6b9 with no conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven PRs reached main after this PR's last CI run, which tested 52fc34c on main f8f543a: #1345, #1493, #1467, #1426, #1465, #1391 and #1473. None of them changes a file this PR changes, and git merge-tree writes tree ed52268 with no conflict. This merge starts CI on main as it now stands, including the new installed-emitter job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three PRs reached main after this PR's last CI run, which tested 17e2643 on main 0f0e777: #1506, #1485 (H2) and #1501. None of them changes a file this PR changes, and git merge-tree writes tree 79ee630 with no conflict. H2 adds an NZ coresident binding and teaches test_support's reference concepts the coresident_adult transform, which the concept-mapping differential beside this PR's encode change reads, so this merge has CI test the two together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Landed as squash At merge time:
|
Since the merge base 875506e, main gained #1473, #1506, H2 (#1485), #1501, #1480 and #1488. The merge is clean. Two files changed on both sides, in separate hunks: compose.py (#1488 on main's side) and test_transport_evidence_kernels.py (H2 on main's side). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…losure (S2b PR 1) (#1482) * Add binding-local views to the Axiom adapter AxiomEngine takes an optional views sequence of AxiomView declarations: a logical engine entity no frame entity maps to (rulespec-be's Child), one bool person selector column, and an outside value per output. The adapter gathers the selected person rows in table order, runs the module's program on the logical entity, checks the engine's array against the selected count, and scatters it back through the same mask; every other row holds the outside value, cast through the output's graph dtype. An empty selection makes no engine call, and a null or non-bool selector refuses. View outputs resolve to the person entity, so callers that key on (binding entity, variable) read them unedited. Each view's program compiles under its own cache key, re-checks the pinned RuleSpec tree first, refuses dense relations, and contributes its derived metadata, so a Child-only module has metadata. The selector joins the person entity's relation_dependencies; view_root_inputs() reports each view's roots. axiom_engine_ref writes views only when present, so every existing reference is unchanged. The view code stays in axiom.py, because source_hash hashes defining files and does not follow imports. * Partition binding-local view roots in the input closure closure_partition no longer refuses a binding that declares views, and BINDING_LOCAL_VIEW_MESSAGE is gone. Per binding, each view's logical entity (Child) joins the live entities and maps to the person table, so a view's roots partition against the closure's Child entries and the existing frame check reads them on the person slice. Roots on that entity stay unmapped on every other binding, and unreached_roots still reads only the binding's engine entity, so it cannot excuse a view root. engine_root_inputs adds each view's roots under its logical entity through AxiomEngine.view_root_inputs(), and refuses a view binding on an injected adapter. binding_view_entities reads a binding's logical entities for the closure and compose. * Parse binding-local views in the registry and check them in compose The registry parses a binding's views block into AxiomView declarations (logical_entity, selector {column}, outside {name: value}) and builds the adapter with them. It refuses an empty list, views beside relations, a binding off the person entity, a logical entity that names a mapped engine entity, a repeated logical entity, a selector with any field but column, a structural selector, a selector that names a binding output, and an outside key that names no binding variable. Once the adapter is pinned, it checks each view's program for relations in place of the binding entity's program, requires each view's outside keys to equal the binding's variables on its logical entity, and refuses a view input named like a root input of the binding entity's program. _check_engine compares an injected adapter's views with the binding's and refuses views on an injected Python adapter. binding_view_dependencies reports each selector as a whole-table person slice, the same columns AxiomEngine.relation_dependencies reports to the rules kernels. Compose unions them with the relation dependencies for every rules node, refuses a view node whose person slice omits an input the closure encodes (other than through a graph node) or defaults on the view's logical entity, and requires each selector on the closure producer's person slice. * Pin NZ-shaped and relation engine references to goldens from the base No NZ CI test builds an axiom_engine_ref for NZ's rows: NZ's engine-free graph injects Python toy engines. The new golden holds references generated at main 4a36edc for NZ's six binding rows (unreached_roots included) over a synthetic tree, and for the registry and adapter relation fixtures, all built engine-free through build_transport_registry and axiom_engine_ref. The test compares them byte for byte, so a reference that gains "views": [] or "views": null without a declared view fails. A second test, gated on POPULACE_RULESPEC_NZ, builds NZ's six real references from the tree alone and compares them with a frozen copy the NZ hub writes at the base. * Add the changelog fragment for binding-local views * Test that every rules kernel requires a view's whole selector slice SimulateRulesKernel._require_relation_inputs reads the adapter's relation_dependencies, which now carry each view's selector, so a node whose person slice omits or masks the selector refuses before the engine runs. The test pins that path for a view adapter. * Pin view code to axiom.py and the no-view outputs to the base path The placement lint now walks every import node in axiom.py, relative and function-body imports included, and a second test requires AxiomView and every view helper to carry axiom.py's module name. A helper moved into a module axiom.py already imports (materialize, concept_mapping) would leave the simulate.* hashes unmoved by an edit to it; the module-name test refuses that move, which the import pin alone passes. P4 gains an output clause: with views=None, materialize returns the same names, order, dtypes and values, through the same engine calls, as a reference copy of main 4a36edc's no-view path kept in the test. NZ's CI graph injects toy engines and never runs AxiomEngine.materialize, so this is the executable guard on view-free outputs. * Class a view's defaulted roots as person columns in the support roster _support_roster mapped each defaulted closure entry to a frame entity through entity_names alone, so a defaulted Child root, which PR 2 writes on the person table, mapped to no entity and missed the closure_default class. _closure_defaults now maps a binding-local view's logical entity to the person entity for entries of that binding's module. Without views the mapping is unchanged. * Freeze NZ's real-tree engine references at the base test_nz_real_refs_match_their_frozen_copy compares NZ's six real references with a frozen copy, and wrote none without a rulespec-nz tree. This adds the copy, generated at PR 1's base (main 4a36edc) from a git archive of rulespec-nz 8dc2507d, the commit NZ's bindings pin. The base run froze the file, and a second base run passed against it. At this branch's head the test passes too, so the six references keep their bytes with views in place. The CI golden test passed at the base as well, which checks that axiom_engine_refs.json came from the base. * Document binding-local views in the declared-units guide The guide said binding-local views remained separate work. It now describes the declaration, the gather and scatter, the outside values, the registry's refusals, the selector slice, the closure's treatment of view roots, and the row-locality assumption that the skip-gated real-engine differential tests. * Union shared-path closure roots to remove binding-order dependence * Cover both logical views to catch dropped input roots * Validate complete outside declarations before pinning bindings * Preserve outside value types in view equality and hashing * Exercise view property edges across every graph dtype * Type and stabilize real Child differential inputs * Freeze CI reference bindings to isolate NZ edits * Clarify view evidence, serialization and selector refusals * Accept overrides of a view selector that a program reads The first review of the fix round (P2) found that 0d6201a's selector refusal also refused an override of a selector that a Person program reads as a root input. The registry accepts that shape, variables() lists the selector, and before 0d6201a the counterfactual bridge applied such an override. validate_input_override now names a selector only when no program reads it, which is exactly when the bridge refuses the override as outside the engine's inputs, so the check changes no override the bridge applies. test_a_selector_that_a_program_reads_stays_overridable drives the counterfactual bridge with a Person program that reads is_child: the override reaches that program's input and the rows the view selects. It fails on 0d6201a's check. The refusal test now also runs through the bridge, which calls no program. The guide states the rule. * Correct the two docstrings that review B flagged `_real_inputs` said the pinned engine refuses a wrong-typed input by name. At axiom-rules-engine 753ce7d, `src/dense.rs` reports a bool, text or date column as a type mismatch when a program needs a number from it ("expected decimal-compatible dense column" for an arithmetic operand, "{side} side of comparison is not numeric" for a comparison), and neither message names the input. The docstring now says that. `axiom_ref_goldens.py`'s module docstring wrote `"\n"` in a non-raw string, so it rendered a line break where the regeneration recipe needs the two characters. It now escapes the backslash. Two merges of main bring this branch up to date: `02265bb5` merges main `875506e8`, and `36800b14` merges main `3e7a7045` (#1473, #1506, H2 #1485, #1501, #1480 and #1488). git merged both without conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S2b PR 2 from Belgium's port (d048). It runs
person_rolebindings inencodeand writes their entities' closure defaults on the person table, so eachChildinput gets a column. It follows the S2b synthesis, which combines the two designs on #259 (comments 6594895348 and 6594940534). It branched from main3995d92d8, where #1477 merged, and lands before G8-x. Of the PRs merged since, only F1 (#1481) touches its files, and7a3e756amerges maine2918f4f, F1 included, into it. The NZ hub cleared commits 1 to 3 atf905863cand OK'd commit 4 at8a4e7a47.Commits
Four commits. The first three make one change each, as the NZ hub asked, and the fourth applies the review's four optional findings:
2981316aEncodeperson_rolebindings as each person's own value.9128040aRefuse bindings that compute one person column differently.f905863cWriteperson_roleentities' closure defaults on the person table.8a4e7a47Refuse aperson_roleentity in the group kernels and test theChildgather.F/=packages/microcosm-frame/src/microcosm/frame/andT/=packages/microcosm-build/src/microcosm/build/transport/. Line numbers refer to main3995d92d8.What it changes
1.
person_rolebindings runA
person_rolebinding feeds an engine entity with one row per person in a role, such as Axiom'sChild. On main,encodedefers every binding with a group rule (is_executable,F/concept_mapping.py:1740-1742), andencode_groupsdefersperson_role(:1948-1950). So no step writes these inputs. Belgium's 35Childbindings have no column: 27 identities on age and 8 predicates on education enrolment, across 11 modules.is_executableadmitsperson_role.encodewrites each such binding through its existing line,out[binding.concept_entity][binding.engine_input] = values, one value per person on the person table.encode_groupsskips executable bindings,person_roleamong them. It still defers bindings on an entity with no built units.person_rolebinding to read person concepts (:1078-1090).2. One column per table and name
encodewrites aperson_rolebinding to the person table, beside the engine'sPersonbindings. On main,_check_shared_names(:1552-1572) compares bindings per engine entity. So it admits aChildbinding and aPersonbinding that share a name but compute it differently. With commit 1 alone,encodethen kept whichever came last [executed: an age identity and an enrolment predicate namedxgave[False, True, False, True]in one order and[40, 38, 10, 30]in the other].The check now compares bindings per table they write: executable bindings per concept entity, group bindings per engine entity as before. No packaged mapping shares a name across engine entities [executed, all four mappings], so the check refuses none of them.
3. Closure defaults for
person_roleentitiesThe S2b synthesis review (brief 6035) found that
concepts.encode@1gathers closure defaults only for the engine entities in the mapping'sentity_correspondence(T/column_kernels.py:407-413). So a defaulted root onChildreaches no table.person_roleentities to the person table's (ConceptMapping.person_role_entities). It reads the whole mapping, not only the selected paths, since a selected module may default every input of an entity that only other modules bind. Belgium'sbe-dg/statutes/family_benefits/amounts.yamlholds 12Childroots and no binding [executed]._defaultsmerges every engine entity's defaults for one table beforeapply_defaultsruns. Two defaults that agree in type and value share one column, and the kernel refuses two that differ ("conflicting defaults"). It also refuses a default on a column that a binding encodes, as it did for one entity ("already encoded",F/input_closure.py:834-836)._full_mapping).person_rolewith another group rule (_check_person_role_entities), sinceencodewrites persons in a role on the person table andencode_groupswrites other group bindings on built units. No packaged mapping mixes them [executed, all four].concepts.encode@1takes noknobsparam (T/column_kernels.py:355-362) and passes no state bindings toencode[read]. So neither path supports a knob-targeted or state-boundChildroot, as neither supports aPersonone.4. The review's four findings
Review brief 6036 approved commits 1 to 3 at
f905863cand raised four optional findings (#1480 (comment)). Commit 4 applies all four. It edits onlyT/column_kernels.pyand its test file.person_roleentity.concepts.encode@1writes aperson_roleentity's bindings and defaults on the person table.concepts.encode_groups@1andtransport.scenario_override@1still took such an entity as theirengine_entityand wrote its closure defaults on a unit table as well. In the review's run,concepts.encode_groups@1withengine_entity="Child"wrotechild_in_careonfamilyand refused nothing._group_inputsnow refuses anengine_entitythat the mapping'sperson_rolebindings name. It reads the whole mapping, as the defaults gather does.Childside of the gather. The old-gather differential holds no defaultedChildroot, so it checks only the gather's non-Childpart. The new property,test_person_and_child_defaults_merge_on_the_person_table_or_refuse, drawsPersonandChilddefaults and the gather order._closureenforces it. With a closure, each selected path names a module the closure lists, which may bind nothing; without one, each names a module the mapping binds.person_rolebindings add an entity to the person table's defaults. A comment inConceptsEncodeKernel.runnow says so: the kernel writes no defaults for an engine entity outside the correspondence that noperson_rolebinding names, even when the closure defaults its inputs.Invariants and tests
Each person's own value (P7, a Hypothesis property): for any concept frame, a
person_rolebinding's column equals, row by row, its transform of that person's own concepts. The property runs an identity and a predicate, the two transforms Belgium uses.No deferral: neither encoder lists a
person_rolebinding as deferred.No interference: adding
person_rolebindings changes no other binding's output, inencodeorencode_groups, and no deferred list.Row-by-row reference:
test_encode_matches_a_row_by_row_referencenow counts Belgium's 35 bindings among the executable ones it compares.Decoders: no concept's decoder changes in any packaged mapping [executed: old rule vs new, per concept, all four mappings]. So the
invertible_conceptspins hold.Pins: two tests pinned the old deferral and now pin the new one:
test_only_bindings_without_built_units_are_deferred(test_group_encode.py) andtest_group_bindings_other_than_person_role_are_deferred(test_concept_mapping.py).One encoded column per table and name, in any order (commit 2). Validation refuses two bindings that write the same table and name unless they compute it alike. It counts two bindings as alike when they share concepts and transform, and, for group bindings, the group rule. An executable binding, with no rule or
person_role, computes each row from that person's own concepts, so its rule doesn't change the value. So validation doesn't require one rule throughout: aPersonbinding and aChildbinding that compute a column alike write the same values, and it admits them.test_bindings_that_write_one_person_column_compute_it_alike, validation refuses an age identity and an enrolment predicate that share a name, in both orders, forChildbesidePersonand besideDependant, and admits aPersonand aChildbinding that compute the column alike.test_one_person_column_passes_in_any_order_only_when_computed_alike(Hypothesis) draws up to five bindings overPerson,ChildandDependant, each computingxone of two ways, in any order. Validation admits them if and only if they all computexalike.Defaults on the person table (commit 3):
test_person_role_bindings_and_defaults_go_on_the_person_table(Hypothesis): aChildbinding and twoChilddefaults, one from a module that binds nothing, land on the person table beside thePersoninputs.test_person_role_defaults_share_the_person_table_with_person_inputs: in either gather order, the kernel writes agreeingPersonandChilddefaults as one column and refuses disagreeing ones. It refuses aChilddefault on an encoded column. When a run selects only a module that defaults and binds nothing, the kernel writes that module's defaults. It refuses a path that the mapping doesn't bind and the closure doesn't list, and a mapping path the closure doesn't list.test_without_person_role_defaults_encoding_matches_the_old_gather(Hypothesis, a differential): with no closure root on aperson_roleentity, the kernel's columns equal, byte for byte, those of the same run after a patch makesperson_role_entitiesreturn nothing, which reproduces main's gather. It draws the mapping with or without aChildbinding, and the selected paths and their order. It holds no defaultedChildroot, so commit 4's property covers that side.test_an_entity_holds_persons_in_a_role_or_built_units:person_role_entitieslistsChildonce, and validation refusesChildwith bothperson_roleandreference_member.PersonandChilddefaults share one person column, or the kernel refuses (commit 4).test_person_and_child_defaults_merge_on_the_person_table_or_refuse(Hypothesis) drawsPersonandChilddefaults over three names, with values fromFalse,True,0,1,0.0and0.5, and the gather order. When both entities default a name and the two defaults differ in type or value,concepts.encode@1refuses with "conflicting defaults". Otherwise each name gets one person column that holds its default.The group kernels refuse a
person_roleentity (commit 4).test_group_kernels_refuse_a_person_role_entity(Hypothesis) runsconcepts.encode_groups@1andtransport.scenario_override@1on the role mapping: each runs forFamilyand refusesChild.Mutants [executed, each on a clean copy, over
test_concept_mapping.py,test_group_encode.py,test_axiom_concept_mapping.pyandtest_transport_column_kernels.py]:is_executable0165b3d7, over the three concept-mapping files)encode_groupshunktest_bindings_that_write_one_person_column_compute_it_alikeandtest_one_person_column_passes_in_any_order_only_when_computed_alikeentity_correspondenceonly, as main doestest_person_role_bindings_and_defaults_go_on_the_person_tableandtest_person_role_defaults_share_the_person_table_with_person_inputsperson_roleentities from the selected bindings, not the whole mappingtest_person_role_defaults_share_the_person_table_with_person_inputsperson_roleentities in place ofPersonon the person tabletest_person_encoding_equals_direct_mapping_and_defaultstest_person_role_defaults_share_the_person_table_with_person_inputstest_person_role_bindings_and_defaults_go_on_the_person_tableandtest_person_role_defaults_share_the_person_table_with_person_inputstest_an_entity_holds_persons_in_a_role_or_built_unitsWith no mutant, the four files pass: 379 tests at
f905863c, and 375 at9128040aalone.At
8a4e7a47, overtest_transport_column_kernels.py, which passes 36 tests with no mutant [executed]:test_group_kernels_refuse_a_person_role_entitytest_person_and_child_defaults_merge_on_the_person_table_or_refusetest_person_role_defaults_share_the_person_table_with_person_inputsentity_correspondenceonly, as main doestest_person_role_bindings_and_defaults_go_on_the_person_tableperson_roleentities from the selected bindingstest_person_role_defaults_share_the_person_table_with_person_inputsReach
NZ, US and UK mappings hold no
person_rolebinding [executed], so their encoded tables keep their values. For each of themperson_role_entities()returns nothing, soconcepts.encode@1gathers the same defaults as on main [read], and commit 4's guard refuses nothing they declare. The differential above checks that equality on generated frames.Kernel hashes, per commit [executed: the 42 kernels
build_transport_registryinstalls besides its threesimulate.*, at main3995d92d8and at each of commits 1 to 3; with those three, the registry holds 45, the NZ hub's count. For commit 4, all 45 ofmake_composed_fixture's registry, and the threesimulate.*again underAxiomEngine]:2981316aconcepts.encode@1,concepts.encode_groups@1,gates.battery@1,transport.create@1,transport.create@2,transport.input_closure@1,transport.scenario_override@1,transport.unit_attributes@19128040af905863ccalibration.prepare@1andtakeup.assign@18a4e7a47calibration.prepare@1,concepts.encode@1,concepts.encode_groups@1,takeup.assign@1,transport.scenario_override@1,transport.unit_attributes@1calibration.prepare@1andtakeup.assign@1The 8 hash
F/concept_mapping.py, which commits 1 to 3 edit;gates.battery@1reaches it through its binding sources. Commit 3 also editsT/column_kernels.py.takeup.assign@1lives in that file, andcalibration.prepare@1hashes it through_columns[read, source-hash map]. Commit 4 edits onlyT/column_kernels.py, so it moves the six kernels that hash that file, all among the 10. Add a person-level coresident_adult transform kind #1477 moved the same 8.simulate.*hash the adapter classes, not these files [read, source-hash map]. No US or UK runtime kernel namesF/concept_mapping.pyorT/column_kernels.pyin itssource_hash[read, source-hash map]. For Add a person-level coresident_adult transform kind #1477's edit toF/concept_mapping.py, the NZ hub hashed six of them and none moved [NZ, executed].NZ's node keys: commits 1 to 3 move
transport.create@1and@2, so all of NZ's nodes re-key. Their population identity changes, and their output bytes don't. S2D-AGREED item 6 asks for a stated re-run of such a change once NZ's M4, its first calibrated population, has passed, and M4 hasn't passed [NZ, ask 7]. On main, NZ's rules bindings hold no engine pin, andbuild_transport_registryraises without one (T/registry.py:126-129,:405-412) [read], so NZ can't build its registry from them yet. G8-x, NZ's first real-data run, sets both pins [NZ].US primary-QRF worker identity:
F/concept_mapping.pysits in the worker's static import closure, through the US adapter (F/adapters/policyengine_us.py:46). So the PR movesworker_module.transitive_imports_sha256andtransitive_environment_code_sha256[executed on main with a one-line comment; the same line inF/adapters/axiom.pymoves nothing].stacked_spine.py:5681-5694refuses a pool that a run recorded under the old identity. Add a person-level coresident_adult transform kind #1477 did the same. Route A has the details; the running US release keeps its pin at9434c504b.T/column_kernels.pydoesn't sit in that closure [executed: appending a comment to it atf905863cleaves the worker binding semantically equal, with the same import and environment hashes]. Commit 4 edits only that file, and the binding at8a4e7a47equals the one atf905863c, byte for byte [executed, fresh processes].Belgium's
Childroots and the closure gateThe NZ hub asked for the closure gate to pass for all 277 of Belgium's
Childroots. That evidence belongs to PR 4, Belgium's activation, which writes the closure rows for those roots. Two things keep it out of this PR:Childroots.closure_partitionreports a root on an engine entity outside the bindings'entity_namesasunmapped_entity, not live (T/evidence_contract.py:999-1000and:1046-1051;T/evidence_kernels.py:432passesentity_names.values()) [read]. Belgium's frame has noChildentity, so a gate run before PR 1 would pass these roots without checking them.Belgium's surface fixture,
be.json, holds 277Childroots in 12 modules [executed,child_roots.py, with Belgium's mapping]. Belgium's 35Childbindings bind 35 of them, which close as concept-encoded once this PR lands. PR 4 must close the other 242, among them all 12 inbe-dg/statutes/family_benefits/amounts.yaml.Childto the person table, the partition puts a defaulted root whose column the person slice lacks inframe_missing(T/evidence_contract.py:1201-1212) [read], and the gate fails it. Commit 3 writes that column; this PR's tests show a defaultedChildroot reaching the person table.Childroots (section 3 above), the gate fails a gap, and an engine-optional root passes only where Belgium's gate setsallow_engine_optional. So PR 4 closes each of the 242 as defaulted, or as engine-optional where the gate allows it.Checks
f905863c, 24 files: 3,152 passed and 3 skipped [executed]. The skipped tests need what this machine lacks: the Axiom dense extension for the real-engine test (test_axiom_relations.py:798), and a staged NZ feed for two generator round-trips (test_nz_transport_declarations.py:281, whichNZ_CHRONICLE_ARTIFACTgates). At8a4e7a47: 3,154 passed, the two new tests among them, and the same 3 skipped [executed].85efd107:git merge-treewrites the merged tree without conflicts, and the files either PR touches pass on it (test_concept_mapping.py,test_group_encode.py,test_axiom_concept_mapping.py,test_transport_column_kernels.pyandtest_transport_holdout_separation.py: 447 passed atf905863c) [executed]. At8a4e7a47, the head still merges without conflicts into F1 and into main4a36edcf[executed].8a4e7a47: all 10 checks pass on run 41726655780, which tested8a4e7a47merged into main79a533c7. Its engine-free job runs all 704 engine-free test files (tools/ci_test_plan.py list-job engine-free), among them the 16 others that reachF/concept_mapping.pyor the column kernels. The engine-us and engine-uk jobs run the US and UK concept-mapping tests.f905863c, with four optional findings that commit 4 applies.8a4e7a47, with three optional findings that a follow-up PR takes.f905863c[NZ, executed].8a4e7a47.52fc34c6merged mainf8f543a4(G), and CI run 41748288587 passed on it.17e26433merged main0f0e777b, seven PRs later (Re-pin the US Chronicle feed so three dimension values carry one label each #1345, Include geography_constants in the target-compilation cache keys #1493, Test staged bundles through the installed emitter in CI #1467, UK #1095 A/B fixes: the SPI age band (uk-data#628), households' HRAD from Table 8a receipts, and the SPI pay floor (uk-data#629) #1426, Stop a build that must send telemetry when its emitter cannot start #1465, Calibrate the QRF sign gates out of fold, for every gated UK imputation #1391 and Give a stage launched on Modal a telemetry-only collector token #1473), and7a3e756amerges maine2918f4f, three more (Re-pin the US Chronicle feed to add Federal Student Aid's loan portfolio tables #1506, Bind NZS co-residence from household composition (H2) #1485 and Tighten five G tests that checked less than their names claim #1501). None of them changes a file this PR changes, and git merges them without conflicts. CI run 41887940219 passes all 11 checks on7a3e756a, among them Test staged bundles through the installed emitter in CI #1467's new installed-emitter job. It tests this PR beside H2's coresident binding, whose reference concepts the concept-mapping differential reads.🤖 Generated with Claude Code