recon_gen.common.l2.contract

Per-L2-primitive column contracts (BT.5 — derivation for BT.4 triage).

Pure-function derivation over a loaded L2Instance. The output is a typed map that names, per L2 entity, the rows on <prefix>_transactions that entity owns plus the per-column predicates those rows must satisfy. BT.4’s exception-triage surface walks the result against an observed runtime snapshot and surfaces each gap as a card with a deep link to the relevant editor page.

BT.0 lock 4 anchored the design call: the typed L2 primitives carry enough to derive the contract — no new domain model, no DB-side work, no new validator. This module exists so BT.4 doesn’t have to re-walk the primitives itself.

Severability: imports primitives only. The editor-path strings mirror common/html/_studio_editor_routes.py::_entity_id (the route shape’s authoritative source) but the link target is just a string — this module doesn’t import the html layer.

The selectors + predicates are renderer-agnostic on purpose. BT.4’s SQL composer reads them; a unit test reads them; the same shape would serve a CLI recon-gen probe if that ever lands.

Functions

derive_column_contracts(instance)

Derive per-L2-primitive column contracts from a loaded L2Instance.

Classes

ChainEdgeContract(parent, child, ...)

Per-(parent, child) chain expectation.

ColumnContracts(rails, templates, ...)

The full derivation result.

ColumnPredicate(column, kind, expected)

One per-column expectation on the rows selected by a contract.

LimitContract(parent_role, rail, direction, ...)

Per-(parent_role, rail, direction) cap.

RailContract(rail_name, leg_kind, selector, ...)

Per-Rail expectations on rows tagged rail_name = <name>.

RowSelector(column, equals)

Narrows <prefix>_transactions to the rows one contract owns.

TemplateContract(template_name, selector, ...)

Per-TransferTemplate expectations on rows tagged template_name = <name>.

class recon_gen.common.l2.contract.ChainEdgeContract(parent, child, is_singleton, fan_in, expected_parent_count, predicates, editor_path)[source]

Bases: object

Per-(parent, child) chain expectation.

Z.A grammar: a chain with one child encodes required semantics (the child always fires under the parent); a chain with multiple children encodes XOR (exactly one fires per parent invocation). is_singleton carries that distinction so BT.4 can phrase the diagnosis appropriately (“missing required child” vs “missing XOR sibling fire”).

Singleton chains add transfer_parent_id NOT NULL as a row-level predicate — under the SPEC, every firing of a required child IS a chain firing, so transfer_parent_id is always populated. XOR chains do NOT add the predicate: the matview-level “exactly-one-fired” check lives in _v_config_chain_children (BS.5), not at the row level.

The chain has no single selector column on the transactions table (the chain identity is parent-name + child-name + transfer_parent_id linkage), so the contract omits selector — BT.4 composes the SQL against the rows tagged with the child name.

Parameters:
  • parent (Identifier)

  • child (Identifier)

  • is_singleton (bool)

  • fan_in (bool)

  • expected_parent_count (int | None)

  • predicates (tuple[ColumnPredicate, ...])

  • editor_path (str)

child: Identifier
editor_path: str
expected_parent_count: int | None
fan_in: bool
is_singleton: bool
parent: Identifier
predicates: tuple[ColumnPredicate, ...]
class recon_gen.common.l2.contract.ColumnContracts(rails, templates, chain_edges, limits)[source]

Bases: object

The full derivation result.

All four collections are tuples (frozen + iteration-stable). Order mirrors the L2Instance declaration order so a test asserting on a specific contract’s index stays deterministic across re-derivations of the same instance.

Parameters:
chain_edges: tuple[ChainEdgeContract, ...]
limits: tuple[LimitContract, ...]
rails: tuple[RailContract, ...]
templates: tuple[TemplateContract, ...]
class recon_gen.common.l2.contract.ColumnPredicate(column, kind, expected)[source]

Bases: object

One per-column expectation on the rows selected by a contract.

column is a plain transactions column name (account_role, amount_direction, transfer_parent_id) OR the dotted form metadata.<key> for a JSON metadata field. BT.4’s SQL composer knows to translate the dotted form to a JSON_VALUE extraction.

kind discriminates the predicate shape:

  • equalscolumn = expected (expected is str)

  • one_ofcolumn IN expected (expected is tuple[str, ...])

  • not_nullcolumn IS NOT NULL (expected is None)

Parameters:
  • column (str)

  • kind (Literal['equals', 'one_of', 'not_null'])

  • expected (object)

column: str
expected: object
kind: Literal['equals', 'one_of', 'not_null']
class recon_gen.common.l2.contract.LimitContract(parent_role, rail, direction, cap, editor_path)[source]

Bases: object

Per-(parent_role, rail, direction) cap.

LimitSchedule is balance-side (aggregated over <prefix>_daily_balances) rather than per-row; carrying it as a distinct contract kind keeps BT.4 from having to translate cap into a row-level predicate. The triage page renders limits in their own panel.

Parameters:
  • parent_role (Identifier)

  • rail (RailName)

  • direction (Literal['Outbound', 'Inbound'])

  • cap (Decimal)

  • editor_path (str)

cap: Decimal
direction: Literal['Outbound', 'Inbound']
editor_path: str
parent_role: Identifier
rail: RailName
class recon_gen.common.l2.contract.RailContract(rail_name, leg_kind, selector, predicates, editor_path)[source]

Bases: object

Per-Rail expectations on rows tagged rail_name = <name>.

leg_kind discriminates the source primitive so BT.4 can label the gap card (“Two-leg rail expects …”, “Single-leg rail expects …”) without re-walking the L2.

Parameters:
  • rail_name (Identifier)

  • leg_kind (Literal['two_leg', 'single_leg'])

  • selector (RowSelector)

  • predicates (tuple[ColumnPredicate, ...])

  • editor_path (str)

editor_path: str
leg_kind: Literal['two_leg', 'single_leg']
predicates: tuple[ColumnPredicate, ...]
rail_name: Identifier
selector: RowSelector
class recon_gen.common.l2.contract.RowSelector(column, equals)[source]

Bases: object

Narrows <prefix>_transactions to the rows one contract owns.

Always a column-equals-literal — the L2 primitives that own transactions rows (Rail / TransferTemplate) each project to exactly one selector column (rail_name / template_name).

Parameters:
  • column (str)

  • equals (str)

column: str
equals: str
class recon_gen.common.l2.contract.TemplateContract(template_name, selector, predicates, leg_rail_names, editor_path)[source]

Bases: object

Per-TransferTemplate expectations on rows tagged template_name = <name>.

leg_rail_names is retained on the side (rather than only as a one_of predicate over rail_name) so BT.4 can render the template’s per-leg fan-out — the predicate is the gate, the list is the explanation.

Parameters:
  • template_name (Identifier)

  • selector (RowSelector)

  • predicates (tuple[ColumnPredicate, ...])

  • leg_rail_names (tuple[Identifier, ...])

  • editor_path (str)

editor_path: str
leg_rail_names: tuple[Identifier, ...]
predicates: tuple[ColumnPredicate, ...]
selector: RowSelector
template_name: Identifier
recon_gen.common.l2.contract.derive_column_contracts(instance)[source]

Derive per-L2-primitive column contracts from a loaded L2Instance.

Pure function. No I/O, no caching, no mutation of the input. Output is BT.4-consumable directly: for each entity, BT.4 SELECTs the rows matching the entity’s selector then evaluates the predicates against the result set.

Parameters:

instance (L2Instance) – The L2 model to derive against.

Return type:

ColumnContracts

Returns:

A ColumnContracts with one entry per L2-declared Rail, one per TransferTemplate, one per (parent, child) chain edge, and one per LimitSchedule row.