recon_gen.common.spine.drift

Drift family — concrete Invariant + ViolationGenerator impls.

Two L1 invariants share one generator (the many-to-many edge that motivates the registry):

  • DriftInvariant — sub-ledger drift. Fires when a LEAF internal account’s stored balance ≠ Σ posted legs at business_day_end. Reads from <prefix>_drift.

  • LedgerDriftInvariant — parent (ledger) drift. Fires when a PARENT internal account’s stored balance ≠ Σ(child stored balances) + Σ(direct postings). Reads from <prefix>_ledger_drift.

DriftGenerator emits a child account with stored money OFF by magnitude from its leg-total, AND a parent account with stored money equal to the clean leg-total. So the child drifts (stored−computed = magnitude) AND the parent drifts (parent.stored − Σ child.money is also off, by ‑magnitude). One emission, two detectors fire — that’s the spine’s many-to-many edge.

Per the AS.1 RNG convention: scenario_for accepts seed; the generator carries rng: random.Random. Drift’s emission itself is deterministic by construction (one account, one day, one leg) — the RNG hook is there for the convention’s structural uniformity, not because drift needs choice. Anomaly (AT.2) WILL use it.

Classes

DriftGenerator(child_account_id, child_role, ...)

Emit a child account whose stored money drifts from its leg-total by magnitude, plus a parent account whose stored money equals the CLEAN leg-total.

DriftInvariant([prefix])

Sub-ledger drift detector.

LedgerDriftGenerator(parent_account_id, ...)

Plant a parent+child pair where the parent's stored money diverges from Σ child stored by delta.

LedgerDriftInvariant([prefix])

Parent-ledger drift detector.

class recon_gen.common.spine.drift.DriftGenerator(child_account_id, child_role, parent_role, parent_account_id, parent_account_role, anchor_day, magnitude, rng=<factory>, leg_amount=100.0, prefix='spec_example')[source]

Bases: object

Emit a child account whose stored money drifts from its leg-total by magnitude, plus a parent account whose stored money equals the CLEAN leg-total. Result: child drifts (stored−computed = magnitude) AND parent drifts (parent.stored − Σ child.money = −magnitude).

The pre-AS.3 simple shape: one day, one account pair, one leg. AS.3 promotes this to a stateful day-by-day fold; AS.4 generalizes to cross-account vector state.

Parameters:
  • child_account_id (str)

  • child_role (str)

  • parent_role (str)

  • parent_account_id (str | None)

  • parent_account_role (str | None)

  • anchor_day (date)

  • magnitude (float)

  • rng (Random)

  • leg_amount (float)

  • prefix (str)

property also_trips_ledger_drift: RuleViolation | None

when this generator’s child-drift propagates up to the parent’s _ledger_drift. None when no parent account is present in the shape (the L2 instance has no account with the child’s parent_role).

Type:

The secondary edge

anchor_day: date
child_account_id: str
child_role: str
property claimed_accounts: frozenset[str]

The child account_id this plant drifts + the parent account_id when the shape has one (ledger_drift edge fires on the parent). AV.5.

emit(conn, *, scenario_id=None)[source]
Return type:

None

Parameters:
  • conn (SyncConnection)

  • scenario_id (str | None)

property intended: RuleViolation
leg_amount: float = 100.0

Clean leg amount; the child’s stored money is this + magnitude.

magnitude: float
parent_account_id: str | None
parent_account_role: str | None
parent_role: str
prefix: str = 'spec_example'
rng: Random
class recon_gen.common.spine.drift.DriftInvariant(prefix='spec_example')[source]

Bases: object

Sub-ledger drift detector. Persona-blind (no L2 join in the matview SQL), so scenario_for(role) resolves against any leaf internal account with that role.

Parameters:

prefix (str)

detect(conn)[source]
Return type:

set[Violation]

Parameters:

conn (SyncConnection)

name: ClassVar[str] = 'drift'
prefix: str = 'spec_example'

Prefix of the deployed L2 instance’s matviews. Concrete invariants carry this so detect() can read the right matview; AS.1’s Protocol stayed minimal (no prefix field) since not every invariant variant needs one.

scenario_for(role, *, magnitude=5.0, seed=None, instance=None, child_account_id=None, parent_account_id=None)[source]

Resolve the role against the shape and return a generator that manufactures a drift breach on a leaf account of that role.

instance=None loads the bundled spec_example; AS.x callers thread the real instance.

AY.4.c — child_account_id / parent_account_id override the default synthetic IDs. The plant adapter (AY.4.c.3) threads OLD DriftPlant.account_id through these kwargs so N drift plants on the same role produce N distinct generators (the default f”acct-drift-child-{role}” derivation would collide). Existing test callers can pass nothing → preserves the synthetic defaults byte-stable.

Return type:

DriftGenerator

Parameters:
  • role (str)

  • magnitude (float)

  • seed (int | None)

  • instance (L2Instance | None)

  • child_account_id (str | None)

  • parent_account_id (str | None)

class recon_gen.common.spine.drift.LedgerDriftGenerator(parent_account_id, parent_role, child_account_id, child_role, anchor_day, delta, rng=<factory>, leg_amount=100.0, prefix='spec_example')[source]

Bases: object

Plant a parent+child pair where the parent’s stored money diverges from Σ child stored by delta. Child is CLEAN (stored matches its single leg), so only ledger_drift fires — not drift.

Distinct from DriftGenerator (which fires BOTH drift on the child + ledger_drift on the parent as a coupled pair): this generator targets ledger_drift in isolation, modeling the real-world case where a control account is adjusted directly without touching the sub-ledger leaves.

Uses a UNIQUE synthetic parent_role so the matview’s computed_ledger_balance for our synthetic parent only sums OUR synthetic child (no bleed from baseline accounts that happen to share a real parent_role).

Parameters:
  • parent_account_id (str)

  • parent_role (str)

  • child_account_id (str)

  • child_role (str)

  • anchor_day (date)

  • delta (float)

  • rng (Random)

  • leg_amount (float)

  • prefix (str)

anchor_day: date
child_account_id: str
child_role: str
property claimed_accounts: frozenset[str]
delta: float
emit(conn, *, scenario_id=None)[source]
Return type:

None

Parameters:
  • conn (SyncConnection)

  • scenario_id (str | None)

property intended: RuleViolation
leg_amount: float = 100.0
parent_account_id: str
parent_role: str
prefix: str = 'spec_example'
rng: Random
class recon_gen.common.spine.drift.LedgerDriftInvariant(prefix='spec_example')[source]

Bases: object

Parent-ledger drift detector. Fires when a parent’s stored money ≠ Σ(child stored) + Σ(direct legs). DriftGenerator’s child-drift causes Σ(child stored) to shift — so this fires on the parent too.

Parameters:

prefix (str)

detect(conn)[source]
Return type:

set[Violation]

Parameters:

conn (SyncConnection)

name: ClassVar[str] = 'ledger_drift'
prefix: str = 'spec_example'