Repository navigation
Give a person with no household no household's row, and read malformed holdout rows alike in both guards - #1481
Conversation
ConceptMapping.encode runs no frame validator, so a person whose household id is null or names no household can reach _Context. get_indexer gave such a person row -1, and the gathers in reference_rows and household_amount_for_persons read -1 as the household table's last row. A null person household id also found a household row whose own id was null. _Context now gives a null household id row -1 whatever the household table holds, and both gathers read row -1 as an appended sentinel: reference row -1 and amount NaN. So allocation gives such a person 0, and no role flags them as a reference person or as a reference person's partner. Frames whose person household ids all name a household encode as before. The row-by-row reference applies the same rule instead of raising KeyError, and the differential test now draws null and unknown person household ids. New example tests cover the probe cases on Int64 and UInt64 ids, a null id in the household table and an empty household table, and Hypothesis properties check the no-household, row-order and conservation invariants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014eCgZkFeeyUZcCZLNXec1k
The stage guard in _precalibration_stages read row["entity"] and row["measure"] from every holdout_references row, so a list or dict measure raised TypeError and a missing measure or entity raised KeyError. Activation's guard skipped a row that was not an object with a text measure, so such a row dropped silently out of the separation check. One predicate, _is_holdout, now defines a holdout row for both guards. The stage guard skips any other row, builds holdout_columns only from rows whose entity is text, and names a missing entity as "?". Activation's guard reports each row it cannot read as one gap that names the row, so a declared holdout never drops silently out of both guards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014eCgZkFeeyUZcCZLNXec1k
|
NZ hub adopts F1 (#1481). The farness cloud session (brief 208) pushed |
The NZ hub's mutant M4b drops the seen check in _holdout_separation_gaps, so the guard yields every gap it builds, and all 50 tests in test_transport_holdout_separation.py stayed green: no test passed a holdout document in more than one view. The new test passes one document with a malformed row in two views and expects one gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged by the NZ hub (session local_e407a527) at 9b49ef3 with --squash. Gates: |
Brings in main at 79a533c: #1495, #1481 and #1460, which landed while the 31c8939 merge was in CI preparation. The one conflict is the transport battery's module count: #1495 counts #1342's two modules in BINDING_MODULES_ADDED_AFTER_BASE, so this keeps main's form and the branch's base of 198 (us_runtime.scf_education_debt, b1218b0) in place of the hand count 200 from the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tracking issue #1183, follows #1472 and #1477. This makes shared code handle two kinds of malformed input safely: a person whose household pointer finds no household, and a holdout row whose measure isn't text. Valid frames and specs keep every output.
Scope of the harm. This change adds consistency and defence in depth. It fixes no live bug in a production run.
validate_concept_tables(F/concepts.py:1403) runs whereF/transport.pybuilds the transport frame (:593and:876). It refuses a null or unknown person household id (_require_structure) and reports a reference person who isn't a member (_check_pointers).ConceptMapping.encodedoesn't run that validator, and #1477 already gaveencodea rule for a null household id in_coresident_adult_flags. The rest of_Contextnow follows the same rule.F/ispackages/microcosm-frame/src/microcosm/frame/andT/ispackages/microcosm-build/src/microcosm/build/transport/. Line numbers refer to this PR's head.Authors. A cloud session wrote the first two commits from the NZ hub's brief 208, then stopped at its account's limit. The NZ hub adopted the PR on 2026-11-03 (#1183 comment 6600936673), ran the checks below, and added
9b49ef30, a test for the gap guard's dedupe.Shared hunks
F/concept_mapping.py(the fix 1 hunks, 17 lines):2096-2098,_Context.__init__: a person whose household id is null gets row -1, even where the household table holds a null id of its own.get_indexeralready gives -1 for an unknown id.pd.NAfindspd.NAinget_indexer, so this closes review 6595462928's "Fixes owed 3".:2121-2125,household_amount_for_persons: it appends a NaN to the amounts before it gathers, so row -1 reads NaN, never the last household's amount. This works on an empty household table too.:2127-2136,reference_rows: it appends -1 the same way, so a person with no household has reference row -1._matched_ids,_coresident_adult_flagsand the group path (_Units) stay as on main, andencodestill refuses no null or unknown pointer.T/compose.py(the fix 3 and fix 4 hunks, 38 lines):1010-1016, new_is_holdout: one predicate, an object whosemeasureis text. Both guards now read holdouts through it. It matches main's old activation test (:1027on main).:1019-1049,_holdout_separation_gaps(fix 4): the guard yields one gap for eachholdout_referencesrow that isn't an object with a text measure, naming the row by index and name. It still skips such a row as a holdout.seenmoves up so the guard reports each malformed row once, even when the same document arrives in two views.:3015-3021,_precalibration_stages(fix 3): the stage guard keeps only_is_holdoutrows and buildsholdout_columnsonly from rows whose entity is text as well.:3134,_precalibration_stages' refusal: it reads the entity with.get('entity', '?').test_support/microcosm_frame/concept_reference.py(fix 2):57-78:household_ofgivesNonefor a null or unknown household id, and the household lookup skips a household row whose own id is null.valuegives NaN andreference_ofgivesNonefor a person with no household.:121-124: allocation gives 0 unless the person is their own household's reference person.Invariants
AllocateToReferencePersongives them 0.AllocateToReferencePersonvalues sum to the household amount.encodegives exactly what main gives.validate_transport_activationstill raisesTypeErroron a holdout row that isn't an object, or on a list or dictname, as main does;Tests, mutants and packaged counts
The NZ hub ran these at
85efd107on Python 3.14.7, one process at a time (-n 0), with each run's Hypothesis database outside the tree andNZ_CHRONICLE_ARTIFACTpointing at the NZ feed. CI passed all 10 checks at85efd107.test_concept_mapping.py,test_group_encode.py,test_axiom_concept_mapping.pytest_transport_holdout_separation.pytest_transport_composed_graph.py -k "holdout or precalibration"test_nz_axiom_input_closure.py,test_nz_transport_declarations.pyThe two skips (
test_nz_axiom_input_closure.py:535and:610) needaxiom_rules_enginewith its dense extension and a rulespec-nz checkout atPOPULACE_RULESPEC_NZ.ruff checkandruff format --checkpass on the five changed files, andtools/ci_test_plan.py verifyprintsverification=ok.Mutants. Each mutant undoes one hunk, or all of one fix's hunks. The hub ran each against the test file in its row, then restored the tree;
git statuscame back empty after the last one.test_concept_mapping.py__init__'s -1 for a null household idhousehold_amount_for_personsappendsreference_rowsappendsconcept_reference.py)test_transport_holdout_separation.py_is_holdoutfilterholdout_columns.get('entity', '?')seencheck on those gapsThe tests each mutant turned red:
TestExecution::test_encode_matches_a_row_by_row_referenceand 17TestNoHouseholdcases (allocation ×4, empty household table, I1, I2, reference row ×4, null household id ×2, partner flag ×4).test_encode_matches_a_row_by_row_reference, the differential against the reference.test_stage_guard_reads_a_malformed_holdout_row_as_activation_does(×5). M3a: I5 and four of those cases. M3b and M3c: I5 and one case each.test_activation_reports_a_malformed_holdout_row(×4).test_guard_names_a_malformed_holdout_row_once_across_viewsin9b49ef30. It passes one document with a malformed row in two views and expects one gap. F1's review (brief 210 v2) ran M4b at this head: only that test went red, out of the file's 51 (Activate the NZ graph against pinned Chronicle facts (NZ graph G8) #1183 comment 6614494025). CI passed all 10 checks at9b49ef30(run 41625995645).Packaged counts (
f1_packaged_counts.pyat85efd107).split_for_transport(6 and 2), and NZ's forty-household toy frame (120 and 40). None has a null person household id, a person household id that names no household, or a null household id.holdout_references. Every declared row is an object with a text measure and a text entity.So, by the hunks above, fix 1 changes no output on these frames, and fixes 3 and 4 change no outcome on the packaged specs.
The changed old test.
test_non_text_measure_keeps_activation_outcomedropsholdout_referencesfrom itsresourceparameter. Fix 4 now reports a holdout row with a non-text measure, so activation's outcome there changes by design.test_activation_reports_a_malformed_holdout_rowcovers that row, with list and dict measures among its cases.Kernel hashes
make_composed_fixtureinstalls 45 kernels. The hub hashed every one withregistry.kernels.implementation_hash(ref)at F1's base on main (3995d92d) and at85efd107, in separate processes, along with the threesimulate.*kernels underAxiomEngine. Eight of the 45 moved, the same eight and the same values as the table below, and none underAxiomEnginemoved.9b49ef30changes only a test file.The cloud author attributed each move by hashing main with one changed source file at a time from this head.
F/concept_mapping.pyalone moves all eight, andT/compose.pyorconcept_reference.pyalone moves none, so no kernel's hash readsT/compose.py. A second run at main repeated every hash.F/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyF/concept_mapping.pyThese are the eight kernels that hash
F/concept_mapping.py, as review 6595462928 listed.US primary-QRF worker identity
F1 moves it.
F/concept_mapping.pysits in the worker's static import closure, through the US adapter (F/adapters/policyengine_us.py:46importsConceptMapping); the shortest path runspuf_qrf_worker→microcosm.build.us_runtime→us_runtime.other_health_insurance→frame.adapters.policyengine_us→frame.concept_mapping[read; executed: brief 217]. The identity hashes each closure module's raw file bytes (packages/microcosm-build/src/microcosm/build/us_runtime/worker_identity.py:580-632), so docstring edits count too [read: brief 217].primary_qrf_worker_execution_binding()at3995d92dand at9b49ef30differs in three fields:semantic_identity.worker_module.transitive_imports_sha256,semantic_identity.transitive_environment_code_sha256andsemantic_identity_sha256. The worker's own source,uv.lock, the installed distributions, the interpreter and the environment agree [executed: brief 217].3995d92dthat takes only F1'sF/concept_mapping.pygives9b49ef30's binding byte for byte, and9b49ef30with3995d92d's copy of that file gives3995d92d's. So that file accounts for the whole move [executed: brief 217, Activate the NZ graph against pinned Chronicle facts (NZ graph G8) #1183 comment 6613701891, with all four trees run from one worktree path, since the binding'saudit_aliasesrecord the venv path; Belgium's hub found the same].packages/microcosm-build/src/microcosm/build/us_runtime/stacked_spine.py:5681-5694raises "late primary-QRF semantic worker identity changed" [read], and the equality check returnsFalsefor3995d92d's recorded binding against9b49ef30's live one [executed: brief 217]. Runs that carry a legacy worker attestation take the other branch (:5672-5679), which compares the recorded worker with the attested one, so F1 doesn't touch them [read: brief 217].9434c504b, so F1 doesn't reach them; a US run built from a main with F1 gets the new identity. Main already moved this identity after9434c504b: Read US catalog units from engine source and retire the unit waiver #1484 changedF/adapters/policyengine_us.py, which sits in the same closure [inferred from the closure above]. Encode person_role bindings and their closure defaults on the person table (S2b PR 2) #1480 moves the identity throughF/concept_mapping.pytoo, so whichever of the two lands second moves it again [inferred: brief 217].Reviews
9b49ef30: APPROVE_WITH_NITS, with nothing owed in code (Activate the NZ graph against pinned Chronicle facts (NZ graph G8) #1183 comment 6614494025). It reran M1a, M1b, M3b and M4b, ran three mutants of its own, and foundencodeequal to main's on 1,200 inputs across 174,900 columns. Its two nits are the wording fixes in this body.9b49ef30(11/03).🤖 Generated with Claude Code