recon_gen.common.spine.chain_parent_disagreement
Chain-parent-disagreement family — Invariant + ViolationGenerator.
AX.1 promotion of the AB.2.6 / AB.2.3 plant. The matview <prefix>_chain_parent_disagreement GROUPs <prefix>_current_transactions by (transfer_id, template_name) and surfaces rows where COUNT(DISTINCT transfer_parent_id) > 1 — a single child Transfer’s legs claim multiple parent Transfers, which is an ETL bug (parent reference drift, cross-cycle contamination, or a chain emitter that forgot the first-firing-wins rule from SPEC gap doc §3).
Per the AB.0 lock: chain integrity is an L2-SHAPE invariant (the L2 yaml declares the chain; ETL must honor the declared parent linkage). It lives in ALL_L2_SHAPE_INVARIANTS alongside the other 3 chain/XOR/fan-in invariants AX promotes.
- Matview semantics:
Filter: transfer_parent_id IS NOT NULL AND template_name IS NOT NULL AND status <> ‘Failed’.
GROUP BY (transfer_id, template_name).
HAVING COUNT(DISTINCT transfer_parent_id) > 1.
Account columns are NOT filtered or grouped → the generator’s synthetic account is fine.
The plant is account-shape-agnostic — the matview only cares that ≥2 distinct transfer_parent_id values exist under one (transfer_id, template_name). The generator emits 2 Posted leg rows sharing one synthetic transfer_id + template_name, each carrying a distinct synthetic transfer_parent_id.
Single-edge property (matches AT.3 / anomaly / money_trail): transfers-only — no daily_balances rows → no drift trip from this plant.
Classes
|
Emit 2 Posted transaction legs sharing one transfer_id + template_name but assigning different transfer_parent_id values — surfaces in the matview's COUNT(DISTINCT transfer_parent_id) > 1 branch. |
|
Detector for the AB.2.3 matview. |
- class recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementGenerator(child_template_name, anchor_day, parent_a_transfer_id='tr-cpd-parent-a', parent_b_transfer_id='tr-cpd-parent-b', rail_name='_spine_plant', prefix='spec_example', account_id_override=None)[source]
Bases:
objectEmit 2 Posted transaction legs sharing one transfer_id + template_name but assigning different transfer_parent_id values — surfaces in the matview’s COUNT(DISTINCT transfer_parent_id) > 1 branch.
Account fields are synthetic + deterministic (the matview’s GROUP BY ignores them). The transfer_id + template_name combination IS the violation’s identity; both are derived from child_template_name so two generators built for the same template would collide at compose time — caught by AV.5’s claimed_accounts pairwise-disjoint check.
Single-edge: transfers-only emit → no balance rows → no drift trip (matches the AT.3 anomaly / money_trail shape).
AY.4.c.2 — account_id_override allows the plant adapter (AY.4.c.3) to thread OLD plant account_ids through, preventing PK collisions when N plants of the same shape compose.
- Parameters:
child_template_name (str)
anchor_day (date)
parent_a_transfer_id (str)
parent_b_transfer_id (str)
rail_name (str)
prefix (str)
account_id_override (str | None)
- property account_id: str
Single synthetic account_id the generator’s 2 legs land on. Matview filters don’t depend on account columns; the account is here just to satisfy NOT NULL constraints + AV.5’s claimed_accounts contract.
account_id_overridewins when set (AY.4.c.2 — plant-adapter PK-collision avoidance).
- account_id_override: str | None = None
- anchor_day: date
- child_template_name: str
- property claimed_accounts: frozenset[str]
Single synthetic account — AV.5 contract. Two generators targeting the same child_template_name collide here (same account_id derivation → same string in the claimed set).
- emit(conn, *, scenario_id=None)[source]
- Return type:
None- Parameters:
conn (SyncConnection)
scenario_id (str | None)
- property intended: RuleViolation
The natural-key tuple the matview surfaces post-plant.
- parent_a_transfer_id: str = 'tr-cpd-parent-a'
- parent_b_transfer_id: str = 'tr-cpd-parent-b'
- prefix: str = 'spec_example'
- rail_name: str = '_spine_plant'
- property transfer_id: str
Deterministic child transfer_id — used as the matview’s natural-key tuple value + the row’s id discriminator.
- class recon_gen.common.spine.chain_parent_disagreement.ChainParentDisagreementInvariant(prefix='spec_example')[source]
Bases:
objectDetector for the AB.2.3 matview.
Identity tuple: (transfer_id, child_template_name). The matview’s other columns (distinct_parent_count, parent_transfer_id_min, parent_transfer_id_max, business_day) are diagnostic — they help an analyst eyeball which parents disagreed but they’re not part of the violation’s identity.
- Parameters:
prefix (str)
- name: ClassVar[str] = 'chain_parent_disagreement'
- prefix: str = 'spec_example'
- scenario_for(*, anchor_day=datetime.date(2030, 1, 1), instance=None)[source]
Pick a chain whose singleton child is a TransferTemplate from the L2; return a generator that plants ONE child Transfer with 2 legs carrying disagreeing transfer_parent_id values.
Raises ValueError if the L2 has no chain whose singleton child resolves to a TransferTemplate (the AB.2.6 picker’s input requirement).
- Return type:
- Parameters:
anchor_day (date)
instance (L2Instance | None)