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:
objectA 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).
descriptionis 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:
objectA 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_roleMUST resolve to a singletonAccount(never anotherAccountTemplate) — 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_instancesuses when synthesizing per-template instances. Both default toNone; 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’srolefield) 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:
objectA 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_groupflags collapse intolen(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):
childrenis nowtuple[ChainChildSpec, ...]— the AB.4 chain-levelfan_in+expected_parent_countflags 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:
objectOne entry in
Chain.children— name plus optional fan-in flag.AB.6 (2026-05-19) relocated
fan_in+expected_parent_countfrom 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.nameresolves to either a Rail or a TransferTemplate (same resolution rules R5 / S4 apply per-child entry).fan_in=Truedeclares 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 whenfan_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:
objectPer-(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_singletoncarries 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 NULLas a row-level predicate — under the SPEC, every firing of a required child IS a chain firing, sotransfer_parent_idis 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:
objectThe 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:
rails (tuple[RailContract, ...])
templates (tuple[TemplateContract, ...])
chain_edges (tuple[ChainEdgeContract, ...])
limits (tuple[LimitContract, ...])
- chain_edges: tuple[ChainEdgeContract, ...]
- limits: tuple[LimitContract, ...]
- rails: tuple[RailContract, ...]
- templates: tuple[TemplateContract, ...]
- class recon_gen.common.l2.ColumnPredicate(column, kind, expected)[source]
Bases:
objectOne per-column expectation on the rows selected by a contract.
columnis a plain transactions column name (account_role,amount_direction,transfer_parent_id) OR the dotted formmetadata.<key>for a JSON metadata field. BT.4’s SQL composer knows to translate the dotted form to aJSON_VALUEextraction.kinddiscriminates the predicate shape:equals—column = expected(expectedisstr)one_of—column IN expected(expectedistuple[str, ...])not_null—column IS NOT NULL(expectedisNone)
- 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:
objectA 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.rolevalues 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:
objectA loaded + parsed L2 institutional model.
Z.C (2026-05-15) — the legacy
instancefield has been dropped. The DB-table prefix lives on the cfg ascfg.db_table_prefix; the QS-resource-ID prefix lives ascfg.deployment_name. Each L2 YAML is pure topology + persona + theme; the cfg yaml carries the deployment-specific identifiers.- Parameters:
accounts (tuple[Account, ...])
account_templates (tuple[AccountTemplate, ...])
rails (tuple[TwoLegRail | SingleLegRail, ...])
transfer_templates (tuple[TransferTemplate, ...])
chains (tuple[Chain, ...])
limit_schedules (tuple[LimitSchedule, ...])
description (str | None)
institution_name (str | None)
institution_acronym (str | None)
role_business_day_offsets (dict[str, int] | None)
theme (ThemePreset | None)
investigation_personas (tuple[InvestigationPersona, ...])
- account_templates: tuple[AccountTemplate, ...]
- 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:
ValueErrorRaised when an L2 YAML fails to load or fails per-entity validation.
- exception recon_gen.common.l2.L2ValidationError[source]
Bases:
ValueErrorRaised when a loaded
L2Instancefails cross-entity validation.
- class recon_gen.common.l2.LimitContract(parent_role, rail, direction, cap, editor_path)[source]
Bases:
objectPer-(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 translatecapinto 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:
objectA 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.Limitsmap; 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) renamedtransfer_type→rail— 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) addeddirection: a single(parent_role, rail)may now carry two schedules — oneOutbound(classic per-rail send cap) and oneInbound(AML / structuring threshold on inbound volume). The validator broadens uniqueness from(parent_role, rail)to(parent_role, rail, direction). DefaultOutboundkeeps 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:
objectPer-Rail expectations on rows tagged
rail_name = <name>.leg_kinddiscriminates 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:
objectNarrows
<prefix>_transactionsto 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:
objectA Rail that produces one Transaction leg per firing.
Per SPEC: single-leg rails MUST be reconciled by EITHER a
TransferTemplatewhoseleg_railsincludes this rail OR an aggregating rail whosebundles_activityincludes this rail’sname. A single-leg rail without either reconciliation path is a configuration error (validator catches at load).leg_direction = Variablemeans 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:
objectPer-TransferTemplate expectations on rows tagged
template_name = <name>.leg_rail_namesis retained on the side (rather than only as aone_ofpredicate overrail_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:
objectEverything 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]
- link_tint: str
- logo: str | None
- 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:
objectA multi-leg shared Transfer that bundles many Rail firings.
Per SPEC: every firing of a
leg_railsrail with the sametransfer_keyMetadata values posts to the same shared Transfer. L1 Conservation flags the Transfer if its legs don’t sum toexpected_net; L1 Timeliness flags any leg that posts after the derivedTransfer.Completion.A Rail listed in
leg_railsMUST NOT also fire standalone Transfers — its firings always join the shared Transfer matching thetransfer_keyvalues.leg_rail_xor_groups(AB.3) declares mutually-exclusive subsets ofleg_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_violationmatview (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:
objectA Rail that produces two Transaction legs (debit + credit) per firing.
When fired as a standalone Transfer,
expected_netMUST be set (typically0); L1 Conservation enforcesΣ legs = expected_net. When the rail is a leg-pattern of a TransferTemplate,expected_netMUST 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:
originshorthands “both legs share this Origin”;source_origin/destination_originoverride per leg when the legs differ (e.g., the leg touching an external counterparty isExternalForcePostedwhile the internal counterpart isInternalInitiated). The validator (rule O1) checks every leg resolves to an Origin under the SPEC’s resolution table.PostedRequirements / aging:
posted_requirementsadds Rail-specific fields beyond the auto-derived TransferKey + chain-Required-true parent_transfer_id (seederived.posted_requirements_for);max_pending_age+max_unbundled_ageare 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-
--l2case — 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_exampleinstance.The one built-in default every app’s build path uses when the caller doesn’t supply an explicit
l2_instance. Passload_instance(".../sasquatch_pr.yaml")for the canonical persona demo.- Return type:
- 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:
- Returns:
A
ColumnContractswith 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:
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, …).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.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_breachare embedded inline frominstance.limit_schedulesat 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 schemaclears stale state. The returned string can be fed straight topsqlorpsycopg2.cursor.execute(sql).dialectselects 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 newDialectenum value plus per-helper Oracle/Postgres-style branches incommon.sql.dialect.Z.C —
prefixis the cfg.db_table_prefix (formerly read off the droppedL2Instance.instancefield).- Return type:
str- Parameters:
instance (L2Instance)
prefix (str)
dialect (Dialect)
- recon_gen.common.l2.emit_schema_drop_sql(instance, *, prefix, dialect=Dialect.POSTGRES)[source]
Emit DROP statements for every per-prefix object
emit_schemacreates.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
psqlor splitting + executing per-statement. Idempotent — every DROP isIF EXISTS(or a swallow-already-gone PL/SQL block on Oracle). Useschema clean -o FILEfor the CLI surface.Z.C —
prefixis the cfg.db_table_prefix.- Return type:
str- Parameters:
instance (L2Instance)
prefix (str)
dialect (Dialect)
- 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=Falseto 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) raiseL2ValidationError.Y.2.gate.c.12 — when
RECON_GEN_RUN_DIRis set, the raw YAML is captured to<run-dir>/l2/<instance-prefix>.yamlfor per-run debugging. See_capture_to_run_dir.- Return type:
- 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:
The Rail’s own
posted_requirements(integrator-declared).TransferKeyfields for any TransferTemplate where this rail appears inleg_rails(auto-derived from the template’s grouping rule — a leg can’t be Posted without naming the grouping values).parent_transfer_idif 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
KeyErrorifrail_namedoesn’t match any declared rail.- Return type:
tuple[Identifier(str),...]- Parameters:
instance (L2Instance)
rail_name (Identifier)
- 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 usesDROP 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 —
prefixis the cfg.db_table_prefix.- Return type:
str- Parameters:
instance (L2Instance)
prefix (str)
dialect (Dialect)
- recon_gen.common.l2.validate(instance)[source]
Run every cross-entity validation rule on
instance.Fail-fast: raises
L2ValidationErroron the first rule violation with a message naming the offending field + the rule.- Return type:
None- Parameters:
instance (L2Instance)
Modules
Auto-derive a |
|
In-memory |
|
|
|
Per-L2-primitive column contracts (BT.5 — derivation for BT.4 triage). |
|
|
|
BTa.8 — ETL-feed gap plants for the bundled-demo path. |
|
Studio "Deploy changes" pipeline (X.4.g + BS.4 architecture shift). |
|
Computed views over a loaded |
|
Editor primitives — server-owned cascade for the X.4.e editor flow. |
|
YAML → |
|
BU.1.8 — Typed pipeline overlay primitives. |
|
BU.1 — Plant registry for the Trainer surface. |
|
LAYER 2 institutional-model primitives, typed 1:1 against |
|
Probe fetcher — BT.2's observed-row side. |
|
Prefix-aware SQL DDL emission for an |
|
Demo-seed primitives for any L2 instance. |
|
|
|
Studio session-state sidefile (X.4.h.7). |
|
In-memory |
|
Theme palette as an L2 model concept. |
|
Topology projection of an |
|
|
|
|
|
BT.4 — Exception triage gap detector. |
|
BV.4.0/4.1 — v-overlay lifecycle orchestration. |
|
|
Run every cross-entity validation rule on |