foley.obs#
Observability & the reproducible run-artifact for foley (#11).
The cross-cutting layer that makes foley debuggable, auditable, and re-renderable —
the “snapshots/stories for the SFX layer”. Every instrumented operation
(generate / add_from / ingest / search / similar — and, later, a
#7 find() / #8 weave() scope) can emit one RunManifest:
simultaneously the debug trace (span tree), the reproducible plan/seed record,
and the provenance record — report 10 §1.3.
Manifest-first: the run-artifact is pure stdlib and exists on a bare
pip install foley; OpenTelemetry is an optional mirror behind the
foley[obs] extra (vendor-neutral GenAI spans for Langfuse/Datadog/…). **Off by
default**: import foley stays dol-only and a plain façade call is a byte-for-byte
no-op until foley.obs.enable() / $FOLEY_OBS=1 / with foley.obs.run(...).
Sensitive prompt/query/narration text is redacted (salted content hash) by default.
All four submodules are stdlib-only at import; opentelemetry loads lazily inside
OTelTracer only, so importing this package is dol-only.
Functions
|
Serialize |
|
A leak-free digest of an |
|
Load and typed-rehydrate a |
|
Open an explicit run scope that aggregates nested façade calls into ONE manifest. |
|
The get-or-create seam every façade wraps its body in. |
The active |
|
|
Turn observability on process-wide (and apply any |
|
Turn observability off process-wide (façades revert to a byte-for-byte no-op). |
|
Apply |
|
Restore the default config (test teardown; clears injected store/tracer/clock). |
Whether observability is on (via |
|
|
Return the effective |
|
Redact one string per |
Classes
|
The reproducible run-artifact for one foley operation (trace ⊕ plan ⊕ provenance). |
|
One node of the run's span tree (the trace half of the artifact). |
|
One SELECT-stage step of a |
|
Process-wide observability configuration (flipped by |
|
Builds one |
|
Starts mirror spans; the DI seam (default no-op, OTel-backed when present). |
The default |
|
|
The SSOT applier: redacts sensitive keys in values, attribute dicts, and manifests. |
|
How a sensitive string is rendered in telemetry ( |
- class foley.obs.NoOpTracer[source]#
Bases:
objectThe default
Tracer— yields the shared_NOOP_SPAN, zero deps.
- class foley.obs.ObsConfig(enabled=False, force_disabled=False, redaction_mode=RedactionMode.hash, salt='foley-obs-v1', prefer_otel=True, run_store=None, tracer=None, clock=<built-in function time>, id_factory=<function ObsConfig.<lambda>>, now=<function _iso_now>)[source]#
Bases:
objectProcess-wide observability configuration (flipped by
enable()/disable()).- clock()#
time() -> floating-point number
Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.
- class foley.obs.RedactionMode(*values)[source]#
-
How a sensitive string is rendered in telemetry (
str-Enum → serializes cleanly).
- class foley.obs.Redactor(mode=RedactionMode.hash, salt='foley-obs-v1', preview_chars=0, fields=<factory>)[source]#
Bases:
objectThe SSOT applier: redacts sensitive keys in values, attribute dicts, and manifests.
- redact_attrs(attrs)[source]#
Redact every sensitive key in a (shallow) attribute/inputs dict.
- Return type:
- redact_error(exc)[source]#
Redact an exception for storage/export.
An exception message can echo the raw prompt/query/narration (hosted backends commonly do), so by default only the exception type name is recorded (safe + still useful); the full
repris kept only infullmode (opt-in local debug).- Return type:
- class foley.obs.RunManifest(run_id, op, created_at=None, foley_version=None, inputs=<factory>, params=<factory>, spans=<factory>, steps=<factory>, ingest_report=None, result_ids=<factory>, candidate_scores=<factory>, credits_ref=None, disclosure_refs=<factory>, seeds=<factory>, plan_ref=None, trace_ref=None, status='ok', error=None, schema_version=1)[source]#
Bases:
SerializableMixinThe reproducible run-artifact for one foley operation (trace ⊕ plan ⊕ provenance).
Persisted by
emit_run_manifest()into a run store keyed byrun_id. Sensitive prompt/query text lives redacted (seefoley.obs.redact); chosen clips are held by reference (SoundRecordid) so the manifest stays light and a trace can be replayed against a fresh index.
- class foley.obs.RunRecorder(manifest, *, tracer, redactor, run_store, clock, id_factory)[source]#
Bases:
objectBuilds one
RunManifest(span tree + composed shapes), tracer-independent.- add_step(step)[source]#
Append a SELECT-stage
Step(#7), deep-redactingdetailat record time.The record-time net (belt over the emit-time sweep): event text carried in
detail(under thequerykey) is hashed here so a manifest read straight offself.manifest.steps— before any emit — never holds raw narration.seqis assigned here (append position) when the caller left itNone.- Return type:
- set_plan_ref(plan_ref)[source]#
Fill the reserved #8
plan_refslot (a light join dict — no text).- Return type:
- span(name, *, kind=None, **attributes)[source]#
Open a span: append a
SpanRecord(from our own clock/ids) + mirror it.
- class foley.obs.SpanRecord(name, span_id, parent_id=None, kind=None, start_ms=None, duration_ms=None, status='ok', attributes=<factory>, events=<factory>, error=None)[source]#
Bases:
SerializableMixinOne node of the run’s span tree (the trace half of the artifact).
Built by the recorder from its own clock + id-factory, independent of any tracer, so the tree is complete even when the OTel mirror is a total no-op.
- class foley.obs.Step(kind, seq=None, event_index=None, span_id=None, status='ok', detail=<factory>)[source]#
Bases:
SerializableMixinOne SELECT-stage step of a
find()run (the plan half of the artifact, #7).The typed entry type of
RunManifest.steps: a compact, redacted record of eachdecompose/refine/search/license_gate/verify/decide/generate/placestage, inseqorder, withspan_idjoining the step back to theSpanRecordthat timed it.detailis a small, already-redacted dict (event text lives under thequerykey so the redactor catches it). Rehydrated element-wise byRunManifest.from_dict()exactly likespans(see the module docstring).
- class foley.obs.Tracer(*args, **kwargs)[source]#
Bases:
ProtocolStarts mirror spans; the DI seam (default no-op, OTel-backed when present).
- foley.obs.configure(**overrides)[source]#
Apply
ObsConfigoverrides WITHOUT flippingenabled(the test-injection seam).- Return type:
- foley.obs.current_run()[source]#
The active
RunRecorder, or_NULL_RUN(for the shared child span).
- foley.obs.disable()[source]#
Turn observability off process-wide (façades revert to a byte-for-byte no-op).
- Return type:
- foley.obs.emit_run_manifest(store, manifest, *, redactor=None)[source]#
Serialize
manifest(redacting the payload) and write it tostore.The plain-dict write path keeps an emit-time interposition point for the belt-and-suspenders redaction sweep (do NOT couple the write to auto-serialization). Returns the
run_id.- Parameters:
store (
MutableMapping) – AMutableMapping[str, dict](default:foley.stores.make_run_store(); a dict in tests).manifest (
RunManifest) – TheRunManifestto persist.redactor – An optional
Redactorapplied to the full payload before the write (the emit-time net).
- Return type:
- foley.obs.enable(**overrides)[source]#
Turn observability on process-wide (and apply any
ObsConfigoverrides).- Return type:
- foley.obs.facade_run(op, *, inputs=None, params=None)[source]#
The get-or-create seam every façade wraps its body in.
Nested (a run is already active) → a child span into the SAME manifest, no emit (the outer owner emits). Enabled + top-level → a new recorder that emits one manifest on exit. Disabled → the zero-cost
_NULL_RUN.
- foley.obs.get_tracer(*, prefer_otel=True)[source]#
Return the effective
Tracer(OTel-backed when available, else no-op).- Parameters:
prefer_otel (
bool) – WhenTrue(default) andopentelemetryis importable, return anOTelTracer(itself a free no-op until the host configures an SDK); any construction failure falls back to the no-op.Falseforces the stdlibNoOpTracer— the hermetic-test lever (this dev env may have otel).- Return type:
- Returns:
A
Tracer(never raises).
- foley.obs.ingest_digest(report)[source]#
A leak-free digest of an
IngestReportfor a manifest.Records the per-clip outcomes (id, status, QC status, notes) + summary counts — NOT the full
SoundRecords. The records carrycaption(which equals the raw generation prompt for a generated clip) +tagsand already live in the meta store keyed by the id referenced inresult_ids, so embedding them would both leak prompt text into the manifest and duplicate data. Duck-typed over the report (.summary()+.results) to avoid an obs → index import coupling.- Return type:
- foley.obs.is_enabled()[source]#
Whether observability is on (via
enable()or$FOLEY_OBSin {1,true,yes}).force_disabled(set byfoley.runtime.offline_scope()for a telemetry-off posture) and a telemetry-off runtime ($FOLEY_OFFLINE) hard-override both — so offline mode’s “nothing leaves the device” contract holds even when$FOLEY_OBSis exported.- Return type:
- foley.obs.load_run(store, run_id)[source]#
Load and typed-rehydrate a
RunManifestfromstore(the read helper).- Return type:
- foley.obs.redact_text(text, *, mode=RedactionMode.hash, salt='foley-obs-v1', preview_chars=0)[source]#
Redact one string per
mode.- Parameters:
text (
Optional[str]) – The (possibly sensitive) string, orNone.mode (
RedactionMode) –off→None;full→textverbatim;hash(default) →{"sha256": <salted hex>, "len": <n>}(+"preview"only ifpreview_chars > 0).salt (
str) – Salt mixed into the hash (injectable; default fixed for diffability).preview_chars (
int) – If > 0 (hash mode), include a leadingtext[:preview_chars]preview. Default 0 → zero content leak.
- Returns:
None, the raw string, or a hash dict — depending onmode.
- foley.obs.reset()[source]#
Restore the default config (test teardown; clears injected store/tracer/clock).
- Return type:
- foley.obs.run(op='run', *, inputs=None, params=None, **overrides)[source]#
Open an explicit run scope that aggregates nested façade calls into ONE manifest.
Forces observability on for the scope even when it is merely disabled (the #7
find()aggregation entrypoint). The one thing it does not override is the hard-offforce_disabledflag:foley.runtime.offline_scope()sets it for a telemetry-off posture, so an explicitrun()inside offline mode records and exports nothing — the “nothing leaves the device” contract holds on thefind()/weave()paths too, not just thefacade_runones. Get-or-create: a nestedrun()reuses the active recorder.overridesare per-scopeObsConfigfields (e.g.run_store,prefer_otel,clock).
Modules
The run-recorder: owns the manifest, drives the OTel mirror, off by default (#11). |
|
Redaction of sensitive narration / prompt / query text in telemetry (#11). |
|
The reproducible run-artifact SSOT — |
|
The OpenTelemetry MIRROR seam for foley's observability (#11). |