cadrumo.core.observability.store module¶
Filesystem persistence for run traces and JSONL event logs.
One subdirectory per run_id under
core.config.Settings.cadrumo_runs_dir, containing
trace.json and events.jsonl. Both files round-trip through the
strict pydantic models in core.observability.models.
Run traces are DIAGNOSTIC class. The redaction rule set returned by
core.redaction.rules.default_rules_for_class() for
DIAGNOSTIC walks
every string leaf — NIFs SHA-256-prefixed, URLs reduced to host-only,
bearer-shaped tokens fingerprinted, opaque bearers fingerprinted —
before serialisation. The core redaction helper is imported lazily so
commands that never persist traces avoid resolving the rule registry on
import.
- ENVELOPE_FILENAME¶
Per-run artifact leaf names, owned by this module (see the
run_trace/run_events/run_envelopeentries inSTORAGE_PATH_DEFINITIONS, each declaredowner="cadrumo.core.observability"and interpolating these constants into their grammar rather than re-typing the names).
- validate_run_id(run_id)[source]¶
Return
run_idif it matches the canonical shape, else raise.The canonical shape is 16 lowercase hex characters — the form minted by
core.observability.context._mint_run_id(). Validating every id reaching this layer prevents path-traversal escapes throughruns_dir / run_id.- Parameters:
run_id (
str) – Candidate run identifier.- Return type:
- Returns:
The same
run_idwhen it matches the canonical shape.- Raises:
RunTraceValidationError – When
run_idis not a 16-character lowercase hex string.
- runs_dir(settings=None)[source]¶
Return the configured runs directory. Resolution only; never creates it.
Resolved through
storage_path()forStorageCategory.RUNSrather than by readingcadrumo_runs_dirdirectly here. Both answer the same today, because the settings field is what the accessor consults first – but only one of them stays correct if the member’s resolution ever gains a case.A pure path resolver. Materialisation is a write-side concern: the one production writer (
run_dir()) creates the full<runs_dir>/<run_id>chain itself viamkdir(parents=True)when it stages a run for writing, which brings this root into existence as a side effect of that singlemkdircall – it does not depend on this function creating anything. A read must never materialise a directory for what it failed to find, so every read-only caller (load_trace(),load_envelope_document(),iter_events(),iter_runs(),prune_run_traces()) treats an absent root exactly like an empty one rather than relying on this resolver to conjure it first.- Parameters:
settings (
Settings|None) – Optionalcore.config.Settingsoverride (used by tests). WhenNone, the active settings are loaded viacore.config.load_settings().- Return type:
Path- Returns:
Absolute path to the per-process runs root. Not guaranteed to exist.
- run_dir(run_id, *, settings=None)[source]¶
Return the per-run directory, creating it (and the runs root) when absent.
The one production write path that materialises anything under the runs root:
mkdir(parents=True)creates the per-run subdirectory and, as part of the same call, any missing ancestor – including the runs root itself – in one step. Rejectsrun_idvalues that do not match the canonical minted shape soruns_dir / run_idcannot traverse out of the configured runs directory.- Parameters:
run_id (
str) – 16-char lowercase hex run identifier.settings (
Settings|None) – Optionalcore.config.Settingsoverride.
- Return type:
Path- Returns:
Absolute path to the per-run subdirectory (created if absent).
- save_trace(trace, *, settings=None)[source]¶
Persist a
RunTraceto<runs_dir>/<run_id>/trace.json.Every string leaf passes through
core.redaction.rules.redact_structured()at DIAGNOSTIC class before serialisation so the on-disk record never carries a plaintext NIF, bearer token, or sensitive URL path even if a caller fed one intoarguments.- Parameters:
settings (
Settings|None) – Optionalcore.config.Settingsoverride.
- Return type:
Path- Returns:
Absolute path of the written
trace.jsonfile.
- load_trace(run_id, *, settings=None)[source]¶
Load and strictly validate a persisted
RunTrace.Read-only: does not create the per-run directory, nor the runs root itself. A missing
trace.jsonraisesRunTraceValidationErrorwithout polluting the runs directory with an empty entry.- Parameters:
run_id (
str) – 16-char lowercase hex run identifier.settings (
Settings|None) – Optionalcore.config.Settingsoverride.
- Return type:
- Returns:
The validated
RunTrace.- Raises:
RunTraceValidationError – When
run_idhas an invalid shape, when the file is missing, when its contents fail strict validation, or when its embedded identity names another run.
- save_envelope(run_id, document, *, settings=None)[source]¶
Persist an emitted envelope document to
<runs_dir>/<run_id>/envelope.json.The document is the verbatim, already-CLI-redacted
SchemaEnvelopemapping captured bycore.observability.capture.capture_envelopes()during the run. It is stored key-sorted so the on-disk artifact is byte-stable, and it is durable evidence for the recorded run. Re-validation into a typed envelope happens on load viacore.observability.validate_captured_envelope; this writer stays free of any JSON-contract dependency.
- load_envelope_document(run_id, *, settings=None)[source]¶
Load the persisted emitted-envelope document for a run.
Read-only: does not create the per-run directory, nor the runs root itself. Returns the raw mapping; type it with
core.observability.validate_captured_envelope.- Parameters:
run_id (
str) – 16-char lowercase hex run identifier.settings (
Settings|None) – Optionalcore.config.Settingsoverride.
- Return type:
- Returns:
The persisted envelope mapping.
- Raises:
RunTraceValidationError – When
run_idhas an invalid shape, the file is missing, or its contents are not a JSON object.
- save_events_append(run_id, event, *, settings=None)[source]¶
Append a single
RunEventline to the per-runevents.jsonl.newline=""pins the on-disk line terminator to\\non every platform — mirroringcore.observability.sink.JsonlRunSink— soevents.jsonlis byte-stable across Windows and POSIX writers. Every string leaf in the event is redacted at DIAGNOSTIC class before serialisation so the on-disk record stays free of plaintext NIFs / tokens / sensitive URLs.
- iter_events(run_id, *, settings=None)[source]¶
Return an iterator of
RunEventrecords from the per-runevents.jsonl.Streams records so callers processing a long-running run’s event log can avoid holding the entire file in memory. The
run_idis validated eagerly — before the iterator starts — so a bad id surfaces at the call site instead of on first iteration.Read-only: does not create a run directory when absent, nor the runs root itself. A missing file yields no records.
- iter_runs(*, settings=None)[source]¶
Yield
(run_id, RunTrace)pairs sorted bystarted_atdescending.Directories without a valid
trace.json, whose name does not match the canonicalrun_idshape, or whose embedded trace identity does not match their directory are skipped. This lets crashed runs (no on-exit finaliser call) coexist with healthy ones rather than poisoning a run listing, and blocks any non-run artefacts that may have been dropped into the runs directory by hand.Read-only: does not create the runs directory. No run has ever been saved on a fresh install or an isolated test root, so an absent runs directory is treated exactly like an empty one – yielding nothing – rather than raising.
- prune_run_traces(*, retention_days=None, max_total_bytes=None, settings=None)[source]¶
Delete run directories beyond the retention window or the size ceiling.
Gives the run-trace store a declared retention lifecycle instead of one subdirectory accumulating per run forever. Two independent bounds apply in order, each delegating its survivor decision to the shared
select_filesystem_retention_survivors()selector while this function keeps its ownrmtreeside effect and its own newest-directory-never-size-pruned rule:Age — run directories whose modification time (last write) is older than
retention_daysare removed, so crashed runs that never produced a validtrace.jsonare pruned too rather than accumulating unreadable.Total size — if the surviving run directories still exceed
max_total_byteson disk, the oldest directories are removed until the store fits under the ceiling. The newest run directory is always kept (protect_newest=1), so the trace whose save triggered the prune survives even when it alone exceeds the ceiling.
retention_daysdefaults to the centralizedcadrumo_runs_retention_daysandmax_total_bytestocadrumo_runs_max_total_bytes. Entirely best-effort: a directory that cannot be enumerated, stat’d, or removed is logged and skipped, never raised – pruning must not crash the caller.- Parameters:
retention_days (
int|None) – Age cutoff in days; run directories whose mtime is strictly older are removed. Defaults to the centralized setting.max_total_bytes (
int|None) – Total on-disk size ceiling for the run-trace store. Defaults to the centralized setting.settings (
Settings|None) – Optionalcore.config.Settingsoverride.
- Return type:
- Returns:
Number of run directories removed.