--- title: Observability & PHI Safety Pipeline acronyms: [VTL] created: 2026-07-09 updated: 2026-07-15 type: concept tags: [sdk, compliance, infrastructure] confidence: high source_files: - integrity-sdk/integrity_sdk/security/redactor.py - integrity-sdk/integrity_sdk/integrations/openai_integrity.py - integrity-sdk/integrity_sdk/integrations/langchain_callback.py - integrity-sdk/integrity_sdk/telemetry/tracing.py - integrity-sdk/tests/unit/test_redactor.py - integrity-sdk/tests/unit/test_tracing.py - integrity-sdk/tests/unit/test_openai_integrity.py - integrity-sdk/tests/unit/test_langchain_callback.py - integrity-oracle/backend/src/phi.rs - integrity-oracle/backend/src/handlers.rs - integrity-oracle/backend/migrations/0002_markets_and_judge.sql --- This page replaces the old wiki's `observability-vtl.md`/`phi.md`. It documents the corrected, current design for how PHI/PII/secrets are kept out of oracle storage — the design decided in this rewrite after cross-checking prior Xibalba Solutions spec docs, not the old wiki's speculative "Verifiable Trust Layer" (`GuardrailEngine`, `StateStore` time-travel, a separate `integrity-observability-backend` service, a `ReputationLedger`) — **none of that exists in this repo**; see the note at the bottom. ## Table of contents - [The real gap this design fixes](#the-real-gap-this-design-fixes) - [Redactor — targeted, not blanket masking (built, tested)](#redactor-targeted-not-blanket-masking-built-tested) - [LLM-as-judge evaluation — storage/ingestion plumbing built; rubric [PLANNED]](#llm-as-judge-evaluation-storage-ingestion-plumbing-built-rubric-planned) - [Oracle-side defense in depth — built](#oracle-side-defense-in-depth-built) - [Dual-mode storage — [PLANNED], bigger roadmap item](#dual-mode-storage-planned-bigger-roadmap-item) - [What this page does NOT claim (correcting the old wiki)](#what-this-page-does-not-claim-correcting-the-old-wiki) ## The real gap this design fixes `integrity_sdk/integrations/openai_integrity.py` used to set `GenAIAttributes.PROMPT`/`COMPLETION` (raw prompt/completion text) as OTel span attributes with zero redaction. If a span like that were ever flushed through `client.py`'s telemetry path to the oracle's `POST /v1/telemetry/ingest`, raw agent content — for a healthcare-vertical agent, actual PHI — would land in oracle storage. That is exactly the leak this design exists to close, and it was live in already-shipped code. ## `Redactor` — targeted, not blanket masking (built, tested) `integrity_sdk/security/redactor.py`, alongside the existing `security/attestation.py`/`security/vault.py`. Performs **client-side** masking of specific sensitive-content categories before any prompt/ completion text leaves the agent's process — not a blanket hash-everything strip. An earlier draft of this component specified exactly that blanket rule; it was wrong, because oracle-side LLM-as-judge evaluation (below) needs structurally-intact, if-redacted trace content to actually judge reasoning/tool-use shape — a blanket hash would make that impossible. Regex/heuristic categories matched today: `PRIVATE_KEY` (PEM blocks), `API_KEY` (OpenAI `sk-...`, AWS `AKIA...`, `Bearer ...`), `SSN`, `CREDIT_CARD`, `EMAIL`, `PHONE`, `MRN` (medical record number, labeled convention). Each match is replaced with `[REDACTED:{CATEGORY}]`. **Honest scope, stated in the source itself:** this is real, working masking for common high-confidence cases — not a certified HIPAA Safe Harbor 18-identifier de-identification system, and it will miss PHI with no structural marker (e.g. a patient's name in free text). This is why the oracle-side backstop below exists. Wired into the two named integrations, but **as of 2026-07-15, opt-in, not unconditional**: both `openai_integrity.py`'s `IntegrityOpenAI` and `langchain_callback.py`'s `IntegrityLangChainCallback` accept a `redact_phi: bool` constructor parameter that now **defaults to `False`** (a real, deliberate behavior change from this section's prior claim that redaction ran unconditionally). When `True`, `openai_integrity.py` calls `redact_text(...)` on prompt text, completion text (both streaming and non-streaming), and streamed chunks *before* any span attribute is set or [local-metrology](local-metrology.md) signals are derived from it; `langchain_callback.py` redacts `text_output` and `reasoning_content` the same way. **Any Integrity Health / healthcare-vertical agent MUST pass `redact_phi=True` explicitly** — neither wrapper can infer an agent's `compliance_vertical` on its own, so nothing auto-enables this for a healthcare deployment; leaving it at the default logs a `logger.warning` naming the agent, but nothing currently *enforces* the flag being set correctly (a real, accepted residual risk — see `PRODUCTION_GAPS.md` §3, and [Telemetry Ingestion Pipeline](telemetry-ingestion.md) §2 for the full writeup). `telemetry/tracing.py`'s `trace_run`/`traceable` API is **unaffected by this flag** — it still redacts every string leaf unconditionally, always. Tested in `integrity-sdk/tests/unit/test_redactor.py` (clean text passes through untouched; SSN/email/phone/credit-card/API-key patterns are matched and masked) and, new for the `redact_phi` behavior itself, `tests/unit/test_openai_integrity.py`/`test_langchain_callback.py` (default leaves text raw; `redact_phi=True` redacts prompt/completion/ reasoning text; 13 tests total, neither integration had any coverage before this). **Real gap this page previously overstated, closed 2026-07-11**: this section used to claim redaction was "wired into both instrumentation paths" as if that were the complete picture. It wasn't — `telemetry/ tracing.py`'s `trace_run`/`traceable`/`client.traceable(...)` (the SDK's own documented, *recommended* general-purpose tracing API, not one of the two "instrumentation paths" this page was scoped to) captured a wrapped function's raw arguments and return value with zero redaction. Any consumer decorating their own LLM-calling function with `@client.traceable(...)` — the normal, recommended way to use this SDK — was forwarding raw, unredacted content toward the oracle. Fixed: a new `_redact_value` helper in `tracing.py` recursively applies `redact_text` to every string leaf in `TraceRun.set_outputs`'s value and `_capture_inputs`'s captured arguments, however deeply nested. Tested in the new `integrity-sdk/tests/unit/test_tracing.py` (previously zero dedicated tests existed for this module at all). ## LLM-as-judge evaluation — storage/ingestion plumbing built; rubric `[PLANNED]` `integrity-oracle/backend`'s `judge_evaluations` table now exists (`migrations/0002_markets_and_judge.sql`: `id`, `agent_id` FK'd to `agents`, `run_id`, `judge_model`, `verdict`, `score`, `rationale_summary`, `telemetry_event_id`, `created_at`), and `POST /v1/telemetry/ingest`'s `TelemetryIngestRequest` gained an optional `judge_evaluation` field (`handlers::JudgeEvaluationDto`) that persists into it when present. **Deliberately NOT part of the signed envelope** — the `signable` JSON `ingest_telemetry` builds for `crypto::verify_agent_signature` does not include `judge_evaluation`, so adding one never retroactively breaks an already-signed payload from a client that predates this field; it rides along as an unauthenticated sidecar on an otherwise-authenticated request. Still **`[PLANNED]`**: the actual judge implementation. There is no `client.py` `submit_judge_evaluation(...)` SDK helper, and no judge prompt/rubric anywhere in this repo — "Xibalba Solutions defines" the rubric and it isn't specified yet. This pass built the storage + ingestion plumbing only, exactly the scope asked for; not wired into the [AIS](ais.md) formula in `scoring-core` (that would be its own weights/ formula decision requiring explicit sign-off). ```mermaid flowchart LR Agent["Agent process
(prompt/completion text)"] Redactor["Redactor (SDK, client-side)
SSN / API-key / email / phone / MRN / etc
-- only if redact_phi=True, default False"] Envelope["Signed telemetry envelope
(otel_spans, judge_evaluation)"] PHIrs["oracle phi.rs backstop
(server-side, same categories, ALWAYS runs)"] DB["Postgres
(telemetry_events, judge_evaluations)"] Reject["400 PhiDetected
(before any DB/RPC work)"] Agent -->|redact_phi=True| Redactor --> Envelope Agent -->|redact_phi=False, default| Envelope Envelope -->|POST /v1/telemetry/ingest| PHIrs PHIrs -->|unredacted pattern found| Reject PHIrs -->|clean| DB ``` ## Oracle-side defense in depth — built `POST /v1/telemetry/ingest` now independently rejects (`400`, `AppError::PhiDetected`, real HTTP status — not a silent drop) any payload carrying a recognized *unredacted* raw PHI/PII/secret pattern, via `integrity-oracle/backend/src/phi.rs`. This mirrors (not reinvents) `redactor.py`'s regex categories (`PRIVATE_KEY`, `API_KEY`, `SSN`, `CREDIT_CARD`, `EMAIL`, `PHONE`, `MRN`) closely enough to be a real backstop, not a decorative one — same intent per category, translated to Rust's `regex` crate syntax. Runs first in the handler, before any DB/RPC work, over every JSON **string** leaf in `otel_spans` plus the optional `judge_evaluation` (numeric/structural fields are skipped — the SDK's `Redactor` never touches those either, so scanning them would just false-positive on values nobody was ever going to redact). Verified to NOT re-flag the SDK's own `[REDACTED:{CATEGORY}]` output (a compliant, already-redacted payload must pass). Unit-tested in `phi.rs` itself (every category, the non-reflag case, the numeric/string scan boundary) and exercised at the real HTTP layer in `tests/e2e.rs` (a raw SSN in an otel span → real `400`, before signature verification even runs). The oracle no longer relies solely on the SDK-side `Redactor` for this invariant — belt and suspenders, as designed, not a substitute for it (this is still regex/heuristic, not certified de-identification; a name mentioned in free text with no structural marker still gets through both layers). ## Dual-mode storage — `[PLANNED]`, bigger roadmap item Mode 1 (transparent): full trace storage for non-regulated verticals, prioritizing developer debugging visibility. Mode 2 (Sovereign ZK-mode, for Integrity Health/healthcare and any PHI-adjacent vertical): raw content never leaves local hardware at all — only a hash and a [ZK proof](zkp.md) of correct measurement leave the agent's process. Not built as a toggle; tracked in the root `README.md`'s "Vision & long-term roadmap" section. ## What this page does NOT claim (correcting the old wiki) The old wiki's `observability-vtl.md` described a `Trace Aggregator`, a `Risk Analyzer` (privilege escalation / agentic loop / hallucination detection), a `GuardrailEngine` with pluggable `BaseGuardrail` classes, a `StateStore` supporting agent-state "time travel" (rewind/edit/fork), a `ReputationLedger`, and a standalone `integrity-observability-backend` service. **None of these exist in `integrity-sdk` or `integrity-oracle` today.** Only the `Redactor` and the (not-yet-built) judge-evaluation ingestion path are real/planned per the current design; treat the rest of that old page as unbuilt product ideation, not documentation. Related: [Telemetry Ingestion Pipeline](telemetry-ingestion.md) (the full end-to-end collection→batching→signing→oracle-pipeline writeup this page's `redact_phi` section is one slice of), [local metrology](local-metrology.md), [AIS](ais.md), [ComplianceGate](compliance-gate.md), [integrity-sdk](integrity-sdk.md), [AIS API — Versioned Wire Spec](ais-api-spec.md) (the telemetry-envelope signing fix that made this pipeline actually reach the oracle for the first time is documented there and on [integrity-oracle](integrity-oracle.md)).