recon_gen.common.l2

LAYER 2 institutional model — typed primitives + loader + validator.

The production library code that drives every per-prefix L2 surface:

primitives.py — typed dataclasses for every L2 SPEC primitive. loader.py — YAML → primitives, with friendly error messages. validate.py — load-time SPEC validation rules + rejection tests. schema.py — prefix-aware SQL emission for L1 + L2 tables + matviews. seed.py — 90-day baseline + plant overlays (emit_full_seed). derived.py — Current* / computed_* helpers (PostedRequirements etc.).

External callers import from this package’s public surface (from recon_gen.common.l2 import L2Instance), not from any internal submodule.

class recon_gen.common.l2.Account(id, scope, name=None, role=None, parent_role=None, expected_eod_balance=None, description=None)[source]

Bases: object

A 1-of-1 account that exists exactly once in the institution.

Per SPEC: singletons that Rails reference by Role; the Role is technically optional but in practice required for any Account a Rail touches (per F1, enforced by the validator at load time).

description is free-form prose (markdown OK) read by handbook + training render templates per the SPEC’s “Description fields” rule. Optional at the type level but SHOULD be filled.

Parameters:
  • id (Identifier)

  • scope (Literal['internal', 'external'])

  • name (Name | None)

  • role (Identifier | None)

  • parent_role (Identifier | None)

  • expected_eod_balance (Decimal | None)

  • description (str | None)

description: str | None
expected_eod_balance: Decimal | None
id: Identifier
name: Name | None
parent_role: Identifier | None
role: Identifier | None
scope: Literal['internal', 'external']
class recon_gen.common.l2.AccountTemplate(role, scope, parent_role=None, expected_eod_balance=None, description=None, instance_id_template=None, instance_name_template=None)[source]

Bases: object

A class of accounts that exists in many instances at runtime.

Per SPEC: declares the SHAPE; the specific account instance for a given posting is selected at posting time (typically from Transaction.Metadata). parent_role MUST resolve to a singleton Account (never another AccountTemplate) — enforced by the validator at load time per the SPEC’s “singleton parent only” constraint.

instance_id_template + instance_name_template (M.4.2b) — optional Python str.format() templates the demo seed’s _materialize_instances uses when synthesizing per-template instances. Both default to None; the seed falls back to the legacy synthetic patterns ("cust-{n:03d}" for id, "Customer {n}" for name) so existing L2 fixtures don’t drift. Integrators opt in via YAML to control the persona’s per-template naming, e.g.:

instance_id_template: “cust-{n:03d}-bigfoot” instance_name_template: “Bigfoot-{n}”

Both templates support the placeholders {role} (the template’s role field) and {n} (1-indexed instance number). Loader rejects format strings that reference any other placeholder.

Parameters:
  • role (Identifier)

  • scope (Literal['internal', 'external'])

  • parent_role (Identifier | None)

  • expected_eod_balance (Decimal | None)

  • description (str | None)

  • instance_id_template (str | None)

  • instance_name_template (str | None)

description: str | None
expected_eod_balance: Decimal | None
instance_id_template: str | None
instance_name_template: str | None
parent_role: Identifier | None
role: Identifier
scope: Literal['internal', 'external']
recon_gen.common.l2.CadenceExpression

alias of str

class recon_gen.common.l2.Chain(parent, children, description=None)[source]

Bases: object

A firing rule: one parent + one list of candidate children.

Per SPEC: list cardinality carries the entire firing semantic — singleton ⇒ required (the child SHOULD fire; missing surfaces as an orphan exception); multi ⇒ XOR (exactly one of the listed children SHOULD fire per parent instance). The legacy required / xor_group flags collapse into len(children) (Z.A — locked 2026-05-13).

Aggregating rails MUST NOT appear in children (they don’t have per-Transfer parents — they sweep on cadence). Validator enforces.

AB.6 (2026-05-19): children is now tuple[ChainChildSpec, ...] — the AB.4 chain-level fan_in + expected_parent_count flags moved per-child to allow mixed-cardinality chains. Loader rejects chain-level fan_in / expected_parent_count with an actionable error pointing at the per-child shape (hard cut per AB.6.0 lock, no deprecation grace window).

Parameters:
  • parent (Identifier)

  • children (tuple[ChainChildSpec, ...])

  • description (str | None)

children: tuple[ChainChildSpec, ...]
description: str | None
parent: Identifier
class recon_gen.common.l2.ChainChildSpec(name, fan_in=False, expected_parent_count=None)[source]

Bases: object

One entry in Chain.children — name plus optional fan-in flag.

AB.6 (2026-05-19) relocated fan_in + expected_parent_count from chain-level to per-child. Motivation (per SPEC_gap_feedback §5): a single chain may carry mixed-cardinality children — some 1:1 (ACH / wire / check) AND one N:1 (batched payout) — which a single chain-level flag can’t express.

name resolves to either a Rail or a TransferTemplate (same resolution rules R5 / S4 apply per-child entry).

fan_in=True declares THIS child is N:1 — N parent firings may share one child Transfer (the batched-payout pattern). Validator requires fan_in children to resolve to TransferTemplates only.

expected_parent_count (when set) declares the exact number of parent firings per child Transfer. Set + matview flags exact-mismatch (parent count != expected). Unset + matview falls back to orphan-only detection (parent count < 2). Must be None when fan_in=False (validator C8b).

Parameters:
  • name (Identifier)

  • fan_in (bool)

  • expected_parent_count (int | None)

expected_parent_count: int | None
fan_in: bool
name: Identifier
class recon_gen.common.l2.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.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.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']
recon_gen.common.l2.CompletionExpression

alias of str

recon_gen.common.l2.Duration

alias of timedelta

class recon_gen.common.l2.InvestigationPersona(name, account_id, role)[source]

Bases: object

A curated AML / compliance scenario actor for handbook walkthroughs.

BXa.1 (2026-05-30): promoted from the hardcoded table that lived inside common/handbook/vocabulary.py::_sasquatch_pr_vocabulary. The handbook’s Investigation walkthroughs substitute these display names ({{ vocab.demo.investigation.layering_chain[0].name }} etc.) — curated narrative the L2 author writes, not deriveable from L2 topology. Sasquatch fixture carries 6 entries (Juniper Ridge LLC + Cascadia Trust Bank + Cascadia—Operations + Shell Company A/B/C); other operator L2s default to empty tuple and the existing {% if %} gates in the docs hide the walkthroughs that depend on the curated names.

role values currently in use: convergence_anchor, counterparty_bank, operations_account, shell_entity. Open enum — handbook template gates on specific role strings.

Parameters:
  • name (str)

  • account_id (str)

  • role (str)

account_id: str
name: str
role: str
class recon_gen.common.l2.L2Instance(accounts, account_templates, rails, transfer_templates, chains, limit_schedules, description=None, institution_name=None, institution_acronym=None, role_business_day_offsets=None, theme=None, investigation_personas=())[source]

Bases: object

A loaded + parsed L2 institutional model.

Z.C (2026-05-15) — the legacy instance field has been dropped. The DB-table prefix lives on the cfg as cfg.db_table_prefix; the QS-resource-ID prefix lives as cfg.deployment_name. Each L2 YAML is pure topology + persona + theme; the cfg yaml carries the deployment-specific identifiers.

Parameters:
account_templates: tuple[AccountTemplate, ...]
accounts: tuple[Account, ...]
chains: tuple[Chain, ...]
description: str | None
institution_acronym: str | None
institution_name: str | None
investigation_personas: tuple[InvestigationPersona, ...]
limit_schedules: tuple[LimitSchedule, ...]
rails: tuple[TwoLegRail | SingleLegRail, ...]
role_business_day_offsets: dict[str, int] | None
theme: ThemePreset | None
transfer_templates: tuple[TransferTemplate, ...]
exception recon_gen.common.l2.L2LoaderError[source]

Bases: ValueError

Raised when an L2 YAML fails to load or fails per-entity validation.

exception recon_gen.common.l2.L2ValidationError[source]

Bases: ValueError

Raised when a loaded L2Instance fails cross-entity validation.

class recon_gen.common.l2.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.LimitSchedule(parent_role, rail, cap, direction='Outbound', description=None)[source]

Bases: object

A daily cap on per-direction flow per (parent role, rail, direction).

Per SPEC: time-invariant in v1. The library projects each entry into the relevant StoredBalance.Limits map; L1’s Limit Breach invariant evaluates per child individually (the cap is per-child, not aggregated across siblings of the parent). Z.B (2026-05-15) renamed transfer_typerail — the field now references a Rail name directly, eliminating the templated-leg footgun where a LimitSchedule on transfer_type=<leg_rail_type> failed to fire on transactions tagged with the template’s transfer_type instead. AB.1 (2026-05-19) added direction: a single (parent_role, rail) may now carry two schedules — one Outbound (classic per-rail send cap) and one Inbound (AML / structuring threshold on inbound volume). The validator broadens uniqueness from (parent_role, rail) to (parent_role, rail, direction). Default Outbound keeps every pre-AB.1 YAML byte-equivalent.

Parameters:
  • parent_role (Identifier)

  • rail (RailName)

  • cap (Decimal)

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

  • description (str | None)

cap: Decimal
description: str | None
direction: Literal['Outbound', 'Inbound']
parent_role: Identifier
rail: RailName
recon_gen.common.l2.Money

alias of Decimal

recon_gen.common.l2.Origin

alias of str

class recon_gen.common.l2.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.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.SingleLegRail(name, metadata_keys, leg_role, leg_direction, origin=None, posted_requirements=<factory>, max_pending_age=None, max_unbundled_age=None, aggregating=False, bundles_activity=<factory>, cadence=None, description=None, metadata_value_examples=<factory>, amount_typical_range=None, firings_typical_per_period=None)[source]

Bases: object

A Rail that produces one Transaction leg per firing.

Per SPEC: single-leg rails MUST be reconciled by EITHER a TransferTemplate whose leg_rails includes this rail OR an aggregating rail whose bundles_activity includes this rail’s name. A single-leg rail without either reconciliation path is a configuration error (validator catches at load).

leg_direction = Variable means the leg’s amount AND direction are determined at posting time by a containing TransferTemplate’s ExpectedNet closure requirement. Each TransferTemplate MUST contain at most one Variable-direction leg.

Per-leg Origin overrides (source_origin / destination_origin) are deliberately absent here — they only make sense on a 2-leg rail. The loader rejects them at load if they appear in YAML for a single-leg rail (hard error, per the M.1a design call).

Parameters:
  • name (Identifier)

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

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

  • leg_direction (Literal['Debit', 'Credit', 'Variable'])

  • origin (str | None)

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

  • max_pending_age (timedelta | None)

  • max_unbundled_age (timedelta | None)

  • aggregating (bool)

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

  • cadence (str | None)

  • description (str | None)

  • metadata_value_examples (tuple[tuple[Identifier, tuple[str, ...]], ...])

  • amount_typical_range (tuple[Decimal, Decimal] | None)

  • firings_typical_per_period (FiringsTypicalPerPeriod | None)

aggregating: bool
amount_typical_range: tuple[Decimal, Decimal] | None
bundles_activity: tuple[Identifier, ...]
cadence: str | None
description: str | None
firings_typical_per_period: FiringsTypicalPerPeriod | None
leg_direction: Literal['Debit', 'Credit', 'Variable']
leg_role: tuple[Identifier, ...]
max_pending_age: timedelta | None
max_unbundled_age: timedelta | None
metadata_keys: tuple[Identifier, ...]
metadata_value_examples: tuple[tuple[Identifier, tuple[str, ...]], ...]
name: Identifier
origin: str | None
posted_requirements: tuple[Identifier, ...]
class recon_gen.common.l2.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
class recon_gen.common.l2.ThemePreset(theme_name, version_description, analysis_name_prefix, data_colors, empty_fill_color, gradient, primary_bg, secondary_bg, primary_fg, secondary_fg, accent, accent_fg, link_tint, danger, danger_fg, warning, warning_fg, success, success_fg, dimension, dimension_fg, measure, measure_fg, logo=None, favicon=None)[source]

Bases: object

Everything that varies between theme variants.

Parameters:
  • theme_name (str)

  • version_description (str)

  • analysis_name_prefix (str | None)

  • data_colors (list[str])

  • empty_fill_color (str)

  • gradient (list[str])

  • primary_bg (str)

  • secondary_bg (str)

  • primary_fg (str)

  • secondary_fg (str)

  • accent (str)

  • accent_fg (str)

  • link_tint (str)

  • danger (str)

  • danger_fg (str)

  • warning (str)

  • warning_fg (str)

  • success (str)

  • success_fg (str)

  • dimension (str)

  • dimension_fg (str)

  • measure (str)

  • measure_fg (str)

  • logo (str | None)

  • favicon (str | None)

accent: str
accent_fg: str
analysis_name_prefix: str | None
danger: str
danger_fg: str
data_colors: list[str]
dimension: str
dimension_fg: str
empty_fill_color: str
favicon: str | None
gradient: list[str]
measure: str
measure_fg: str
primary_bg: str
primary_fg: str
secondary_bg: str
secondary_fg: str
success: str
success_fg: str
theme_name: str
version_description: str
warning: str
warning_fg: str
class recon_gen.common.l2.TransferTemplate(name, expected_net, transfer_key, completion, leg_rails, leg_rail_xor_groups=(), description=None, firings_typical_per_period=None)[source]

Bases: object

A multi-leg shared Transfer that bundles many Rail firings.

Per SPEC: every firing of a leg_rails rail with the same transfer_key Metadata values posts to the same shared Transfer. L1 Conservation flags the Transfer if its legs don’t sum to expected_net; L1 Timeliness flags any leg that posts after the derived Transfer.Completion.

A Rail listed in leg_rails MUST NOT also fire standalone Transfers — its firings always join the shared Transfer matching the transfer_key values.

leg_rail_xor_groups (AB.3) declares mutually-exclusive subsets of leg_rails — exactly one member of each inner tuple SHOULD fire per Transfer. Empty default keeps every pre-AB.3 template byte- equivalent; the structural validator (C1a-d) enforces members ⊆ leg_rails, members are Variable-direction, no overlap between groups, ≥2 members per group. Runtime “exactly one fires” check lives in the _xor_group_violation matview (AB.3.3).

Parameters:
  • name (Identifier)

  • expected_net (Decimal)

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

  • completion (str)

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

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

  • description (str | None)

  • firings_typical_per_period (FiringsTypicalPerPeriod | None)

completion: str
description: str | None
expected_net: Decimal
firings_typical_per_period: FiringsTypicalPerPeriod | None
leg_rail_xor_groups: tuple[tuple[Identifier, ...], ...]
leg_rails: tuple[Identifier, ...]
name: Identifier
transfer_key: tuple[Identifier, ...]
recon_gen.common.l2.TransferType

alias of str

class recon_gen.common.l2.TwoLegRail(name, metadata_keys, source_role, destination_role, origin=None, source_origin=None, destination_origin=None, expected_net=None, posted_requirements=<factory>, max_pending_age=None, max_unbundled_age=None, aggregating=False, bundles_activity=<factory>, cadence=None, description=None, metadata_value_examples=<factory>, amount_typical_range=None, firings_typical_per_period=None)[source]

Bases: object

A Rail that produces two Transaction legs (debit + credit) per firing.

When fired as a standalone Transfer, expected_net MUST be set (typically 0); L1 Conservation enforces Σ legs = expected_net. When the rail is a leg-pattern of a TransferTemplate, expected_net MUST be unset — the template owns the bundle’s ExpectedNet. Per F3 this is a cross-entity validation rule (the validator’s pass 2).

Per-leg Origin: origin shorthands “both legs share this Origin”; source_origin / destination_origin override per leg when the legs differ (e.g., the leg touching an external counterparty is ExternalForcePosted while the internal counterpart is InternalInitiated). The validator (rule O1) checks every leg resolves to an Origin under the SPEC’s resolution table.

PostedRequirements / aging: posted_requirements adds Rail-specific fields beyond the auto-derived TransferKey + chain-Required-true parent_transfer_id (see derived.posted_requirements_for); max_pending_age + max_unbundled_age are aging-watch durations.

Parameters:
  • name (Identifier)

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

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

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

  • origin (str | None)

  • source_origin (str | None)

  • destination_origin (str | None)

  • expected_net (Decimal | None)

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

  • max_pending_age (timedelta | None)

  • max_unbundled_age (timedelta | None)

  • aggregating (bool)

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

  • cadence (str | None)

  • description (str | None)

  • metadata_value_examples (tuple[tuple[Identifier, tuple[str, ...]], ...])

  • amount_typical_range (tuple[Decimal, Decimal] | None)

  • firings_typical_per_period (FiringsTypicalPerPeriod | None)

aggregating: bool
amount_typical_range: tuple[Decimal, Decimal] | None
bundles_activity: tuple[Identifier, ...]
cadence: str | None
description: str | None
destination_origin: str | None
destination_role: tuple[Identifier, ...]
expected_net: Decimal | None
firings_typical_per_period: FiringsTypicalPerPeriod | None
max_pending_age: timedelta | None
max_unbundled_age: timedelta | None
metadata_keys: tuple[Identifier, ...]
metadata_value_examples: tuple[tuple[Identifier, tuple[str, ...]], ...]
name: Identifier
origin: str | None
posted_requirements: tuple[Identifier, ...]
source_origin: str | None
source_role: tuple[Identifier, ...]
recon_gen.common.l2.default_l2_bytes()[source]

Raw bytes of the packaged default spec_example.yaml.

For the audit fingerprint / provenance hash of the no---l2 case — hashing the exact bytes the default is loaded from keeps the fingerprint deterministic + byte-identical to what the report claims it hashed.

Return type:

bytes

recon_gen.common.l2.default_l2_instance()[source]

Load + validate the persona-neutral default spec_example instance.

The one built-in default every app’s build path uses when the caller doesn’t supply an explicit l2_instance. Pass load_instance(".../sasquatch_pr.yaml") for the canonical persona demo.

Return type:

L2Instance

recon_gen.common.l2.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.

recon_gen.common.l2.emit_schema(instance, *, prefix, dialect=Dialect.POSTGRES)[source]

Emit the full DDL script for an L2 instance’s prefixed L1 schema.

Three layers, all per L2 instance prefix:

  1. Base tables<prefix>_transactions + <prefix>_daily_balances, v6 column shape (entry BIGSERIAL, amount_money + amount_direction, transfer_parent_id, rail_name, template_name, bundle_id, supersedes, …).

  2. Current* views<prefix>_current_transactions + <prefix>_current_daily_balances, materializing L1’s max-Entry-per-logical-key theorems so dashboard SQL is transparent to technical-error supersession.

  3. L1 invariant views (M.1a.7)<prefix>_drift / <prefix>_ledger_drift / <prefix>_overdraft / <prefix>_expected_eod_balance_breach / <prefix>_limit_breach (plus 2 helpers: <prefix>_computed_subledger_balance + <prefix>_computed_ledger_balance). Each materializes one of the SPEC’s L1 SHOULD-constraints as a queryable exception surface; rows in these views are the constraint violations. Caps for <prefix>_limit_breach are embedded inline from instance.limit_schedules at view-emit time (CASE branches per declared (parent_role, rail) pair) so the view DDL stays JSON-path-portable.

Idempotent: every CREATE is preceded by a DROP IF EXISTS so re-running the same apply schema clears stale state. The returned string can be fed straight to psql or psycopg2.cursor.execute(sql).

dialect selects the SQL flavor. P.3.d unblocked Oracle by threading dialect helpers through every template; both branches are now first-class. New dialects would need a new Dialect enum value plus per-helper Oracle/Postgres-style branches in common.sql.dialect.

Z.C — prefix is the cfg.db_table_prefix (formerly read off the dropped L2Instance.instance field).

Return type:

str

Parameters:
recon_gen.common.l2.emit_schema_drop_sql(instance, *, prefix, dialect=Dialect.POSTGRES)[source]

Emit DROP statements for every per-prefix object emit_schema creates.

The teardown counterpart of emit_schema. Composes the same private drop helpers that prelude every CREATE in the full schema output, plus the base-table and base-index drops.

Order matters: matviews → views → tables, with Inv matviews and L1 invariant matviews first (they depend on the Current* matviews which depend on the base tables). Indexes get dropped before the tables they index.

Returns one SQL string suitable for piping to psql or splitting + executing per-statement. Idempotent — every DROP is IF EXISTS (or a swallow-already-gone PL/SQL block on Oracle). Use schema clean -o FILE for the CLI surface.

Z.C — prefix is the cfg.db_table_prefix.

Return type:

str

Parameters:
recon_gen.common.l2.load_instance(path, *, validate=True)[source]

Load + validate an L2 YAML file into an L2Instance.

By default (validate=True), runs the full cross-entity validation pass (common.l2.validate.validate) before returning, so a malformed instance fails at YAML load time rather than at first render. This is the M.2d.2 contract — every cross-entity SHOULD-constraint listed in the SPEC’s Validation Rules section is a parse-time error by default.

Pass validate=False to skip the cross-entity pass — useful for narrow loader tests that intentionally exercise partial fixtures without satisfying every reference-resolution rule.

Loader-side errors (malformed YAML, missing required fields, type shapes, identifier format, Money coercion) raise L2LoaderError. Validator-side errors (reference resolution, uniqueness, cardinality, vocabulary, Origin resolution) raise L2ValidationError.

Y.2.gate.c.12 — when RECON_GEN_RUN_DIR is set, the raw YAML is captured to <run-dir>/l2/<instance-prefix>.yaml for per-run debugging. See _capture_to_run_dir.

Return type:

L2Instance

Parameters:
  • path (Path | str)

  • validate (bool)

recon_gen.common.l2.posted_requirements_for(instance, rail_name)[source]

Return the resolved PostedRequirements set for rail_name.

Unions three sources per the SPEC’s “PostedRequirements” subsection:

  1. The Rail’s own posted_requirements (integrator-declared).

  2. TransferKey fields for any TransferTemplate where this rail appears in leg_rails (auto-derived from the template’s grouping rule — a leg can’t be Posted without naming the grouping values).

  3. parent_transfer_id if this rail is the singleton child of any Chain row, OR if a TransferTemplate containing this rail is the singleton child of any Chain row. Under the Z.A grammar collapse, a singleton-children row encodes “required” semantics (parent firing always invokes this child) — every firing IS a chain firing, so parent_transfer_id is always populated. A multi-children (XOR) row makes parent_transfer_id optional — only one of the siblings fires per parent invocation.

Output is deduped and sorted lexicographically — deterministic across runs, integrator-declared overlap with TransferKey auto-derivation collapses cleanly.

Raises KeyError if rail_name doesn’t match any declared rail.

Return type:

tuple[Identifier (str), ...]

Parameters:
recon_gen.common.l2.refresh_matviews_sql(instance, *, prefix, dialect=Dialect.POSTGRES)[source]

Emit REFRESH MATERIALIZED VIEW commands in dependency order.

M.1a.9 made every L1-pipeline view a MATERIALIZED VIEW (kills the correlated-subquery cost the deployed dashboard pays per visual on DIRECT_QUERY mode). Refresh contract for integrators: after every batch insert into the base tables, call this SQL to recompute every dependent matview. Order matters — leaves first, then helpers, then L1 invariants — because a downstream matview’s REFRESH reads from upstream matview data.

Returns one REFRESH MATERIALIZED VIEW <name>; per line on PG / Oracle. SQLite has no matviews — refresh becomes a per-table DELETE FROM <name>; INSERT INTO <name> <body>; pair, where <body> is the same SELECT the matview was originally created with. To avoid duplicating every matview body here, the SQLite branch uses DROP TABLE CREATE TABLE AS <body> — re-runs the schema’s matview-create SQL by tearing down + rebuilding.

Caller splits + executes (psycopg2’s cursor.execute can’t run multiple statements separated by ; reliably; the verify script splits on ;n and runs each per-statement).

Z.C — prefix is the cfg.db_table_prefix.

Return type:

str

Parameters:
recon_gen.common.l2.validate(instance)[source]

Run every cross-entity validation rule on instance.

Fail-fast: raises L2ValidationError on the first rule violation with a message naming the offending field + the rule.

Return type:

None

Parameters:

instance (L2Instance)

Modules

auto_scenario

Auto-derive a ScenarioPlant covering every L1 invariant from an L2 instance.

cache

In-memory L2Instance cache + atomic file-write primitive (X.4.a.6).

config_table

<prefix>_config_kv — flattened cfg + L2 JSON tree (BC.12).

contract

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

coverage

coverage_for() — per-L2-primitive presence in <prefix>_transactions.

demo_etl_gaps

BTa.8 — ETL-feed gap plants for the bundled-demo path.

deploy_pipeline

Studio "Deploy changes" pipeline (X.4.g + BS.4 architecture shift).

derived

Computed views over a loaded L2Instance (M.1a.4).

editor

Editor primitives — server-owned cascade for the X.4.e editor flow.

loader

YAML → L2Instance loader.

pipeline_overlays

BU.1.8 — Typed pipeline overlay primitives.

plant_registry

BU.1 — Plant registry for the Trainer surface.

primitives

LAYER 2 institutional-model primitives, typed 1:1 against SPEC.md.

probe

Probe fetcher — BT.2's observed-row side.

schema

Prefix-aware SQL DDL emission for an L2Instance (M.1.4 + M.1.5).

seed

Demo-seed primitives for any L2 instance.

serializer

serialize_l2(instance) str — round-trip-stable YAML emit (X.4.d.3).

studio_state

Studio session-state sidefile (X.4.h.7).

tg_cache

In-memory TestGeneratorConfig cache for Studio's data-shaping panel.

theme

Theme palette as an L2 model concept.

topology

Topology projection of an L2Instance — typed value object + renderer.

trainer

plants_per_node() — derive per-topology-node planted-exception counts from an L2 scenario object (X.4.c.6).

trainer_timeline

compute_plant_timeline — project planted exceptions onto a per-day timeline (X.4.h.6.a).

triage

BT.4 — Exception triage gap detector.

v_overlay

BV.4.0/4.1 — v-overlay lifecycle orchestration.

validate(instance)

Run every cross-entity validation rule on instance.