Skip to content

Add opt-in declared tax-unit partitions and validated binary Axiom relation bindings, with NZ benefit units as a declared unit kind - #1438

Merged
MaxGhenis merged 5 commits into
mainfrom
be/declared-units-s1
Nov 2, 2026
Merged

MaxGhenis merged 5 commits into
mainfrom
be/declared-units-s1

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Plan steps S1 and S2a of the Belgian transport port, in ~/reviews/belgium-2026-10-12/transport-port/PLAN.md.

WIP. The S1 lane stopped at its six-hour limit before it finished verifying, so I committed its work as it stood, and CI passed 10 of 10 at 9ecfa87. Before this leaves draft it needs CI at the merged head, Sol's agreement on that head, and NZ's OK.

What changes

  • Unit declarations. frame/unit_construction.py gains a versioned MultiUnitRule list with registered benefit and tax membership kinds. transport/unit_declarations.py holds the CREATE refs and the one parser. transport.create@1 lifts the unchanged NZ rule, and transport.create@2 reads the versioned list.
  • Tax partitions. Tax membership needs an explicit non-null boolean per person. Both eligible reciprocal partners pair, and everyone else, minors included, gets a singleton assessment. The smaller physical ID becomes spouse A. No legal eligibility rule lives here: a country supplies that boolean.
  • Axiom relations. frame/adapters/axiom.py gains frozen binary manifests, source and compiled tuple checks, related-input enumeration and identity-ordered native batches. A relation here links a declared group partition to its person members. Cardinality one means exactly one selected member per root, and many allows none. A pure-Python injected adapter can't claim native relation execution.
  • Calibration. Roster preparation moves from build/calibration_columns.py into the unit framework, with the contract unchanged.
  • Whole relations. Every rules kernel and composition share missing_relation_inputs. A node must declare each related data column, and no row mask may touch a dependency's entity. A mask can drop a member and leave every unit populated, so no partition check sees that loss.
  • Override ownership. An override or solve input that names a related input on another entity refuses, because no batch reads that table. A real root input of that name stays overridable.
  • Legacy CREATE. transport.create@1 keeps its family-only refusal, and transport.create@2 carries the wider domain.

No file under build/be/ or build/nz/ changes, and no country activates anything.

Invariants

Invariant Test
Each person joins exactly one unit per partition, and each unit sits in one household test_every_declared_partition_nests_and_preserves_identity (Hypothesis)
IDs and roles hold under row shuffling and income changes, and changed membership changes identity same, plus test_tax_partition_membership_changes_population_identity (Hypothesis)
Weights stay on households, and each unit inherits once test_unit_weights_inherit_household_once (Hypothesis)
Asymmetric and self pointers never pair test_a_nonreciprocal_or_self_pointer_never_forms_a_pair (Hypothesis)
Batches order by physical identity, and people with equal values stay distinct test_related_rows_order_by_identity_and_preserve_equal_values (Hypothesis)
An undeclared relation refuses test_extra_manifest_and_undeclared_relation_refuse
A node reaches an engine only with every relation dependency whole test_a_node_passes_only_with_every_relation_dependency_whole (Hypothesis), test_every_rules_entry_point_refuses_a_partial_relation, test_graph_refuses_a_row_mask_that_drops_a_relation_member
An override changes a table a batch reads, or refuses test_bridge_override_on_another_entity_refused, test_bridge_root_input_sharing_a_related_name_stays_overridable
An absent declaration keeps identity and the legacy native call test_absent_relation_identity_and_call_are_unchanged
Aliased overrides refuse before native execution, and unaliased overrides reach the batch test_bridge_aliased_related_input_override_refused, test_bridge_unaliased_related_input_override_reaches_native_batch
Every related role and input is a graph dependency, and a mask refuses test_relation_inputs_must_be_explicit_graph_dependencies (Hypothesis)
create@1 refuses what it always refused test_legacy_create_keeps_its_family_only_domain

Differential tests. test_nz_create_alias_is_byte_identical_to_declared_create compares partitions, IDs, roles, inventories and inherited weights across the two CREATE versions. test_calibration_kernel_roster_framework_matches_previous_kernel compares against a frozen copy of the helper at 09a2fce.

What moves

  • Kernel hashes: the lane's table reads 22 of 36 existing refs moved, transport.create@2 added, and 14 held. The fix commit also edits frame/kernels.py, which moves simulate.rules@1. No production registry binds that ref, and the simulate parity pin is re-recorded with tools/graph_parity_repin.py.
  • Belgium: all five Belgian package dumps stay identical to main at this head.
  • NZ: the CREATE implementation hash moves, and its frame key and dependent keys move with it. The lane's differential keeps NZ's topology, inventory and roles, but NZ needs a fresh population run before it reuses cached M4-era results.
  • NZ engine reference: 865 bytes, byte-identical against pinned rulespec-nz 8dc2507d5.

Executed

The lane ran these with real Axiom CLI 0.2.2, build e0bda64:

  • It compiled a relation fixture and got exact decimals 2 and 1 for two people against one, in both fast and explain modes.
  • It compiled the Belgian couple pipeline and got the same results in both modes on eight outputs, from synthetic inputs. Those runs show native relation behaviour, and they make no legal claim.
  • It reversed the tuples, and the engine refused them.

The lane killed the mutants in its table, apart from one that a later check masked. It added a direct regression for that one. Its file-by-file table reads 2,469 passed and 27 skipped across 28 files. The lane marked that report as in progress, so CI's result replaces it.

Review. Sol reviewed 76a137c and requested three changes, each with an executed reproduction. All three are in a395c1e. Writing the end-to-end test then exposed a fourth defect: composition required ID and membership columns in slices, and compile_graph refuses those, so no relation node could compile. The new check needs a slice only for data columns.

Sol then agreed at a395c1e. Since that review, 9ecfa87 changes one citation test, and ecec60c merges main, which adds S3, w3a, #1422 and #1436. The merge conflicted in one place, the import block of transport/cli.py, and both imports stay. The shared hunks match 9ecfa87's apart from one context line in that block. Merged with main 11c1f4e, every Belgian dump matches main byte for byte, and on a 33-ref check 19 kernel hashes moved, 14 held and transport.create@2 joined.

eccd804 then merges #1441, with no conflict. The shared diff keeps the same SHA-256 as at ecec60c, and NZ gave its OK at that head.

Fix mutants. I removed each of the ten new guards in turn and ran both test files that reach them. All ten failed.

Limits

  • Python dense extension: the host has no compatible build, so the Python dense-adapter relation test skips.
  • Full Belgian module: dense readiness stays unverified. The lane read the engine source and expects a refusal on direct cross-entity derived references. Any rulespec-be change belongs to a later PR.
  • Legal eligibility: how a country's result reaches the CREATE boundary stays open for the NZ and Belgian hubs.

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits November 1, 2026 10:11
Add a versioned MultiUnitRule list with registered benefit and tax
membership kinds, so a country can declare several unit partitions
beside its households. transport.create@1 lifts the unchanged NZ rule
and transport.create@2 reads the versioned list.

Tax membership requires an explicit non-null boolean per person. Both
eligible reciprocal partners pair; everyone else stays a singleton.

Add frozen binary relation manifests to the Axiom adapter, with source
and compiled tuple checks, related-input enumeration and identity-ordered
native batches. A binding without relations keeps its identity and its
legacy native call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sol's review of 76a137c found three defects, and an end-to-end test
found a fourth:

- A row mask could drop a relation member yet leave every unit populated,
  so no partition check saw it and the engine got partial data. Every rules
  kernel now refuses a node whose slices don't deliver its relations whole.
- A related-input override on another entity wrote a column no batch reads,
  so it changed nothing. It now refuses, and a real root input of that name
  stays overridable.
- transport.create@1 accepted non-family units. It keeps its old refusal,
  and transport.create@2 carries the wider domain.
- Composition required id and membership columns in slices, which
  compile_graph refuses, so no relation node could compile. Tables always
  carry those columns, so only data columns need a slice. Composition and
  the rules kernels now share missing_relation_inputs.

frame/kernels.py moves simulate.rules@1's hash, so the simulate parity
pin is re-recorded with tools/graph_parity_repin.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The NZ source-citation test pinned the old one-line lift. The parser now
binds the rule first, to keep the family-only refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main adds S3, w3a, #1422 and #1436. Git conflicts in one place, the import
block of transport/cli.py: S1 widens the population_kernels import, and
#1436 adds the entitlement import above it. Both stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review

This report comes from the NZ hub's cloud review. It isn't NZ's OK: the NZ hub reads it and answers Belgium.

VERDICT: APPROVE
Reviewed head: 9ecfa87
Needs Max: no

Base: 09a2fce539686f9276bbbee1b4a339ed881c97ca (merge base of S1 with main, and of S1 with 362f96e10). NZ commit read: 362f96e1033aedb34cd601c033e5efade59a07a6.

Hunk reproduction. The 12-file diff is 2,188 lines. Its SHA-256 is 7d0ecb7c34b62d919911024c598616037e7a8bc60883f5b47dfcce5b658dc6da only with --abbrev=9. This clone's automatic abbreviation is 8 characters and gives d899ded0…. The content is the same; only the index lines differ.

Environment. Python 3.13.18. One venv built with uv sync --all-packages --locked from the main checkout. Its lock (e7d00ea4…) equals w3b's. The base and S1 locks (a6e91aa7…) differ only in the uk extra's policyengine-uk pin, and --all-packages doesn't install that extra. Each tree ran in its own process with its own PYTHONPATH, and each run printed compose.py and unit_construction.py from its own tree.

Findings

None. I found no NZ input that S1, or S1 merged onto 362f96e10, refuses or computes differently.

Two notes for the NZ hub. Neither is a request to Belgium.

  1. H27a item 5 needs a baseline that already includes S1. S1 moves 18 implementation hashes that NZ's graph uses (table below). After Add opt-in declared tax-unit partitions and validated binary Axiom relation bindings, with NZ benefit units as a declared unit kind #1438 lands, H27a's check ("the only moves are calibration.prepare@1 and takeup.assign@1") holds only if both sides of the comparison already contain S1. Measured across the merge itself, it would report about 20 moves. S1's moves don't touch the stop condition: no kernel hashes transport/registry.py. terminal_kernels.py:48 and geography_kernels.py:22 bind microcosm.calibrate.registry, a different module.
  2. H27a's SKELETON_KERNELS line will meet a changed expression. S1 rewrites the set as CREATE_REFS | frozenset({...}) and drops the literal "transport.create@1" (compose.py:157). NZ's added line goes inside the new literal. Today's merge conflicts only in the import block, as predicted.

Items

1. CREATE@1 across commits: byte-identical. I ran TransportCreateKernel.run at base and at S1, in separate processes. Each run used NZ's packaged benefit_unit_rule.json (same bytes in all three trees, a9e8cbfd…) on synthetic donor H5 files written by write_population_sources. The inputs:

  • the toy fixture, with the hours edit at 0 and at 35;
  • 700 donor tables from a derandomized Hypothesis strategy (max_examples 400 + 300). Each has 1 to 5 households of 1 to 6 persons, with shuffled person order, symmetric partner pairs, acyclic parent pointers that include chains and teen parents, financial-independence hours around 30, and mixed support channels. The largest frame had 21 persons.

For each case I compared the person, household and family tables (columns, dtypes and full-precision CSV, index included), resolve_weights for every entity, the schema, the metadata, the whole receipt, benefit_unit_attributes with the is_partnered/is_single aliases, and benefit_unit_roles.

Result: 392 cases built units and were identical in every part. The other 310 refused at both commits with the same message: a dependent reference person, or co-resident parents in different units. No case built at one commit and refused at the other. The digests of the full result files match (b76dd587…, 74bbe0d5…, 121904b5…).

Why it holds:

  • For a BenefitUnitRule, _Pointers.read validates the same six person columns as base, in a different order (unit_construction.py:994 at S1). It still validates the whole subset before reciprocal_partner_rows (:1068), so error precedence is unchanged.
  • _build_partition's new nesting and head checks (:734, :737) never fired on NZ's rule.

2. Family-only refusal: unchanged, and keyed only on entity. create_unit_declaration (unit_declarations.py:24-30) parses the document with BenefitUnitRule.from_spec and refuses when rule.entity != "family". That is the check and message base had inline at population_kernels.py:233, moved, not new. It reads only the entity field. NZ's rule sets "entity": "family", which matches NZ_SCHEMA and is fixed by B8/MC7. No other NZ field (engine_entity, prose, kind) enters the decision.

S1 also applies the check in cli.prepare_transport_build (cli.py:290), which at base parsed the rule without it. That's a no-op for NZ.

3. Unit attributes: same names, dtypes, nulls and values on NZ's rule. At both commits: n_members, n_adults and n_dependent_children are int64; is_couple, is_sole_parent, and the aliases is_partnered (= is_couple) and is_single (= ~is_couple) are bool; family_type is pandas str; youngest_dependent_child_age is nullable Int64 and null for every childless unit (unit_construction.py:879-886). The only edit there skips reading age when a frame has no child rows.

Person row order, family_id (the head's person id) and family row order are byte-identical, so first aggregation over the lowest structural person id doesn't move. H24's nullable-input defect is unchanged: the same to_numpy(dtype=np.int64) sits at adapters/axiom.py:1824 at S1, at :1424 at 2b44c12.

4. n_units: no NZ reader, and the single-partition receipt keeps its shape and value. At w3b, nothing in transport/, nz/, the graph or gates reads CREATE's n_units or unit_construction. The hits are writers only (population_kernels.py:295, column_kernels.py:341, the reporting kernels). receipt_contract.json is the take-up contract, not the CREATE receipt.

At S1, n_units is sum(len(units)) over one partition (population_kernels.py:308), and unit_construction is the single diagnostics mapping when there is one declaration (:311). The whole receipt JSON was byte-identical in all 392 cases.

5. unit_entity: no dependence. NZ has one concepts.encode_groups@1 node, nz.as.inputs. Its params carry no unit_entity, and its rule is a plain BenefitUnitRule. _unit_declaration lifts that to a one-entry MultiUnitRule (column_kernels.py:143), and _group_inputs takes matches = list(declaration.units) when unit_entity is absent and there is one unit (:435-440). That is the same rule base used. H23 to H26 add counterfactual, solve-zero and calibration.prepare@1 nodes, not group encodes. No NZ resource mentions unit_entity, multi_unit_rule or tax_unit.

6. Relation checks: no NZ node trips them, today or in the H23/H24 shapes.

  • At run time, SimulateRulesKernel._require_relation_inputs (frame/kernels.py:134, called at :195) and the per-component call in SimulateCounterfactualKernel (rules_kernels.py:333) read engine.relation_dependencies (axiom.py:642). That method iterates self._relations or (), which is empty for every NZ engine, so missing_relation_inputs (kernels.py:41) gets no dependencies and returns [] whatever the slices or row masks are.
  • validate_input_override returns at once when there are no relations (axiom.py:659). It covers both the overrides (rules_kernels.py:466) and solve_input (:512). So H24's two components (main_benefits_rates, family_tax_credits), H23's simulate.solve_zero@1 with family solve_input, and the term masks all pass. Term masks aren't Slice rows in any case.
  • Toy engines without the method skip the check.
  • At compose time, _require_relation_inputs (compose.py:2818, called from :2805 and :2984) returns early when the binding has no relations key. No NZ resource has one.

What supplies the dependencies: the hand-kept relations manifest in axiom_rules_bindings.json. _binding_relations (registry.py:192) parses it, and it is passed to AxiomEngine(relations=…) (:457). The compiled program is consulted separately. With a manifest, assert_declared_relations checks keys, slots, input union and source arguments against program.relations at registry build (registry.py:471) and at materialize (axiom.py:911). Without one, which is NZ's case, assert_no_relations (registry.py:473, axiom.py:1747) refuses any compiled module that declares relations.

The runtime and compose checks read the manifest, but a manifest that disagrees with the compiled artifact can't get past registry build, which runs in prepare_transport_build before any data. For NZ, with no manifest, a RuleSpec module that compiles relations would first refuse when the real AxiomEngine is built. In engine-free tests that's never; in a real run it's G8-x. That's the same place and the same call as at base (registry.py at base called assert_no_relations unconditionally), so S1 adds no new G8-x failure mode. I didn't inspect rulespec-nz's compiled relations; that's outside this repository and scope.

7. Slices: no NZ slice names an entity id or membership column. I scanned every NZ resource. The only *_id columns in slices are data columns that CREATE owns: partner_person_id, parent_1/2_person_id, reference_person_id, source_household_id, family_household_id, family_head_person_id, family_source_household_id. None is person_id, household_id, family_id, person_household_id or person_family_id. export_contract.json lists the ids under required only; its entity_slices for export and gates hold data columns.

At 362f96e10, _check_export_contract (compose.py:1258) derives the automatic ids and memberships from transport_entity_schema(BenefitUnitRule…). At S1 that function lifts the rule and returns MultiUnitRule.entity_schema(), which gives the same ("household", "family") schema. S1 doesn't touch microcosm-graph source; its graph change is one test fixture. So any refusal of id slices in compile_graph is base behaviour. test_transport_composed_graph.py compiles NZ's graph with the same outcome on w3b and merged.

8. Hashes. I recomputed them with no engine, using the registration sequence of build_transport_registry and the routers bound to an uninitialised AxiomEngine instance (a real run binds that class). Of 36 existing refs, 22 moved and 14 held, and transport.create@2 was added. That matches Belgium's first claim, including the named held set. The later diagnostic (33 refs, 19 moved) is consistent with leaving out the three simulate.* routers; I didn't reproduce that diagnostic itself. calibration.prepare@1 (target_kernels.py:612), transport.create@1 and concepts.encode_groups@1 (column_kernels.py:478) do bind unit_construction.py. transport.create@1 and transport.create@2 share one hash (34d2268b…) because the subclass is defined in the same module; node keys still differ by ref.

NZ uses 18 of the moved kernels, so NZ node keys re-key. No NZ resource or NZ test pins a hash or node key. On NZ inputs, behaviour holds:

  • CREATE@1: item 1.
  • transport.unit_attributes@1 calls the same benefit_unit_attributes through declared_unit_attributes. Its new person.family_role output is dropped by _columns, which projects only declared outputs (column_kernels.py:253).
  • concepts.encode_groups@1: item 5.
  • calibration.prepare@1: prepare_roster_counts is the old _prepare_roster_counts body moved, and _roster_identity equals calibration_predicates._identity. NZ's household_size roster count therefore computes the same.
  • The simulate.* routers: item 6. The no-relations materialize path is unchanged apart from the new keyword-argument assembly, which adds relations= only when program.relations is non-empty.
  • The rest moved only because the module bytes they bind changed: target_kernels, population_kernels, the Axiom adapter module.

9. Partitions. No NZ resource at 362f96e10, or in the merged tree, names transport.create@2, multi_unit_rule, tax_unit or pairing_eligibility. NZ's graph declares exactly one transport.create@1. No file under build/nz/ or build/be/ changes between base and S1.

10. Merged tree. git merge --no-commit 9ecfa8742c onto 362f96e10 conflicted in one place only: compose.py's import block. I kept all three lines and didn't commit. The closed list ran on both trees:

File w3b merged
test_nz_transport_declarations.py 25 passed, 2 skipped 25 passed, 2 skipped
test_nz_spec_package.py 4 failed, 1199 passed 4 failed, 1199 passed
test_nz_benefit_gross_up.py 13 passed 13 passed
test_transport_composed_graph.py 1 failed, 461 passed 1 failed, 461 passed
test_transport_entitlement_graph.py 67 passed 67 passed
test_country_spec.py 143 passed 143 passed
test_nz_joint_geography.py 21 passed, 12 skipped 21 passed, 12 skipped

No test passes on w3b and fails merged. I compared the junit outcomes of all 1,948 test IDs. The set of IDs and the failure assertions are the same on both trees; the only difference is the iteration order of a set.

The five failures known on w3b, the same on both trees:

  • TestScenarios::test_committed_grid_satisfies_the_scenario_contract (extra VHF)
  • TestSourceCitations::test_every_dotted_microcosm_symbol_in_the_package_resolves (microcosm.concept_mapping.v1)
  • the two TestReportingClassifiers tests (*_annual_gross against *_gross_weekly_payment)
  • test_closed_export_contract_enforces_exact_entity_slices

Skips:

  • test_nz_transport_declarations.py:281: the two generator parametrizations need NZ_CHRONICLE_ARTIFACT.
  • test_nz_joint_geography.py: one needs NZ_CHRONICLE_ARTIFACT (:394), and 11 need NZ_AS_AREA_SOURCES/NZ_JOINT_GEOGRAPHY_SOURCES (:592, :602, :635×7, :654, :671).

S1 tests run for items 1 to 9:

  • At S1: test_declared_units.py 46 passed, test_transport_declared_units.py 18 passed (it includes test_nz_create_alias_is_byte_identical_to_declared_create), test_transport_population_kernels.py 32 passed, test_transport_column_kernels.py 31 passed, test_calibration_columns.py 105 passed.
  • At base: test_transport_population_kernels.py 32 passed, test_transport_column_kernels.py 30 passed.

Hash table

Engine-free implementation hashes (first 12 hex characters). The simulate.* routers are bound to the AxiomEngine class.

Kernel Base 09a2fce539 S1 9ecfa8742c Status NZ graph uses it
calibrate.ordered_adam@1 0d4577effa14 0d4577effa14 held yes
calibration.prepare@1 e8a2f92b8217 0929b16c0be4 moved yes
concepts.encode@1 bcb84fa73efc 7815fae8ed57 moved yes
concepts.encode_groups@1 55da7a2fda7d 062f88411ad6 moved yes
diagnostics.calibration@1 bfc9b52d391d d38f8fad075a moved yes
export.prepare@1 cbfb50d207b1 48feda5bc762 moved yes
export.readback@1 09a5b029b928 96be393c760c moved yes
gates.battery@1 5c3a0112216b f6b0bb60ff3f moved yes
geography.assign_atomic@1 d60f7ec38890 d60f7ec38890 held yes
geography.derive@1 d60f7ec38890 d60f7ec38890 held yes
geography.gate@1 d60f7ec38890 d60f7ec38890 held yes
geography.support_from_facts@1 1a7b43faf985 60273ea83880 moved yes
geography.support_import@1 d60f7ec38890 d60f7ec38890 held no
money.annual_transfers@1 c8a067cd750e c8a067cd750e held yes
money.minor_units@1 c8a067cd750e c8a067cd750e held yes
money.normalize@1 1b31a2b5e2aa 1b31a2b5e2aa held yes
reporting.carry_income_decile@1 80335f0183c2 80335f0183c2 held yes
reporting.income_decile@1 80335f0183c2 80335f0183c2 held no
reporting.receipt_group@1 80335f0183c2 80335f0183c2 held yes
simulate.counterfactual@1 0d641daca471 fee0468ce45f moved yes
simulate.rules_by_ref@1 50738c098048 63adf504ece8 moved yes
simulate.solve_zero@1 e475cbebfb02 9512f2b455a0 moved yes
takeup.assign@1 69b37ac54107 2f072b6bf09f moved yes
takeup.bands@1 1ceca3ba7c7c 1ceca3ba7c7c held no
takeup.cell_diagnostics@1 1ceca3ba7c7c 1ceca3ba7c7c held no
takeup.compare@1 d9a4d861d0f9 4bafe488d9e2 moved no
takeup.gap@1 d77b1c352e4e 0415d6587dc1 moved yes
targets.compile@1 c85c41d28c9b 435713bc62fb moved yes
targets.problem@1 62f1be54c530 36844baf327b moved yes
transport.boundary@1 ccf0801a9bd8 fc9d10670f6a moved yes
transport.create@1 d72110e9a2b3 34d2268bb967 moved yes
transport.create@2 — 34d2268bb967 added no
transport.currency@1 5d1815597fd9 9c3457ddb908 moved no
transport.package@1 8e685c71c808 8e685c71c808 held yes
transport.quantile_map@1 1e758c4fa713 390e7d37a1f5 moved no
transport.scenario_override@1 55da7a2fda7d 062f88411ad6 moved no
transport.unit_attributes@1 a1ec47db6dc4 f50ec74a1bfe moved yes

Totals: 22 moved, 14 held, 1 added. 18 of the moved kernels sit in NZ's transport_graph.json or entitlement_graph.json.


Generated by Claude Code

Main adds #1441, which touches transport/entitlement.py and its tests.
Nothing conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review November 2, 2026 00:18
@MaxGhenis
MaxGhenis merged commit 1a42e5e into main Nov 2, 2026
10 checks passed
@MaxGhenis
MaxGhenis deleted the be/declared-units-s1 branch November 2, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant