foley.obs.redact

Redaction of sensitive narration / prompt / query text in telemetry (#11).

A published product’s narration is frequently confidential or pre-release, yet foley exfiltrates prompt/query text to LLM, generation, and search backends (report 12 §privacy). So every value foley writes into a span attribute or a run-manifest is passed through this module’s SSOT redactor, which — by default — replaces a sensitive string with a salted content hash + length (never the raw text), so a manifest still joins a prompt to its provenance without exposing it.

Stdlib-only. Applied at TWO boundaries (belt-and-suspenders, mirroring qc._json_safe’s construction-time clamp + serialization sweep):

  • record-time (primary): the recorder routes every sensitive value through Redactor.redact_value() before it enters the in-memory SpanRecord / RunManifest — so raw text never reaches the OTel mirror either;

  • emit-time (net): Redactor.redact_manifest() deep-walks the serialized manifest before the store write, catching anything stuffed past record-time.

foley.obs.redact.REDACT_FIELDS: frozenset[str] = frozenset({'context_text', 'gen_ai.completion', 'gen_ai.prompt', 'generation_prompt', 'narration', 'negative_prompt', 'onset', 'prompt', 'query'})

The attribute / field keys whose string values are sensitive and redacted. The narration/prompt/query surfaces report 12 §11 names; context_text / narration / gen_ai.* producers arrive with the #7 agent, but the seam + keys exist now.

class foley.obs.redact.RedactionMode(value)[source]

How a sensitive string is rendered in telemetry (str-Enum → serializes cleanly).

class foley.obs.redact.Redactor(mode: RedactionMode = RedactionMode.hash, salt: str = 'foley-obs-v1', preview_chars: int = 0, fields: frozenset[str] = <factory>)[source]

The SSOT applier: redacts sensitive keys in values, attribute dicts, and manifests.

redact_attrs(attrs: dict | None) dict[source]

Redact every sensitive key in a (shallow) attribute/inputs dict.

redact_error(exc: BaseException) str[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 repr is kept only in full mode (opt-in local debug).

redact_manifest(payload)[source]

Deep-walk payload (dict/list), redacting any sensitive key at any depth.

The emit-time net: catches inputs.query / seeds[*].prompt / any nested sensitive value a caller stuffed past the record-time layer.

redact_value(key: str, value)[source]

Redact value iff key is a sensitive field and value is a string.

foley.obs.redact.redact_text(text: str | None, *, mode: RedactionMode = RedactionMode.hash, salt: str = 'foley-obs-v1', preview_chars: int = 0)[source]

Redact one string per mode.

Parameters:
  • text – The (possibly sensitive) string, or None.

  • modeoffNone; fulltext verbatim; hash (default) → {"sha256": <salted hex>, "len": <n>} (+ "preview" only if preview_chars > 0).

  • salt – Salt mixed into the hash (injectable; default fixed for diffability).

  • preview_chars – If > 0 (hash mode), include a leading text[:preview_chars] preview. Default 0 → zero content leak.

Returns:

None, the raw string, or a hash dict — depending on mode.