recon_gen.common.spine.stuck_unbundled
Stuck-Unbundled family — twin of stuck_pending with disjoint conditions.
Same shape as stuck_pending:
Transaction-based plant (single tx, no balance row)
L2-coupled via rail.max_unbundled_age (vs max_pending_age)
Wall-clock matview using CURRENT_TIMESTAMP - posting
Single-edge registry entry (no overlap with drift / ledger_drift / overdraft / expected_eod — Posted but bundle_id IS NULL is exactly the matview’s filter, and Posted legs don’t trip stuck_pending)
The disjoint conditions vs stuck_pending (per schema.py:2091-2094):
status=’Posted’ (vs ‘Pending’) — AggregatingRails only bundle Posted legs; a Pending leg isn’t “stuck unbundled,” it’s just “stuck pending”
bundle_id IS NULL — the row has been Posted but no AggregatingRail has picked it up
Per validator R8, max_unbundled_age is only meaningful on rails whose rail_name appears in some AggregatingRail’s bundles_activity
posting + max_unbundled_age overshot
Same TZ convention as stuck_pending: datetime.now() LOCAL per [[project-local-tz-convention]]; SQLite test absorbs UTC skew via ±12h overshoot windows.
Classes
|
Emit a single Posted transaction with bundle_id IS NULL whose posting is in the past of as_of by max_unbundled_age_seconds + overshoot_seconds. |
|
Stuck-Unbundled detector. |
- class recon_gen.common.spine.stuck_unbundled.StuckUnbundledGenerator(transaction_id, transfer_id, rail_name, account_id, account_role, account_parent_role, max_unbundled_age_seconds, overshoot_seconds, as_of, prefix='spec_example')[source]
Bases:
objectEmit a single Posted transaction with bundle_id IS NULL whose posting is in the past of as_of by max_unbundled_age_seconds + overshoot_seconds. NO balance row, NO related rows.
Post-AW.5: as_of is the owned temporal frame; matview reads the same value from <prefix>_config.as_of → tests deterministic, no TZ skew.
- Parameters:
transaction_id (str)
transfer_id (str)
rail_name (str)
account_id (str)
account_role (str)
account_parent_role (str | None)
max_unbundled_age_seconds (int)
overshoot_seconds (int)
as_of (datetime)
prefix (str)
- account_id: str
- account_parent_role: str | None
- account_role: str
- as_of: datetime
- property claimed_accounts: frozenset[str]
The single account_id this plant strands as Posted-unbundled. AV.5.
- emit(conn, *, scenario_id=None)[source]
- Return type:
None- Parameters:
conn (SyncConnection)
scenario_id (str | None)
- property intended: RuleViolation
- max_unbundled_age_seconds: int
- overshoot_seconds: int
- prefix: str = 'spec_example'
- rail_name: str
- transaction_id: str
- transfer_id: str
- class recon_gen.common.spine.stuck_unbundled.StuckUnbundledInvariant(prefix='spec_example')[source]
Bases:
objectStuck-Unbundled detector. The matview gates on
status = 'Posted'ANDbundle_id IS NULLANDage_seconds > max_unbundled_age_seconds(per-rail cap from L2). Identity is (transaction_id, rail_name).- Parameters:
prefix (str)
- name: ClassVar[str] = 'stuck_unbundled'
- prefix: str = 'spec_example'
- scenario_for(rail_name, *, as_of, overshoot_seconds=60, account_role='CustomerSubledger', instance=None, account_id=None)[source]
Resolve rail_name against the shape; plant a Posted-but- unbundled transaction with posting = as_of − (rail. max_unbundled_age + overshoot).
as_of is the owned temporal frame the matview reads from <prefix>_config.as_of (per AW.2). See StuckPendingInvariant .scenario_for for the full contract — same shape applies here.
Raises ValueError if rail doesn’t exist OR doesn’t have a max_unbundled_age (matview excludes those — uncovered scenario would silently inert).
AY.4.c — account_id overrides the default synthetic ID. The plant adapter (AY.4.c.3) threads OLD StuckUnbundledPlant.account_id through this kwarg so N plants on the same rail produce N distinct generators (the default f”acct-stuck-unbundled-{rail_name}” derivation would collide). Existing test callers can pass nothing → preserves the synthetic default byte-stable.
- Return type:
- Parameters:
rail_name (str)
as_of (datetime)
overshoot_seconds (int)
account_role (str)
instance (L2Instance | None)
account_id (str | None)