recon_gen.common.spine.plant_adapter

AY.4.c.3 — scenario_to_generators(plants, instance, anchor).

Walks a ScenarioPlant (the OLD per-plant-kind aggregate from common/l2/seed.py) and materializes one spine ViolationGenerator per plant. The AY.4.d build_full_seed_sql rewrite composes the returned tuple via ScenarioContext.compose(dry_run=True) + the AY.4.b renderer to produce the production seed SQL.

## Mapping table — one branch per plant kind

Plant | Generator | Notes |

|----------------------------------|————————————|----------------------------------------------------| | DriftPlant | DriftGenerator | factory + child_account_id override | | OverdraftPlant | OverdraftGenerator | factory + account_id override | | LimitBreachPlant | LimitBreachGenerator (Outbound) | factory + account_id override | | InboundCapBreachPlant | LimitBreachGenerator (Inbound) | factory + account_id override | | StuckPendingPlant | StuckPendingGenerator | factory + account_id override | | StuckUnbundledPlant | StuckUnbundledGenerator | factory + account_id override | | FailedTransactionPlant | FailedTransactionGenerator | direct construct (no factory yet) | | SupersessionPlant | SupersessionGenerator | direct construct | | ChainParentDisagreementPlant | ChainParentDisagreementGenerator | direct + account_id_override | | TwoTemplateChainPlant | TwoTemplateChainGenerator | direct + L2 resolution + account_id_override | | XorVariantMissedFiringPlant | XorGroupMissedFiringGenerator | direct + account_id_override | | XorVariantOverlapPlant | XorGroupOverlapGenerator | direct + account_id_override | | FanInChainPlant (healthy) | FanInChainGenerator(healthy) | direct + account_id_override | | FanInChainMissingParentPlant | FanInChainGenerator(missing) | direct + account_id_override | | FanInChainExtraParentPlant | FanInChainGenerator(extra) | direct + account_id_override | | MultiXorMissedPlant | MultiXorMissedGenerator | direct + account_id_override | | MultiXorOverlapPlant | MultiXorOverlapGenerator | direct + account_id_override | | TransferTemplatePlant | TransferTemplateGenerator | direct + L2 resolution (kind from first leg_rail) | | RailFiringPlant | RailFiringGenerator | direct + L2 resolution (rail kind) | | InvFanoutPlant | InvFanoutGenerator | direct construct |

## What the adapter does NOT do

  • Chain-completion side-effects. The OLD _emit_rail_firing_rows + _emit_transfer_template_rows plant _emit_plant_chain_completion child rows so the chain integrity matview doesn’t false-positive. The spine generators omit these. AY.5’s re-lock pass either accepts the drift (matview rows differ) or a dedicated ChainCompletionGenerator lands as a separate concern.

  • `transfer_key` metadata cascade. The OLD path stamps containing TransferTemplate transfer_key field values on every leg the template’s leg_rails fire. Spine generators emit a minimal metadata payload. Same drift-acceptance contract.

These omissions are documented in each spine generator module’s “What this does NOT do yet” section + are the source of expected byte drift at AY.5.

Functions

scenario_to_generators(scenarios, instance, *)

Walk every plant collection on scenarios + return the matching spine generator per plant.

recon_gen.common.spine.plant_adapter.scenario_to_generators(scenarios, instance, *, frame=None, prefix='spec_example')[source]

Walk every plant collection on scenarios + return the matching spine generator per plant. Order matches the OLD emit_seed’s per-kind dispatch (drift → overdraft → limit → inbound cap → two-template → … → inv-fanout) so debug output stays diff-friendly.

BD.3 (2026-05-25): the only temporal input is frame: AsOfFrame. The legacy anchor / plant_window / as_of kwargs were dropped (no-compat-shim — pre-stable framing). The four production callers — cli/data.py::data apply, common/l2/seed.py × 2, tests/e2e/_seed_helpers.py — each construct a frame at the seam.

Frame resolution:

  1. frame is supplied → anchor_day = frame.as_of, plant_window = frame.window.

  2. Not supplied (positional-only test callers in tests/unit/test_spine_ay4c3_plant_adapter.py and other bare emit_seed(inst, scenario) test seams) → degenerate fallback frame: as_of = scenarios.today, plant_window = [scenarios.today - 90, scenarios.today]. The 90-day backward window matches emit_baseline_seed’s default window_days=90 so plants with days_ago up to 90 fit the at_offset_from_end validation. Single-day windows break L1 plants with days_ago > 0 (drift/overdraft default days_ago=4).

wall_clock (for StuckPending / StuckUnbundled generators) derives from anchor_day at noon.

prefix defaults to “spec_example” (the in-process test harness shape). Production callers (AY.4.d build_full_seed_sql) pass cfg.db_table_prefix; every constructed generator inherits self.prefix = prefix so insert_tx / insert_balance writes to the correctly-prefixed tables.

Return type:

tuple[ViolationGenerator, ...]

Parameters: