recon_gen.common.spine.limit_breach

Limit-Breach family — deepest L2 coupling of the L1 spine.

LimitBreachInvariant fires when per-(account, business_day, rail, direction) Σ ABS(amount_money) exceeds the L2’s LimitSchedule.cap for that (parent_role, rail, direction) triple. The first spine invariant whose smart constructor reads BOTH:

  1. An L2 entity (the cap from LimitSchedule)

  2. The plant’s amount AS a function of the L2 value (cap + overshoot)

This is AP.3 finding #4’s from_instance smart constructor — the disproof of the “blind generator” hypothesis. The cap value itself is a load-bearing input to the emission, not just a discovery target.

Per AU.3.b’s TZ note: limit_breach’s matview is wall-clock- agnostic (groups by DATE(posting), not CURRENT_TIMESTAMP - posting). So the plant uses a static anchor day (2030-01-01 like drift/overdraft/expected_eod) — no TZ-skew concerns.

Sign convention from the CHECK constraint on <prefix>_transactions: - amount_direction='Debit' requires amount_money <= 0 - amount_direction='Credit' requires amount_money >= 0

The matview’s SUM(ABS(amount_money)) makes both contribute positively to the per-direction total. So: - Outbound limit (Debit) plant: amount_money = -(cap + overshoot) - Inbound limit (Credit) plant: amount_money = (cap + overshoot)

Empirical-edge prediction (same as stuck_unbundled): Posted leg with NO matching balance row doesn’t trip drift (no JOIN match in _computed_subledger_balance). Single-edge registry entry expected. Test verifies.

Classes

LimitBreachGenerator(account_id, ...[, prefix])

Emit a single Posted transaction whose ABS(amount_money) = cap + overshoot for the given (account, day, rail, direction).

LimitBreachInvariant([prefix])

Per-rail per-direction flow-cap detector.

class recon_gen.common.spine.limit_breach.LimitBreachGenerator(account_id, account_role, account_parent_role, rail_name, direction, cap, overshoot, anchor_day, prefix='spec_example')[source]

Bases: object

Emit a single Posted transaction whose ABS(amount_money) = cap + overshoot for the given (account, day, rail, direction). The matview’s GROUP BY collapses to this single row → SUM = cap + overshoot > cap → limit_breach fires.

Sign convention is locked by the transactions CHECK constraint — Debit ⇒ money ≤ 0; Credit ⇒ money ≥ 0. The matview’s SUM(ABS) makes both contribute positively.

Parameters:
  • account_id (str)

  • account_role (str)

  • account_parent_role (str)

  • rail_name (str)

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

  • cap (float)

  • overshoot (float)

  • anchor_day (date)

  • prefix (str)

account_id: str
account_parent_role: str
account_role: str
anchor_day: date
cap: float
property claimed_accounts: frozenset[str]

The single account_id this plant breaches a cap on. AV.5.

direction: Literal['Outbound', 'Inbound']
emit(conn, *, scenario_id=None)[source]
Return type:

None

Parameters:
  • conn (SyncConnection)

  • scenario_id (str | None)

property intended: RuleViolation
overshoot: float
prefix: str = 'spec_example'
rail_name: str
class recon_gen.common.spine.limit_breach.LimitBreachInvariant(prefix='spec_example')[source]

Bases: object

Per-rail per-direction flow-cap detector. The matview gates on cap IS NOT NULL (rail+parent_role+direction has a LimitSchedule) AND SUM(ABS(amount_money)) > cap. Identity is (account_id, business_day, rail_name, direction) — analyst-facing diff readability.

Parameters:

prefix (str)

detect(conn)[source]
Return type:

set[Violation]

Parameters:

conn (SyncConnection)

name: ClassVar[str] = 'limit_breach'
prefix: str = 'spec_example'
scenario_for(parent_role, rail_name, *, direction='Outbound', overshoot=100.0, instance=None, account_id=None)[source]

Resolve (parent_role, rail_name, direction) against the L2’s LimitSchedule; return a generator that plants ONE Posted transaction on a child account whose account_parent_role = parent_role, with amount_money sized to overshoot the cap.

overshoot=0.0 ⇒ amount == cap ⇒ matview’s strict > filter excludes ⇒ no fire (AP.2 non-violating convention adapted to Money-unit knob). Positive fires.

Return type:

LimitBreachGenerator

Parameters:
  • parent_role (str)

  • rail_name (str)

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

  • overshoot (float)

  • instance (L2Instance | None)

  • account_id (str | None)

Raises ValueError if: - The L2 has no LimitSchedule matching `(parent_role, rail_name,

direction)`

  • The L2 has no child account with account_parent_role = parent_role (the matview filters account_parent_role IS NOT NULL; without a matching child the plant is inert)

AY.4.c — account_id overrides the default synthetic ID. The plant adapter (AY.4.c.3) threads OLD LimitBreachPlant.account_id through this kwarg so N plants on the same (parent_role, rail, direction) triple produce N distinct generators (the default f”acct-limit-breach-{rail_name}-{direction}” derivation would collide). Existing test callers can pass nothing → preserves the synthetic default byte-stable.

Note: LimitBreachGenerator carries only one account_id field (the breaching account); there is no counter_account_id — the matview groups solely on (account_id, business_day, rail_name, direction).