recon_gen.common.l2.demo_etl_gaps

BTa.8 — ETL-feed gap plants for the bundled-demo path.

When the operator runs Studio without an etl_hook configured, the synthetic-data generator produces clean, L2-conformant rows. That’s correct for production deployments — but it leaves the /etl/triage page empty, so the BTa.4 accordion / volume badges / per-kind color stripes never get exercised in the demo experience.

This module exposes one plant function per demo-surfaced ETL trust-killer, mirroring how add_broken_rail_plants / boost_inv_fanout_plants in auto_scenario.py cover the L1-invariant violation kinds. Each function below produces SQL for ONE plant kind; emit_demo_etl_gap_sql composes them.

Two surfaces, two failure shapes:

Plant kind Surface Function —————————– —————- ———————————- phantom rail (INSERT) Triage add_phantom_rail_gap_rows phantom template (INSERT) Triage add_phantom_template_gap_rows missing metadata (INSERT) Triage add_missing_metadata_gap_rows uncovered rail (DELETE) Coverage add_uncovered_rail_gap_rows uncovered template (DELETE) Coverage add_uncovered_template_gap_rows missing_limit_schedule (NOT planted) — see note below

Cold-read v3 finding: planting only Triage gaps (the INSERT kind) left Coverage all-green while Triage went red. The operator’s mental model says “if Triage has gaps, Coverage should reflect the brokenness too” — otherwise a glance at Coverage misleads. DELETE-based plants surgically empty one L2 entity’s transaction set so its Coverage card flips to ✗ (“declared but no rows”).

The missing_limit_schedule gap kind isn’t planted here because it’s an L2-shape gap (rail/role pair without a schedule), not a transaction-stream gap; you can’t synthesize it via inserts.

Architectural note: L1-invariant plants ship as ScenarioPlant dataclass entries that flow through emit_full_seed’s emitter pipeline. ETL-feed gaps live in the transaction stream’s contract layer (not the invariant layer), so they ship as raw INSERT overlays invoked AFTER the generator finishes — same conceptual “plant” pattern (deterministic, demo-only, lights up a specific dashboard surface), different integration point.

Gated by cfg.etl_hook is None at the studio call site — a real operator’s ETL feed is the source of truth; demo overlay would corrupt it. Locked seeds are unaffected because they’re built from the generator pipeline directly, never via this overlay.

Functions

add_chain_orphan_gap_rows(instance, *, ...)

Plant count parent firings for a Required (singleton-children, non-fan_in) chain WITHOUT matching child firings.

add_dead_bundles_activity_gap_rows(instance, ...)

Empty all transactions for one L2-declared bundle_target (DELETE).

add_dead_limit_schedule_gap_rows(instance, ...)

Empty all Debit transactions for one L2-declared LimitSchedule cell (DELETE).

add_dead_metadata_gap_rows(instance, *, ...)

Empty all transactions for one L2-declared (rail, metadata_key) pair (DELETE).

add_missing_metadata_gap_rows(instance, *, ...)

Plant one transaction whose template_name resolves but whose metadata omits the template's required transfer_key.

add_phantom_rail_gap_rows(*, prefix, ...[, ...])

Plant count transactions whose rail_name doesn't resolve in the L2.

add_phantom_template_gap_rows(*, prefix, ...)

Plant count transactions whose template_name doesn't resolve in the L2.

add_uncovered_rail_gap_rows(instance, *, ...)

Empty all transactions for one L2-declared rail (DELETE).

add_uncovered_template_gap_rows(instance, *, ...)

Empty all transactions for one L2-declared template (DELETE).

emit_demo_etl_gap_sql(instance, *, prefix, ...)

Compose all demo ETL-feed gap plants into one SQL overlay.

recon_gen.common.l2.demo_etl_gaps.add_chain_orphan_gap_rows(instance, *, prefix, dialect, anchor, count=3)[source]

Plant count parent firings for a Required (singleton-children, non-fan_in) chain WITHOUT matching child firings.

Picks the alphabetically-first Required chain (singleton children, fan_in=False on the only child). Inserts count rows whose rail_name is the chain’s parent identifier; no row references these as a transfer_parent_id, so the L2FT build_exc_chain_orphans_dataset check fires (parent_firing_count > child_firing_count).

Returns empty string when the L2 declares no Required chain — the BU.3 picker’s “satisfiability” guard.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_dead_bundles_activity_gap_rows(instance, *, prefix, dialect)[source]

Empty all transactions for one L2-declared bundle_target (DELETE).

Picks the alphabetically-first (aggregating_rail, bundle_target) pair declared on an L2 rail’s bundles_activity; deletes every row whose rail_name = bundle_target so the build_exc_dead_bundles_activity_dataset check’s NOT EXISTS matches. Returns empty string when no rail declares bundles_activity.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_dead_limit_schedule_gap_rows(instance, *, prefix, dialect)[source]

Empty all Debit transactions for one L2-declared LimitSchedule cell (DELETE).

The L2FT dead_limit_schedules check fires when no Debit posting matches (parent_role, rail_name). Picks the alphabetically-first (parent_role, rail_name) cell declared in instance.limit_schedules + deletes every matching Debit row. Returns empty string when no LimitSchedule is declared.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_dead_metadata_gap_rows(instance, *, prefix, dialect)[source]

Empty all transactions for one L2-declared (rail, metadata_key) pair (DELETE).

The L2FT dead_metadata check fires per (rail, metadata_key) declared on an L2 Rail when no posting on that rail carries a non-null value for the key. Simplest plant: DELETE every row on the chosen rail — the NOT EXISTS guard then matches for every key the rail declared (rail-level), surfacing one or more rows on the L2FT Hygiene Exceptions sheet’s Dead Metadata Declarations branch.

Picks the alphabetically-first rail with non-empty metadata_keys. Returns empty string when no rail declares any.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_missing_metadata_gap_rows(instance, *, prefix, dialect, anchor)[source]

Plant one transaction whose template_name resolves but whose metadata omits the template’s required transfer_key.

Triage’s _detect_missing_metadata_keys picks this up + renders a missing_metadata_key accordion section. Returns empty string when the L2 has no template with a transfer_key — the gap can’t be synthesized without a target template that declares one.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_phantom_rail_gap_rows(*, prefix, dialect, anchor, count=3, rail_name='legacy_card_swipe')[source]

Plant count transactions whose rail_name doesn’t resolve in the L2.

Triage’s _detect_unmatched_rails picks these up + renders an unmatched_rail accordion section with the volume badge showing <count> rows. The default phantom name (legacy_card_swipe) is chosen to read like a plausible legacy rail an integrator might forget to declare — not a random sentinel.

BU.1 — rail_name is now operator-overridable so the Trainer plant page can pre-populate it from the registry and let the operator type a custom name for the demo / cold-read scenario. Defaults to the bundled-demo sentinel for backward compatibility with the BTa.8 overlay caller.

Parallels add_broken_rail_plants in auto_scenario.py (single plant kind, parameterized count, deterministic output).

Return type:

str

Parameters:
  • prefix (str)

  • dialect (Dialect)

  • anchor (datetime)

  • count (int)

  • rail_name (str)

recon_gen.common.l2.demo_etl_gaps.add_phantom_template_gap_rows(*, prefix, dialect, anchor, count=2)[source]

Plant count transactions whose template_name doesn’t resolve in the L2.

Triage’s _detect_unmatched_templates picks these up + renders an unmatched_template accordion section. rail_name on these rows is a separate phantom so the row contributes to template detection but doesn’t double-count with the rail plant.

Return type:

str

Parameters:
  • prefix (str)

  • dialect (Dialect)

  • anchor (datetime)

  • count (int)

recon_gen.common.l2.demo_etl_gaps.add_uncovered_rail_gap_rows(instance, *, prefix, dialect)[source]

Empty all transactions for one L2-declared rail (DELETE).

The Coverage card for Rails then renders that rail as ✗ (declared but no rows). Picks the alphabetically-last rail deterministically — a stable, demo-clear choice that doesn’t depend on row counts. Returns empty string when the L2 declares no rails (vacuous deployment).

Parallels add_phantom_rail_gap_rows on the Triage side: same demo-shaping intent, opposite DML (INSERT vs DELETE).

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.add_uncovered_template_gap_rows(instance, *, prefix, dialect)[source]

Empty all transactions for one L2-declared template (DELETE).

The Coverage card for Templates then renders that template as ✗. Picks the alphabetically-last template deterministically. Returns empty string when the L2 declares no templates.

Return type:

str

Parameters:
recon_gen.common.l2.demo_etl_gaps.emit_demo_etl_gap_sql(instance, *, prefix, dialect, anchor=None)[source]

Compose all demo ETL-feed gap plants into one SQL overlay.

Calls the three per-kind plant functions in order, joins the output, returns a single string of semicolon-terminated INSERTs safe to execute_script against any supported dialect.

anchor controls the wall-clock posting timestamp; defaults to datetime.now() so freshly-planted gaps surface in the default Triage / Probe windows. Pass an explicit datetime in tests for byte-stability.

Composition matches build_default_scenario in cli/_helpers.py (which chains default_scenario_fordensify_scenarioadd_broken_rail_plantsboost_inv_fanout_plants): each plant kind is its own function, called in a fixed sequence.

Return type:

str

Parameters: