recon_gen.common.spine.registry

Invariant ⋈ generator registry — many-to-many edge bookkeeping.

The audit’s AP.3 finding (docs/audits/date_range_model_audit.md §5): a drift ViolationGenerator trips BOTH DriftInvariant AND LedgerDriftInvariant; the xor_variant_* generators both trip the single xor_group_violation detector. The mapping is many-to-many, not 1:1 — so a direct rename of PlantKindcheck_type would lose information. This module is the explicit edge table.

Shape: INVARIANT_GENERATOR_EDGES is dict[type[ViolationGenerator], tuple[type[Invariant], …]]. Reading: “instances of this generator class, when emitted, trip detect() on at least these invariant classes.”

What this enforces (via tests/unit/test_spine_drift.py): - For every (generator_class, invariant_class) edge listed here, an

emitted generator instance actually causes invariant.detect() to include the appropriate Violation. The empirical contract that “many-to-many” claims a generator’s emission ACTUALLY trips that detector — not just that someone wrote it down.

What this enforces additionally (via tests/unit/test_spine_au5_exhaustiveness.py):

  • Per-generator-class exhaustiveness — every class in ALL_GENERATORS has an entry in INVARIANT_GENERATOR_EDGES. Catches “I promoted a new generator but forgot to register it.”

  • Per-invariant-class coverage — every class in ALL_INVARIANTS is reached by at least one generator’s edge set. Catches “I promoted a new invariant but forgot to wire any generator to it.”

  • Empirical-edge contract — every registered edge actually fires on emission (existing per-invariant tests cover this; AU.5’s parametrized test consolidates).

AX.5 registry split (2026-05-23): the spine’s coverage now spans three invariant categories — L1 accounting (drift / overdraft / expected_eod / stuck_* / limit_breach), L2-shape integrity (chain_parent_disagreement / xor_group_violation / fan_in_disagreement / multi_xor_violation — the ETL-side contracts that the L2 yaml’s declared chain/XOR/fan-in structure is honored), and L2-investigation fraud/AML pattern (anomaly / money_trail). Each category gets its own ALL_<CATEGORY>_INVARIANTS + ALL_<CATEGORY> _GENERATORS tuple; ALL_INVARIANTS + ALL_GENERATORS are the unified flat sequences AU.5’s exhaustiveness gate walks.

What this does NOT enforce yet: - View edges (invariant → {Views}); AT lands those when L2’s

Investigation surface comes online.

  • Composition-induced edges (AU.2 finding: drift+parent-overdraft → ledger_drift on overdraft’s parent). The registry stays per-generator-class; composition-induced behavior is documented + tested at the scenario level in tests/unit/test_spine_au2_composition.py.

Module Attributes

INVARIANT_GENERATOR_EDGES

For every generator class, the invariant classes its emission trips.

ALL_L1_INVARIANTS

L1 accounting / audit-trail invariants — the regulator-facing matview surface that the audit PDF covers.

ALL_L2_SHAPE_INVARIANTS

L2-shape integrity invariants (AX) — the ETL-side contracts that the L2 yaml's declared chain/XOR/fan-in structure is honored at data-emit time.

ALL_L2_INVESTIGATION_INVARIANTS

L2 investigation invariants (AT) — fraud / AML pattern detection.

ALL_INVARIANTS

Every Invariant class promoted to the spine, across all three categories.

ALL_L1_GENERATORS

L1 accounting generators — match ALL_L1_INVARIANTS.

ALL_L2_SHAPE_GENERATORS

L2-shape integrity generators (AX) — 1 per single-variant invariant + 2 each for the two-variant ones (XOR missed/overlap, multi-XOR missed/overlap).

ALL_L2_INVESTIGATION_GENERATORS

L2 investigation generators (AT) — match ALL_L2_INVESTIGATION_INVARIANTS.

ALL_AUDIT_FIXTURE_GENERATORS

Audit-fixture generators (AY.2.b) — emit AuditFixture evidence the audit PDF reads directly; no Invariant counterpart on the spine.

ALL_COVERAGE_GENERATORS

Seed-color coverage generators (AY.2.b) — emit CoverageObservation evidence the L1 dashboard's PostedRequirements panel + audit-PDF coverage sections read directly.

ALL_GENERATORS

Every ViolationGenerator class promoted to the spine, across all three categories.

Functions

generators_for(invariant_class)

The generator classes whose emission trips this invariant.

invariants_for(generator_class)

The invariants a given generator's emission trips.

iter_edges()

Every (generator_class, invariant_class) edge as a flat sequence.

recon_gen.common.spine.registry.ALL_AUDIT_FIXTURE_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.failed_transaction.FailedTransactionGenerator'>, <class 'recon_gen.common.spine.supersession.SupersessionGenerator'>)

Audit-fixture generators (AY.2.b) — emit AuditFixture evidence the audit PDF reads directly; no Invariant counterpart on the spine. AU.5’s per-generator gate widens to allow empty edge tuples for these (intended.severity == ‘audit_fixture’ or, post-AY.2.a, isinstance(intended, AuditFixture) is the discriminator).

recon_gen.common.spine.registry.ALL_COVERAGE_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.two_template_chain.TwoTemplateChainGenerator'>, <class 'recon_gen.common.spine.rail_firing.RailFiringGenerator'>, <class 'recon_gen.common.spine.transfer_template.TransferTemplateGenerator'>, <class 'recon_gen.common.spine.inv_fanout.InvFanoutGenerator'>, <class 'recon_gen.common.spine.chain_completion.ChainCompletionGenerator'>)

Seed-color coverage generators (AY.2.b) — emit CoverageObservation evidence the L1 dashboard’s PostedRequirements panel + audit-PDF coverage sections read directly. Non-violating by construction (no matview surfaces them as RuleViolations); the empty edge tuple in INVARIANT_GENERATOR_EDGES is intentional. AU.5’s per-generator gate widens to allow empty edges for any generator in this tuple, paralleling the audit-fixture treatment.

recon_gen.common.spine.registry.ALL_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.drift.DriftGenerator'>, <class 'recon_gen.common.spine.drift.LedgerDriftGenerator'>, <class 'recon_gen.common.spine.overdraft.OverdraftGenerator'>, <class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceGenerator'>, <class 'recon_gen.common.spine.stuck_pending.StuckPendingGenerator'>, <class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledGenerator'>, <class 'recon_gen.common.spine.limit_breach.LimitBreachGenerator'>, <class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementGenerator'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupMissedFiringGenerator'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupOverlapGenerator'>, <class 'recon_gen.common.spine.fan_in_disagreement.FanInChainGenerator'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorMissedGenerator'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorOverlapGenerator'>, <class 'recon_gen.common.spine.anomaly.AnomalyGenerator'>, <class 'recon_gen.common.spine.money_trail.MoneyTrailGenerator'>, <class 'recon_gen.common.spine.failed_transaction.FailedTransactionGenerator'>, <class 'recon_gen.common.spine.supersession.SupersessionGenerator'>, <class 'recon_gen.common.spine.two_template_chain.TwoTemplateChainGenerator'>, <class 'recon_gen.common.spine.rail_firing.RailFiringGenerator'>, <class 'recon_gen.common.spine.transfer_template.TransferTemplateGenerator'>, <class 'recon_gen.common.spine.inv_fanout.InvFanoutGenerator'>, <class 'recon_gen.common.spine.chain_completion.ChainCompletionGenerator'>)

Every ViolationGenerator class promoted to the spine, across all three categories. AU.5’s registration gate asserts each is keyed in INVARIANT_GENERATOR_EDGES.

recon_gen.common.spine.registry.ALL_INVARIANTS: Final[tuple[type[Invariant], ...]] = (<class 'recon_gen.common.spine.drift.DriftInvariant'>, <class 'recon_gen.common.spine.drift.LedgerDriftInvariant'>, <class 'recon_gen.common.spine.overdraft.OverdraftInvariant'>, <class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceInvariant'>, <class 'recon_gen.common.spine.stuck_pending.StuckPendingInvariant'>, <class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledInvariant'>, <class 'recon_gen.common.spine.limit_breach.LimitBreachInvariant'>, <class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementInvariant'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupViolationInvariant'>, <class 'recon_gen.common.spine.fan_in_disagreement.FanInDisagreementInvariant'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorViolationInvariant'>, <class 'recon_gen.common.spine.anomaly.AnomalyInvariant'>, <class 'recon_gen.common.spine.money_trail.MoneyTrailInvariant'>)

Every Invariant class promoted to the spine, across all three categories. AU.5’s coverage gate asserts each is reached by ≥1 generator in INVARIANT_GENERATOR_EDGES.

recon_gen.common.spine.registry.ALL_L1_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.drift.DriftGenerator'>, <class 'recon_gen.common.spine.drift.LedgerDriftGenerator'>, <class 'recon_gen.common.spine.overdraft.OverdraftGenerator'>, <class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceGenerator'>, <class 'recon_gen.common.spine.stuck_pending.StuckPendingGenerator'>, <class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledGenerator'>, <class 'recon_gen.common.spine.limit_breach.LimitBreachGenerator'>)

L1 accounting generators — match ALL_L1_INVARIANTS.

recon_gen.common.spine.registry.ALL_L1_INVARIANTS: Final[tuple[type[Invariant], ...]] = (<class 'recon_gen.common.spine.drift.DriftInvariant'>, <class 'recon_gen.common.spine.drift.LedgerDriftInvariant'>, <class 'recon_gen.common.spine.overdraft.OverdraftInvariant'>, <class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceInvariant'>, <class 'recon_gen.common.spine.stuck_pending.StuckPendingInvariant'>, <class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledInvariant'>, <class 'recon_gen.common.spine.limit_breach.LimitBreachInvariant'>)

L1 accounting / audit-trail invariants — the regulator-facing matview surface that the audit PDF covers.

recon_gen.common.spine.registry.ALL_L2_INVESTIGATION_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.anomaly.AnomalyGenerator'>, <class 'recon_gen.common.spine.money_trail.MoneyTrailGenerator'>)

L2 investigation generators (AT) — match ALL_L2_INVESTIGATION_INVARIANTS.

recon_gen.common.spine.registry.ALL_L2_INVESTIGATION_INVARIANTS: Final[tuple[type[Invariant], ...]] = (<class 'recon_gen.common.spine.anomaly.AnomalyInvariant'>, <class 'recon_gen.common.spine.money_trail.MoneyTrailInvariant'>)

L2 investigation invariants (AT) — fraud / AML pattern detection. Surface on the Investigation dashboard (anomaly + money_trail sheets).

recon_gen.common.spine.registry.ALL_L2_SHAPE_GENERATORS: Final[tuple[type[ViolationGenerator], ...]] = (<class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementGenerator'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupMissedFiringGenerator'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupOverlapGenerator'>, <class 'recon_gen.common.spine.fan_in_disagreement.FanInChainGenerator'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorMissedGenerator'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorOverlapGenerator'>)

L2-shape integrity generators (AX) — 1 per single-variant invariant + 2 each for the two-variant ones (XOR missed/overlap, multi-XOR missed/overlap). 7 generators across 4 invariants.

recon_gen.common.spine.registry.ALL_L2_SHAPE_INVARIANTS: Final[tuple[type[Invariant], ...]] = (<class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementInvariant'>, <class 'recon_gen.common.spine.xor_group_violation.XorGroupViolationInvariant'>, <class 'recon_gen.common.spine.fan_in_disagreement.FanInDisagreementInvariant'>, <class 'recon_gen.common.spine.multi_xor_violation.MultiXorViolationInvariant'>)

L2-shape integrity invariants (AX) — the ETL-side contracts that the L2 yaml’s declared chain/XOR/fan-in structure is honored at data-emit time. Surface on the L2 Flow Tracing dashboard’s Unified L2 Exceptions matview.

recon_gen.common.spine.registry.INVARIANT_GENERATOR_EDGES: Final[dict[type[ViolationGenerator], tuple[type[Invariant], ...]]] = {<class 'recon_gen.common.spine.anomaly.AnomalyGenerator'>: (<class 'recon_gen.common.spine.anomaly.AnomalyInvariant'>,), <class 'recon_gen.common.spine.chain_completion.ChainCompletionGenerator'>: (), <class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementGenerator'>: (<class 'recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementInvariant'>,), <class 'recon_gen.common.spine.drift.DriftGenerator'>: (<class 'recon_gen.common.spine.drift.DriftInvariant'>, <class 'recon_gen.common.spine.drift.LedgerDriftInvariant'>), <class 'recon_gen.common.spine.drift.LedgerDriftGenerator'>: (<class 'recon_gen.common.spine.drift.LedgerDriftInvariant'>,), <class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceGenerator'>: (<class 'recon_gen.common.spine.expected_eod.ExpectedEodBalanceInvariant'>, <class 'recon_gen.common.spine.drift.DriftInvariant'>), <class 'recon_gen.common.spine.failed_transaction.FailedTransactionGenerator'>: (), <class 'recon_gen.common.spine.fan_in_disagreement.FanInChainGenerator'>: (<class 'recon_gen.common.spine.fan_in_disagreement.FanInDisagreementInvariant'>,), <class 'recon_gen.common.spine.inv_fanout.InvFanoutGenerator'>: (<class 'recon_gen.common.spine.money_trail.MoneyTrailInvariant'>,), <class 'recon_gen.common.spine.limit_breach.LimitBreachGenerator'>: (<class 'recon_gen.common.spine.limit_breach.LimitBreachInvariant'>,), <class 'recon_gen.common.spine.money_trail.MoneyTrailGenerator'>: (<class 'recon_gen.common.spine.money_trail.MoneyTrailInvariant'>,), <class 'recon_gen.common.spine.multi_xor_violation.MultiXorMissedGenerator'>: (<class 'recon_gen.common.spine.multi_xor_violation.MultiXorViolationInvariant'>,), <class 'recon_gen.common.spine.multi_xor_violation.MultiXorOverlapGenerator'>: (<class 'recon_gen.common.spine.multi_xor_violation.MultiXorViolationInvariant'>,), <class 'recon_gen.common.spine.overdraft.OverdraftGenerator'>: (<class 'recon_gen.common.spine.overdraft.OverdraftInvariant'>, <class 'recon_gen.common.spine.drift.DriftInvariant'>), <class 'recon_gen.common.spine.rail_firing.RailFiringGenerator'>: (), <class 'recon_gen.common.spine.stuck_pending.StuckPendingGenerator'>: (<class 'recon_gen.common.spine.stuck_pending.StuckPendingInvariant'>,), <class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledGenerator'>: (<class 'recon_gen.common.spine.stuck_unbundled.StuckUnbundledInvariant'>,), <class 'recon_gen.common.spine.supersession.SupersessionGenerator'>: (), <class 'recon_gen.common.spine.transfer_template.TransferTemplateGenerator'>: (), <class 'recon_gen.common.spine.two_template_chain.TwoTemplateChainGenerator'>: (), <class 'recon_gen.common.spine.xor_group_violation.XorGroupMissedFiringGenerator'>: (<class 'recon_gen.common.spine.xor_group_violation.XorGroupViolationInvariant'>,), <class 'recon_gen.common.spine.xor_group_violation.XorGroupOverlapGenerator'>: (<class 'recon_gen.common.spine.xor_group_violation.XorGroupViolationInvariant'>,)}

For every generator class, the invariant classes its emission trips. Read as: “a single emit() from this generator + a refresh of the matviews causes detect() on each listed invariant to include the expected Violation.”

AU.0 finding (audit §5 “AU.0 result”): the OverdraftGenerator edge to DriftInvariant is the empirical consequence of overlapping base-table predicates between two independent matview SELECTs — not a structural claim. An overdraft planted on a LEAF internal account satisfies drift’s matview filter (parent_role IS NOT NULL AND stored ≠ Σ legs), so drift fires too. The registry records this; AU.2’s composition test holds it under multi-generator pressure.

AX.5: the L2-shape generators (chain_parent_disagreement / xor_group_violation / fan_in_disagreement / multi_xor_violation) all register single-edge by default. Cross-class noise (e.g., an XOR plant on a template that also parents a chain may also trip multi_xor_violation) is tolerated per AS.5’s “intended ⊆ detected” contract — multi-edge entries land here only after per-invariant empirical verification, mirroring the OverdraftGenerator pattern.

recon_gen.common.spine.registry.generators_for(invariant_class)[source]

The generator classes whose emission trips this invariant. Reverse-lookup over the edge table.

Return type:

set[type[ViolationGenerator]]

Parameters:

invariant_class (type[Invariant])

recon_gen.common.spine.registry.invariants_for(generator_class)[source]

The invariants a given generator’s emission trips. Empty tuple when the generator class isn’t registered (yet).

Return type:

tuple[type[Invariant], ...]

Parameters:

generator_class (type[ViolationGenerator])

recon_gen.common.spine.registry.iter_edges()[source]

Every (generator_class, invariant_class) edge as a flat sequence. Convenient for parametrized property tests.

Return type:

Iterator[tuple[type[ViolationGenerator], type[Invariant]]]