2026-05-10-x2-pii-emission
X-2 follow-up: data.pii_detected emission point
Date: 2026-05-10 Status: shipped Slug: x2-emission-points-batch-2 Branch: feature/x2-emission-points-batch-2 Strategic plan item: X-2 (continuation of #269)
Summary
Wires data.pii_detected emission in the legacy PII scanner branch of the guardrails middleware. The OCSF projector and EVENT_MAP entries shipped in #269 already handle the event; this PR connects the call site.
The emission carries:
tenantIdfrom the resolved API keyrequestIdfrom the request contextpiiTypes(deduped string list — e.g.["email", "phone"])actionderived fromeffectivePiiMode:block→"blocked",redact→"redacted", otherwise"warned"direction: "inbound"(request-side scan; outbound would be the response-stream guardrail)
Per the X-2 PII hard rule, the event carries only types and the action — no raw prompt text.
Why only this emission, and not anomaly + guardrail in the same PR
anomaly-detection.ts operates on entityType: "sender" | "service" — the "agent" entityType the X-2 plan referenced doesn't exist in the current detector type signature. Wiring behavior.anomaly_spike requires either expanding the entity-type union (out of scope) or emitting from agent-reputation.ts:163 instead (where the agent identity is in scope). That emission point is deferred.
data.guardrail_triggered lives in the V2 pipeline executor, which has a different verdict shape and lifecycle. Wiring it cleanly requires a small refactor to centralize the verdict emission rather than scatter void emit({...}) calls per check. Also deferred.
Files
Modified
src/api/middleware/guardrails.ts(+25 LOC) —data.pii_detectedemission in the legacy PII branch
Verification
pnpm tsgo— exit 0pnpm test:fast— 7755/0 (no regression)pnpm exec oxfmt --check— cleanpnpm exec oxlint --type-aware— 0/0
Lockstep
No public surface change. Ship log only.