# Hand-assembled from the SPEC's "Worked example shapes" section
# (SPEC.md §618+). Used by tests/test_seed_persona_clean.py to verify
# that common.l2.seed produces SQL with ZERO persona-leak strings —
# i.e., that the seed primitives lifted in M.2d.5 are L1-clean and
# don't smuggle in Sasquatch / Bigfoot / SNB / FRB literals.
#
# Every singleton + role here uses a generic, persona-neutral name. If
# the seed accidentally hardcodes a Sasquatch identifier, the
# persona-clean test catches it.
#
# CP — exhibits per-entity business_day_offset on role-shared accounts.
# The two CustomerSubledger singletons (cust-001 + cust-002) below
# share the same role but carry DISTINCT offsets (0 = midnight UTC,
# +5 ≈ Karachi-style EOD). The CustomerSubledger template also carries
# a non-trivial offset (-3) so its materialized instances exhibit a
# template-driven offset (Lock 4 — template offset fans out to instances).
# Values are strawman picks per CP.6 — tweak as the demo narrative
# needs without rebalancing the offset Lock contract.

description: |
  Generic SPEC-shaped instance for cleanliness testing of the lifted
  common.l2.seed primitives.

accounts:
  - id: clearing-suspense
    name: Clearing Suspense
    role: ClearingSuspense
    scope: internal
    expected_eod_balance: 0
    # CL.11 — Clearing Suspense is the one demo account flipped to
    # explicit_daily per CL.0 audit §2.3 lock. Everything else on
    # spec_example uses the sparse default. Clearing suspense MUST
    # report daily (regulatory reconciliation against the upstream
    # rail), so explicit_daily is the natural pick + gives the
    # balance_cadence_gap trainer plant a target.
    balance_cadence: explicit_daily

  - id: north-pool
    name: North Pool
    role: NorthPool
    scope: internal
    expected_eod_balance: 0

  - id: south-pool
    name: South Pool
    role: SouthPool
    scope: internal
    expected_eod_balance: 0

  - id: customer-ledger
    name: Customer Ledger
    role: CustomerLedger
    scope: internal

  - id: external-counterparty-one
    name: External Counterparty One
    role: ExternalCounterparty
    scope: external

  - id: cust-001
    name: Customer Number One
    role: CustomerSubledger
    scope: internal
    parent_role: CustomerLedger
    # CP — midnight-UTC EOD cutoff (the default-explicit pick on a
    # role-shared singleton pair; cust-002 below carries +5).
    business_day_offset: 0

  - id: cust-002
    name: Customer Number Two
    role: CustomerSubledger
    scope: internal
    parent_role: CustomerLedger
    # CP — Karachi-style EOD (+5h). Distinct from cust-001 above to
    # exercise the role-shared per-entity asymmetry: two accounts with
    # the SAME role can carry DIFFERENT offsets (per-entity is the
    # whole point of CP; the deleted role-level map couldn't express
    # this shape).
    business_day_offset: 5

account_templates:
  # CF.0 (2026-06-04) regression fixture — AlphaCustomerStub is
  # ALPHABETICALLY FIRST but has ZERO rails / limit-schedules referencing
  # it. Pre-CF.0 `_pick_template` returned `sorted(...)[0]` →
  # AlphaCustomerStub → drift + limit_breach plants raised ValueError
  # before any SQL (reviewer-confirmed; see docs/audits/_archive/v13_1_1_repro.md).
  # Post-CF.0 the picker scores each template by materializable-plant
  # count and picks the highest; AlphaCustomerStub scores 0 while
  # CustomerSubledger scores 5/5, so the locked seed bytes stay
  # IDENTICAL. If this template ever silently picks AlphaCustomerStub
  # the locked-seed determinism test red-walls — that's the regression
  # gate working as intended.
  - role: AlphaCustomerStub
    scope: internal
    parent_role: CustomerLedger
    instance_id_template: alpha-stub-{n:03d}
    instance_name_template: Alpha Stub {n}
  - role: CustomerSubledger
    scope: internal
    parent_role: CustomerLedger
    # Non-colliding instance ids — the singletons above already use
    # cust-001 / cust-002 (the fallback pattern), so the template
    # picks a distinct ``tmpl-cust-...`` shape. Per validator U7:
    # template-generated ids MUST NOT collide with declared singletons.
    instance_id_template: tmpl-cust-{n:03d}
    instance_name_template: Demo Customer {n}
    # CP — template-level offset (-3h, e.g., Halifax-style EOD). Every
    # instance materialized from this template inherits this offset
    # (Lock 4). U2 makes (role) unique across templates so the
    # template offset naturally fans out without ambiguity.
    business_day_offset: -3

rails:
  # Two-leg with per-leg origin — used by drift plant background postings
  # AND stuck_pending plant (carries max_pending_age).
  - name: ExternalRailInbound
    source_role: ExternalCounterparty
    destination_role: CustomerSubledger
    expected_net: 0
    source_origin: ExternalForcePosted
    destination_origin: InternalInitiated
    metadata_keys: [external_reference]
    posted_requirements: [external_reference]
    max_pending_age: PT24H
    # AB.5 (E7): typical inbound customer-rail amount band.
    amount_typical_range: ["50.00", "5000.00"]
    # AF (E8): compact form — ~20-50 inbound ACH per business day for a
    # small institution. Composes with amount_typical_range above:
    # count × amount = realistic per-day inbound aggregate.
    firings_typical_per_period: [20, 50]

  # Two-leg standalone outbound — used by limit-breach plant.
  - name: ExternalRailOutbound
    source_role: CustomerSubledger
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [external_reference]
    # AB.5 (E7): outbound has a higher upper bound — vendor payments
    # and larger transfers happen here too.
    amount_typical_range: ["50.00", "10000.00"]

  # Single-leg debit — used by stuck_unbundled plant (max_unbundled_age
  # only meaningful on rails appearing in some bundles_activity, satisfied
  # by PoolBalancing below).
  - name: SubledgerCharge
    leg_role: CustomerSubledger
    leg_direction: Debit
    origin: InternalInitiated
    metadata_keys: [merchant_id, customer_id, settlement_period]
    max_unbundled_age: PT4H
    # AB.5 (E7): subledger fee accrual band — small charges.
    amount_typical_range: ["1.00", "100.00"]
    # AF (E8): mapping form — fee accruals settle on a monthly cadence
    # (~60-90 charges per month). Demonstrates the {period, range}
    # form (period != business_day).
    firings_typical_per_period:
      period: month
      range: [60, 90]

  # AB.3.5.spec (2026-05-19) — three Variable-direction settlement-timing
  # variants for the AB.3 XOR group demo. Each represents a different
  # settlement cadence (auto-confirm at sale, standard T+1, slow
  # manual-review T+3); per-merchant config picks ONE per
  # SettlementTimingCycle Transfer. The AB.3.3 xor_group_violation
  # matview surfaces Transfers where zero variants fire (missed) or two+
  # fire (overlap). All three share leg_role=ClearingSuspense (the
  # generic singleton) so the baseline emitter has a stable target
  # without fan-out across template instances.
  - name: SettlementAuto
    leg_role: ClearingSuspense
    leg_direction: Variable
    origin: InternalInitiated
    metadata_keys: [settlement_cycle_id]

  - name: SettlementStandard
    leg_role: ClearingSuspense
    leg_direction: Variable
    origin: InternalInitiated
    metadata_keys: [settlement_cycle_id]

  - name: SettlementSlow
    leg_role: ClearingSuspense
    leg_direction: Variable
    origin: InternalInitiated
    metadata_keys: [settlement_cycle_id]

  # Aggregating two-leg that bundles SubledgerCharge → satisfies R8 + R11.
  - name: PoolBalancing
    source_role: NorthPool
    destination_role: SouthPool
    expected_net: 0
    origin: InternalInitiated
    metadata_keys: [business_day]
    aggregating: true
    cadence: intraday-2h
    bundles_activity:
      - SubledgerCharge

  # Two-leg rail that IS a TransferTemplate leg (S2 — no expected_net;
  # the template owns the bundle's ExpectedNet). First leg of
  # ExternalReconciliationCycle below: each firing posts a balanced
  # debit+credit pair → net = expected_net (0), so the template
  # instances surface as 'Complete'/'Orphaned' (never 'Imbalanced') —
  # the complement of SubledgerCharge-first MerchantSettlementCycle
  # (single-leg → every firing 'Imbalanced'). Together they give the
  # L2FT Transfer Templates "Completion" dropdown all three values.
  - name: ReconciliationLeg
    source_role: ExternalCounterparty
    destination_role: CustomerSubledger
    source_origin: ExternalForcePosted
    destination_origin: InternalInitiated
    metadata_keys: [external_reference]

  # Two-leg standalone (S1 — expected_net set) used as the Required
  # chain child of ExternalReconciliationCycle. Distinct transfer_type
  # so it never overlaps the other rails' Transaction binding (U6).
  - name: ReconciliationClosing
    source_role: CustomerSubledger
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [external_reference]

  # AB.4.5.spec (2026-05-19) — dedicated parent + child rails for the
  # fan-in chain demo. Two-leg parent self-reconciles (expected_net=0);
  # single-leg child is reconciled via BatchedPayoutBatch.leg_rails (S3).
  # N firings of BatchPayoutTrigger fan into one BatchedPayoutBatch
  # Transfer (per chain expected_parent_count=2) — the AB.4.7
  # fan_in_disagreement matview reads parent_count=2 for the healthy
  # case, parent_count<2 for missing, parent_count>2 for extra.
  - name: BatchPayoutTrigger
    source_role: CustomerSubledger
    destination_role: ClearingSuspense
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: InternalInitiated
    metadata_keys: [batch_id]

  - name: BatchPayoutClose
    leg_role: ClearingSuspense
    leg_direction: Credit
    origin: InternalInitiated
    metadata_keys: [batch_id]

  # AB.6.5.spec (2026-05-19) — multi-XOR chain demo. BulkAccrualSettlement
  # is a parent rail that fires the accrual; downstream, exactly ONE of
  # two settlement-vehicle rails (BulkAccrualSettleACH or
  # BulkAccrualSettleWire) should follow. The AB.6.5 multi_xor_violation
  # matview flags violations: 0 children fired (missed) or both fired
  # (overlap).
  - name: BulkAccrualSettlement
    source_role: CustomerSubledger
    destination_role: ClearingSuspense
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: InternalInitiated
    metadata_keys: [accrual_id]

  - name: BulkAccrualSettleACH
    source_role: ClearingSuspense
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [accrual_id]

  - name: BulkAccrualSettleWire
    source_role: ClearingSuspense
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [accrual_id]

  # AJ.1.spec (2026-05-20) — template-heavy regression fixture for SPEC
  # gaps G + H. DisbursementAccrual is the leg_rail of the DisbursementCycle
  # TEMPLATE (below), which is the parent of a multi-XOR chain. Because a
  # leg_rail of a template fires BROADLY in the per-rail loop (M.4.2a stamps
  # template_name on those firings so they surface on the L2FT Transfer
  # Templates sheet), every DisbursementAccrual firing carries
  # template_name=DisbursementCycle but has NO XOR child — the multi_xor_
  # violation matview's parent_firings CTE (template_name IN parent_names)
  # then false-positives them as 'missed' (Gap H). Two-leg balanced (S2 —
  # no expected_net; the template owns it) so the template firing reads
  # 'Complete', not 'Imbalanced'.
  - name: DisbursementAccrual
    source_role: CustomerSubledger
    destination_role: ClearingSuspense
    source_origin: InternalInitiated
    destination_origin: InternalInitiated
    metadata_keys: [disbursement_id]
    # AL.6 (Gap J follow-up, v11.9.3) — high-volume operating-leg band.
    # Paired with the new DisbursementFee [1,1] below: DisbursementCycle's
    # two leg_rails are INDEPENDENT activities at DISTINCT firing volumes
    # that merely settle into the same cycle, NOT one balanced atomic event.
    # The v11.9.2 unit-firing leg-skip (keyed off chain-parenthood) collapsed
    # both legs to one shared per-firing count; they must fire independently.
    firings_typical_per_period: [4, 6]

  # The two XOR settlement vehicles — exactly ONE should follow each
  # DisbursementCycle firing (ACH transfer vs. paper check). Both are
  # children of the DisbursementCycle chain below.
  - name: DisbursementSettleACH
    source_role: ClearingSuspense
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [disbursement_id]

  - name: DisbursementSettleCheck
    source_role: ClearingSuspense
    destination_role: ExternalCounterparty
    expected_net: 0
    source_origin: InternalInitiated
    destination_origin: ExternalForcePosted
    metadata_keys: [disbursement_id]

  # AL.6 (Gap J follow-up, v11.9.3) — a SECOND, low-volume leg_rail on the
  # DisbursementCycle template. With DisbursementAccrual [4,6] it makes
  # DisbursementCycle the regression shape: a chain-parent (multi-XOR),
  # expected_net:0, MULTI-leg template with NO template-level E8 whose legs
  # are INDEPENDENT events at distinct rail-E8 bands. They must keep their
  # own per-rail volumes — the v11.9.2 leg-skip collapsed them to one shared
  # per-firing count. Two-leg balanced (S2, template owns the net) like
  # DisbursementAccrual so the cycle firing still reads 'Complete'.
  - name: DisbursementFee
    source_role: CustomerSubledger
    destination_role: ClearingSuspense
    source_origin: InternalInitiated
    destination_origin: InternalInitiated
    metadata_keys: [disbursement_id]
    firings_typical_per_period: [1, 1]

  # AJ.4b (2026-05-20) — label-only internal balance-maintenance rail.
  # The seed's cascade-credit + opening-balance legs (demo scaffolding
  # that keeps the cumulative-balance walk positive) are tagged with THIS
  # rail instead of a money-movement rail. They must carry a declared
  # rail_name (the unmatched_rail_name L1 invariant rejects any posting
  # matching no rail), but tagging them with a real rail made every
  # firing-count analysis (chain_orphans / multi_xor_violation / the L2FT
  # rails sheet) count the scaffolding as firings of that rail. This rail
  # never fires on its own (the seed skips it in the per-rail loop — an
  # independent firing would pump the clearing GL it credits); its only
  # postings are the cascade + opening legs, which carry source_transfer_id.
  - name: InternalBalanceMaintenance
    source_role: ExternalCounterparty
    destination_role: ClearingSuspense
    expected_net: 0
    source_origin: ExternalForcePosted
    destination_origin: InternalInitiated
    metadata_keys: [source_transfer_id]

  # AL.1 (Gap J) — a balanced 2-SingleLegRail flow joined by CardLoadCycle
  # below (which is NOT a chain parent). The legs net to zero per firing
  # (cardholder credit + clearing-suspense debit). This is the broken case
  # for template-level firings_typical_per_period: pre-AL.3 the legs only
  # fire via the per-rail loop (each its own transfer_id, uncoupled), so
  # CardLoadCycle's E8 band is ignored. AL.3 makes the unit-firing honor it.
  - name: CardLoadCardholderCredit
    leg_role: CustomerSubledger
    leg_direction: Credit
    origin: InternalInitiated
    metadata_keys: [card_load_id]

  - name: CardLoadSweepDebit
    leg_role: ClearingSuspense
    leg_direction: Debit
    origin: InternalInitiated
    metadata_keys: [card_load_id]

transfer_templates:
  - name: MerchantSettlementCycle
    expected_net: 0
    transfer_key: [merchant_id, settlement_period]
    completion: business_day_end+1d
    leg_rails:
      - SubledgerCharge

  # TwoLegRail-first template — firings balance to expected_net=0, so
  # the auto-scenario's three chain-completion variants (all children
  # fire / none / first) surface as Complete / Orphaned / Complete on
  # the template instances. Paired with the chain below (Required child
  # ReconciliationClosing) so firing 2 (no child) is an orphan.
  - name: ExternalReconciliationCycle
    expected_net: 0
    transfer_key: [external_reference]
    completion: business_day_end+1d
    leg_rails:
      - ReconciliationLeg

  # AB.3.5.spec (2026-05-19) — multi-Variable template with one XOR
  # group of 2 members. SettlementAuto and SettlementStandard are
  # mutually exclusive (the merchant's config picks ONE per cycle);
  # SettlementSlow stays as the lone non-grouped Variable for cycles
  # routed through manual review. The auto-scenario's
  # XorVariantMissedFiringPlant fires SettlementSlow as the witness
  # (group has zero firings → matview firing_count=0 violation); the
  # XorVariantOverlapPlant emits both SettlementAuto + SettlementStandard
  # for one Transfer (group has 2 firings → firing_count=2 violation).
  # Together they exercise BOTH branches of the AB.3.3 matview's HAVING
  # firing_count <> 1 filter.
  - name: SettlementTimingCycle
    expected_net: 0
    transfer_key: [settlement_cycle_id]
    completion: business_day_end+1d
    leg_rails:
      - SettlementAuto
      - SettlementStandard
      - SettlementSlow
    leg_rail_xor_groups:
      - - SettlementAuto
        - SettlementStandard
    # AF (E8): template-level firing count. SettlementTimingCycle is the
    # parent of the AG.1 SettlementTimingCycle → BatchedPayoutBatch
    # chain, so it fires as a unit via _emit_baseline_template_firings —
    # this band (~3-8 cycles per week) drives that firing count.
    # Demonstrates the field on a TransferTemplate + the week period.
    firings_typical_per_period:
      period: week
      range: [3, 8]

  # AB.4.5.spec (2026-05-19) — dedicated template for the fan-in chain
  # demo. 2 parent firings of BatchPayoutTrigger share one
  # BatchedPayoutBatch Transfer (per chain expected_parent_count=2);
  # the AB.4.3 _transfer_parents matview reads 2 rows per child.
  # AB.2.3 chain_parent_disagreement matview ignores this template
  # because the fan-in filter excludes fan_in template children
  # (else every fan_in firing would false-positive there).
  - name: BatchedPayoutBatch
    expected_net: 0
    transfer_key: [batch_id]
    completion: business_day_end+1d
    leg_rails:
      - BatchPayoutClose

  # AJ.1.spec (2026-05-20) — the template-heavy regression parent. A
  # TransferTemplate that is BOTH (a) a multi-children XOR chain parent
  # (the DisbursementCycle chain below) AND (b) baseline-fired — its leg
  # DisbursementAccrual fires per-business-day in the rail loop. That
  # composition is what the SPEC Gap G + Gap H families slipped through:
  # the AB.6/AG fixes were validated only against Rail-parent multi-XOR
  # chains, so the Template-parent shape (this one) was never exercised.
  # Sorts AFTER BulkAccrualSettlement, so the AB.6.6 multi-XOR plant
  # picker still selects the Rail-parent chain (preserving that coverage);
  # this chain's Gap H surfaces purely from baseline broad-firing.
  #
  # AL.6 (Gap J follow-up, v11.9.3) — now carries a SECOND leg_rail
  # (DisbursementFee). DisbursementAccrual [4,6] + DisbursementFee [1,1] are
  # INDEPENDENT activities at distinct rail-E8 bands; because this template
  # is a chain parent WITHOUT a template-level E8 opt-in, its legs must fire
  # independently in the per-rail loop (keeping their distinct volumes), NOT
  # collapse into one shared per-firing unit. The
  # test_chain_parent_template_legs_keep_independent_counts guard pins it.
  - name: DisbursementCycle
    expected_net: 0
    transfer_key: [disbursement_id]
    completion: business_day_end+1d
    leg_rails:
      - DisbursementAccrual
      - DisbursementFee

  # AL.1 (Gap J) — multi-1-leg-rail balanced template, NOT a chain parent.
  # firings_typical_per_period must drive a coupled unit-firing (AL.3);
  # pre-fix the legs fire independently via the per-rail loop and the band
  # is ignored. The week band is far from the per-business-day per-rail
  # heuristic, so the coupling RED→GREEN (legs sharing one Transfer) is
  # unambiguous.
  - name: CardLoadCycle
    expected_net: 0
    transfer_key: [card_load_id]
    completion: business_day_end+1d
    leg_rails:
      - CardLoadCardholderCredit
      - CardLoadSweepDebit
    firings_typical_per_period:
      period: week
      range: [1, 2]

chains:
  # First chain on spec_example — makes the L2FT Chains sheet
  # non-empty and exercises both completion_status values
  # ('Completed' when the closing leg fires, 'Incomplete' when it
  # doesn't). Z.A: a single-element ``children`` list encodes
  # "required" semantics — every parent firing must invoke this
  # child. Template-as-parent shape.
  - parent: ExternalReconciliationCycle
    children:
      - ReconciliationClosing
    description: |
      Every reconciliation cycle SHOULD be closed by a matching
      ReconciliationClosing leg; an unmatched cycle surfaces as an
      orphan in the L2FT Chains explorer.

  # AB.2 (2026-05-19) — template-as-chain-child shape: every
  # ReconciliationLeg firing should kick off a downstream
  # MerchantSettlementCycle as its chain child. All leg_rails of
  # the child template share one Transfer with a shared
  # ``parent_transfer_id`` (first-firing-wins per gap doc §3);
  # AB.2.3's ``<prefix>_chain_parent_disagreement`` matview catches
  # any leg that claims a different parent (the ETL-bug case).
  # Activates the auto-scenario's TwoTemplateChainPlant +
  # ChainParentDisagreementPlant.
  - parent: ReconciliationLeg
    children:
      - MerchantSettlementCycle
    description: |
      Demonstrates template-as-chain-child semantics. Every
      ReconciliationLeg firing initiates a MerchantSettlementCycle
      reconciliation downstream; all leg_rails of that template
      share one Transfer and one parent_transfer_id. The L1
      chain_parent_disagreement invariant flags any leg that
      claims a different parent.

  # AB.4.5.spec (2026-05-19) — fan-in chain demo. 2 BatchPayoutTrigger
  # firings batch into one BatchedPayoutBatch Transfer (the merchant-
  # payout-batch pattern: N daily settlements aggregate into one
  # weekly/monthly payout). The auto-scenario plants 3 violations:
  # healthy (2 parents), missing (1 parent ⇒ orphan), extra (3
  # parents ⇒ excess contributor). AB.4.7's fan_in_disagreement
  # matview reads the cardinality per child Transfer.
  - parent: BatchPayoutTrigger
    children:
      - name: BatchedPayoutBatch
        fan_in: true
        expected_parent_count: 2
    description: |
      Demonstrates N:1 fan-in chain semantics (AB.6 per-child shape).
      Multiple BatchPayoutTrigger firings (the upstream settlements)
      share one BatchedPayoutBatch Transfer (the downstream batch
      payout). The L1 fan_in_disagreement invariant flags batches
      whose parent_count != expected_parent_count (missing contributor
      or extra contributor — both ETL bugs).

  # AG.1.spec (2026-05-19) — Template-parent + Template-child shape so
  # spec_example exercises the 4th cell of the {Rail/Template} ×
  # {parent/child} matrix as integration coverage (the AG.1 unit test
  # also covers this via a synthetic L2, but integration coverage in
  # the locked seed surfaces Gap B regressions in plant + matview
  # behavior, not just the bare row emission). Both endpoints are
  # already-declared TransferTemplates: SettlementTimingCycle as parent
  # (3 leg_rails with an XOR group on Auto/Standard) → BatchedPayoutBatch
  # as child (1 leg_rail). BatchedPayoutBatch is also a chain child of
  # the AB.4.5.spec fan-in chain; that's fine — same child shape from
  # two distinct parent chains is a valid L2 declaration.
  - parent: SettlementTimingCycle
    children:
      - BatchedPayoutBatch
    description: |
      Demonstrates Template-parent + Template-child chain semantics
      (AG.1 Gap B regression coverage). Without the AG.1 fix,
      ``state.firings[SettlementTimingCycle]`` is empty at chain-emit
      time and this chain silently emits zero rows — the locked seed
      surfaces the bug on regression. With the fix, the parent
      template fires once per business day and the chain threads
      ``transfer_parent_id`` through BatchedPayoutBatch's leg_rail.

  # AB.6.5.spec (2026-05-19) — dedicated multi-XOR chain so the
  # AB.6.5 _multi_xor_violation matview has rows to surface. Per
  # AB.6.0 Lock 5: rather than mutate an existing chain (which would
  # entangle other invariant tests), this chain stands alone with
  # the BulkAccrualSettlement parent + two settlement-vehicle child
  # rails. The auto-scenario plants MultiXorMissedPlant (parent fires,
  # no child) + MultiXorOverlapPlant (parent fires, BOTH children
  # fire) so both branches of the matview's HAVING <> 1 surface.
  - parent: BulkAccrualSettlement
    children:
      - BulkAccrualSettleACH
      - BulkAccrualSettleWire
    description: |
      Demonstrates the multi-XOR chain contract: every
      BulkAccrualSettlement firing SHOULD be followed by exactly
      ONE of the two settlement-vehicle rails. The L1
      multi_xor_violation invariant flags violations: 'missed'
      when no child fires (the chain.md contract was dropped on
      the floor) or 'overlap' when both fire (XOR alternation
      collapsed into a duplicate).

  # AJ.1.spec (2026-05-20) — TEMPLATE-parent multi-XOR chain (the
  # complement of the Rail-parent BulkAccrualSettlement chain above).
  # DisbursementCycle (a TransferTemplate) is the parent; the two
  # settlement-vehicle rails are XOR children. This is the structural
  # guard the SPEC gap doc calls for: a single template that is a
  # multi-XOR chain parent AND independently baseline-fired catches both
  # Gap G (multi-XOR plant emitter leaking the parent name into rail_name
  # for template children) and Gap H (broad leg firings false-positiving
  # as 'missed' on a healthy baseline). Before the AJ.2/AJ.3 fixes, a
  # fresh seed shows DisbursementCycle 'missed' rows in
  # <prefix>_multi_xor_violation despite every cycle being correctly
  # settled.
  - parent: DisbursementCycle
    children:
      - DisbursementSettleACH
      - DisbursementSettleCheck
    description: |
      Demonstrates a TEMPLATE-parent multi-XOR chain: every
      DisbursementCycle firing SHOULD be settled by exactly ONE of
      the two vehicle rails (ACH transfer or paper check). The L1
      multi_xor_violation invariant flags 'missed' (neither vehicle
      fired) and 'overlap' (both fired). Unlike the Rail-parent
      BulkAccrualSettlement chain, the parent here is a TransferTemplate
      whose leg also fires standalone — the composition that the
      Template-parent regression coverage exists to protect.

limit_schedules:
  # Cap matches ExternalRailOutbound (Z.B 2026-05-15: rail name IS the type).
  - parent_role: CustomerLedger
    rail: ExternalRailOutbound
    cap: 5000.00
  # AB.1 (2026-05-19): per-direction cap on the inbound mirror rail.
  # ExternalRailInbound delivers money TO CustomerSubledger children
  # whose parent_role is CustomerLedger — the L1 limit_breach matview's
  # Inbound branch sums Credit legs on that rail per (account, day)
  # and surfaces the row when InboundFlow > 3000.
  - parent_role: CustomerLedger
    rail: ExternalRailInbound
    cap: 3000.00
    direction: Inbound
theme:
  theme_name: "Recon Gen Theme"
  version_description: "Auto-generated dashboard theme"
  analysis_name_prefix: null
  data_colors:
    - "#2E5090"
    - "#E07B39"
    - "#3A9E6F"
    - "#4A7DC7"
    - "#8E5EA2"
    - "#E6B422"
    - "#4BC0C0"
    - "#8C8C8C"
  empty_fill_color: "#D9D9D9"
  gradient: ["#C5DAF7", "#2E5090"]
  primary_bg: "#FFFFFF"
  secondary_bg: "#F5F6FA"
  primary_fg: "#2D2D2D"
  secondary_fg: "#4A4A4A"
  accent: "#2E5090"
  accent_fg: "#FFFFFF"
  link_tint: "#E8EFF9"
  danger: "#C62828"
  danger_fg: "#FFFFFF"
  warning: "#E65100"
  warning_fg: "#FFFFFF"
  success: "#2E7D32"
  success_fg: "#FFFFFF"
  dimension: "#4A7DC7"
  dimension_fg: "#FFFFFF"
  measure: "#1B2A4A"
  measure_fg: "#FFFFFF"
