recon_gen.common.l2.cache
In-memory L2Instance cache + atomic file-write primitive (X.4.a.6).
Studio’s source-of-truth contract (per SPEC_studio.md): the L2 YAML on
disk is the hard truth; the in-memory L2Instance is a cache of the
file, never a parallel source. This module ships the two primitives that
contract rests on:
L2InstanceCache— Studio-owned read cache. Loads once at startup viacommon/l2/loader.py::load_instance; holds the parsed model so per-request YAML re-parse isn’t on the hot path. Read-only here in X.4.a.6 (get()); thesave()method that writes back to disk lands at X.4.d.3 once the editor’s serializer (serialize_l2) is in place.replace(new_instance)is provided now so the X.4.d.1 mutators have a typed seam to update the cache without touching disk.save_yaml_atomic(text, path)— atomic file write: temp file in the same directory, fsync the contents, then rename onto the target. POSIX rename(2) inside one filesystem is atomic, so a crash mid-write leaves either the old YAML or the new YAML on disk — never a torn half-written file. The temp lives in the target’s parent dir specifically so the rename can’t cross filesystems.
Severability: this module is Studio-side. Dashboards (recon-gen
dashboards) does NOT instantiate L2InstanceCache — it reads the L2
once at startup and keeps the parsed instance in its own scope. The
cache only exists when Studio mounts.
Functions
|
Atomically write |
Classes
|
Studio-owned in-memory cache of one |
- class recon_gen.common.l2.cache.L2InstanceCache(path, instance)[source]
Bases:
objectStudio-owned in-memory cache of one
L2Instance.Constructed once at Studio startup via
L2InstanceCache.from_path; bound to a single L2 YAML path for its lifetime. Studio request handlers callget()to read the current model; once X.4.d lands its mutators, they callreplace(new_instance)to swap the cached value, and X.4.d.3’s serializer-awaresave()will pairreplacewithsave_yaml_atomicso disk + cache stay in sync.Read-only-from-disk for now (X.4.a.6 ships
get()+replace); no reload-on-file-change watcher (per the SPEC’s “Studio writes; nobody else writes” rule).- Parameters:
path (Path)
instance (L2Instance)
- classmethod from_path(path)[source]
Load the YAML at
pathand wrap the result in a cache.- Return type:
- Parameters:
path (Path | str)
- property path: Path
- replace(new_instance)[source]
Swap the cached instance without writing to disk.
X.4.d’s mutators (
mutate_l2/rename_identifier) produce a newL2Instancefrom the old one;replaceinstalls it. Pairing this with a disk write happens insave()below (which composesserialize_l2+save_yaml_atomic+replace).- Return type:
None- Parameters:
new_instance (L2Instance)
- save(new_instance)[source]
Persist + swap: serialize
new_instanceto YAML, atomically write it toself.path, thenreplacethe cached instance.The X.4.e PUT handler composes
mutate_l2 → validate → save— every successful mutation lands on disk + in cache atomically (within one event loop tick). A crash mid-write leaves the prior YAML intact (persave_yaml_atomic’s POSIX rename guarantee).Caller is responsible for validating
new_instanceBEFORE callingsave— a structural-break delete that re-raisesL2ValidationErrorshould never reach disk.- Return type:
None- Parameters:
new_instance (L2Instance)
- recon_gen.common.l2.cache.save_yaml_atomic(text, path)[source]
Atomically write
texttopath.Writes to a temp file in
path.parent(so the final rename stays within one filesystem and is therefore atomic per POSIXrename(2)), fsyncs the file contents, then renames onto the target. A crash between fsync and rename leaves the old file intact; a crash during write leaves a stray.<name>.*.tmpthat the next save overwrites.Caller’s responsibility:
path.parentmust already exist.- Return type:
None- Parameters:
text (str)
path (Path)