recon_gen.common.l2.studio_state

Studio session-state sidefile (X.4.h.7).

Trainer-mode UI mutations (data-shaping panel knobs + scenario window + etl_hook toggle) persist to a sibling .studio-state.yaml next to cfg.yaml so they survive Studio restarts. The main cfg.yaml stays sacred — operator-authored, comments preserved, never re-formatted by Studio.

Why a sidefile, not in-place rewrite of cfg.yaml. Operator-authored cfg.yaml carries structured # comments per section that explain each field’s purpose to humans. Studio’s serialize_l2 / serialize_cfg round-trip would obliterate every comment on first knob mutation (per the SPEC’s “freeform comments dropped on serialize” contract). The sidefile pattern keeps that contract intact for cfg.yaml while still giving Studio a place to persist its session state.

Sidefile contract:

  • Path: <cfg.parent>/.studio-state.yaml (sibling to cfg.yaml). Leading dot signals “internal / not source-controlled” and matches conventional dotfile gitignore patterns.

  • Holds the trainer-mutable subset of TestGeneratorConfig fields (scope / end_date / seed / plants / only_template / derive_balances) plus the trainer’s scenario window (start + end) and the etl_hook.enabled toggle.

  • Missing file ⇒ “use cfg.test_generator defaults” (no error, silent first-run).

  • Malformed file ⇒ same fallback, with a warning to stderr — operator can delete the file to reset.

  • Atomic write via save_yaml_atomic (same primitive L2InstanceCache uses), so a crash mid-write leaves the prior state intact.

No debouncing in v1. Save fires synchronously per mutation. The file is small (~10 lines), the atomic-write is sub-millisecond, and trainer controls are clicks (not continuous slider drags), so there’s no hot-loop pressure to coalesce. Add debouncing if the trainer ever gains a continuous-input control.

Functions

load_studio_state(path)

Load the sidefile from disk; None if missing or malformed.

merge_into_test_generator(cfg_tgen, state)

Apply the sidefile's overrides on top of cfg.test_generator.

save_studio_state(state, path)

Atomically write the sidefile.

sidefile_path_for(cfg_path)

Resolve the sidefile path from the cfg.yaml path.

Classes

StudioState([scope, end_date, seed, plants, ...])

Trainer-mode persistent state.

class recon_gen.common.l2.studio_state.StudioState(scope=None, end_date=None, seed=None, plants=None, only_template=None, derive_balances=None, window=None, etl_hook_enabled=None)[source]

Bases: object

Trainer-mode persistent state. Inverse of the TestGeneratorCache mutable surface.

All fields default to None / sentinel so a brand-new sidefile (or one written by an older Studio version that didn’t know about a field) loads cleanly — missing keys mean “use cfg defaults.”

Parameters:
  • scope (Literal['full', 'exceptions_only', 'uncovered_rails', 'only_template'] | None)

  • end_date (date | None)

  • seed (int | None)

  • plants (tuple[Literal['drift', 'overdraft', 'limit_breach', 'stuck_pending', 'stuck_unbundled', 'supersession'], ...] | None)

  • only_template (str | None)

  • derive_balances (bool | None)

  • window (DateInterval | None)

  • etl_hook_enabled (bool | None)

derive_balances: bool | None = None
end_date: date | None = None
etl_hook_enabled: bool | None = None
only_template: str | None = None
plants: tuple[Literal['drift', 'overdraft', 'limit_breach', 'stuck_pending', 'stuck_unbundled', 'supersession'], ...] | None = None
scope: Literal['full', 'exceptions_only', 'uncovered_rails', 'only_template'] | None = None
seed: int | None = None
window: DateInterval | None = None
recon_gen.common.l2.studio_state.load_studio_state(path)[source]

Load the sidefile from disk; None if missing or malformed.

Malformed = YAML parse error OR top-level isn’t a dict. Logs a warning to stderr in that case so the operator notices that their session state didn’t survive (vs. silently falling back).

Return type:

StudioState | None

Parameters:

path (Path | str)

recon_gen.common.l2.studio_state.save_studio_state(state, path)[source]

Atomically write the sidefile.

Return type:

None

Parameters:
recon_gen.common.l2.studio_state.sidefile_path_for(cfg_path)[source]

Resolve the sidefile path from the cfg.yaml path.

Return type:

Path

Parameters:

cfg_path (Path | str)