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
TestGeneratorConfigfields (scope/end_date/seed/plants/only_template/derive_balances) plus the trainer’s scenariowindow(start + end) and theetl_hook.enabledtoggle.Missing file ⇒ “use
cfg.test_generatordefaults” (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 primitiveL2InstanceCacheuses), 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 the sidefile from disk; None if missing or malformed. |
|
Apply the sidefile's overrides on top of cfg.test_generator. |
|
Atomically write the sidefile. |
|
Resolve the sidefile path from the cfg.yaml path. |
Classes
|
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:
objectTrainer-mode persistent state. Inverse of the
TestGeneratorCachemutable 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:
state (StudioState)
path (Path | str)