Document DLP pipeline — operator guide
When DLP document scanning is enabled for a workspace, every uploaded document passes through an async pg-boss scan after the ingest completes:
When DLP document scanning is enabled for a workspace, every uploaded document passes through an async pg-boss scan after the ingest completes:
An optional Enterprise feature enableable per workspace (workspaces.dlpDocumentScanEnabled, default off; enabling requires the eval gate to pass). Source:docs/DLP_DOCUMENT_PIPELINE.md(Phase 192, DLP-01..06 — v0.28.1). Provenance rows:FACT-CENSUS.mdF-DLP-1, F-DLP-2, F-AUDIT-1.
- Detect — a 3-tier pipeline: admin-configurable regex patterns (
dlpPatternService), checksum validation for the fixed-format Italian identifiers (Codice Fiscale incl. omocodia, P.IVA, IBAN —dlpChecksum), and LLM contextual NER for names/addresses (the existing Ollama client). - Mask — detected entities become document-wide numbered placeholders (
[PERSON_1],[GOV_ID_2], …). Masking is idempotent (mask∘mask = mask) — the placeholder syntax can never re-match. - Re-embed masked —
document_chunks.chunkTextis rewritten with the masked text, the FTSsearchVector/searchVectorMultirecomputed, and vectors re-embedded via the collector's/api/ingest/reembedendpoint. No unredacted vector remains searchable. - Encrypt originals — the original text + every entity value are stored AES-256-GCM encrypted (
encryptionService, the ENCRYPTION_KEY contract). Masking is one-way: the encrypted originals are the only clean copy.
Admin controls
The per-workspace toggle (DLP-01)
Settings → Workspaces → the DLP document scan switch (the workspaces.dlpDocumentScanEnabled column, default off).
- The toggle is disabled with a gate-blocked helper until the eval gate (below) passes — DISABLING is never blocked.
- The state flips optimistically and reverts on a save error.
The eval gate (DLP-05)
The detection-quality eval must pass before enablement:
- POST /api/system/dlp/eval/run (admin) — runs the repeatable runner over the committed Italian-PII fixture corpus (
packages/server/src/__tests__/fixtures/dlp-eval/). Persists the result to the SystemConfig keyDLP_EVAL_LAST_RUN(not admin-editable). - GET /api/system/dlp/eval/result (admin) — reads the last run. Never-run returns
{ passed: false, noRun: true }— never a silent pass. - The gate metric: zero checksum-suppressed false positives on the GOV_ID/FINANCIAL classes. PERSON/ADDRESS recall is documented-only (nerMode marks
stubvslive).
The legacy corpus backfill (DLP-06)
POST /api/system/dlp/backfill (admin, AlertDialog-confirmed in the UI):
- Eval-gated: refuses with
409 { error, gate: "eval-not-passed" }when the last eval result is absent or failed. - Idempotent + resumable: the
dlpScannedAtmarker — a re-run enqueues only the documents lacking the marker. - Rate-limited: queue concurrency 1 (below the live-scan queue).
- Convergence is pinned by the integration twin (real Postgres): masked chunks persisted, tsvectors recomputed, the reembed payload byte-equal to the stored masked text, the zero-remaining-PII audit query.
The four pattern classes (the European/Italian identifiers)
| Class | What it detects |
|---|---|
| GOV_ID | Codice Fiscale (incl. the omocodia validation) |
| FINANCIAL | P.IVA, IBAN (with checksum validation) |
| PERSON | person names (LLM contextual NER) |
| ADDRESS | addresses (LLM contextual NER) |
The checksum classes (GOV_ID/FINANCIAL) are deterministic; the contextual classes (PERSON/ADDRESS) pass through the LLM NER — their recall is the eval result's documented value.
Chat re-composition (DLP-03)
Masked placeholders reach the LLM; at stream end, exactly once, the server re-composes placeholders in the terminal message for users holding dlp:unmask on the citing document's workspace. The additive content field on the done SSE payload carries the re-composed text; widget visitors NEVER receive re-composed text (server-enforced; for the UI it is only a control-hiding matter).
Preview unmask (DLP-04)
GET /api/documents/:documentId/text serves masked text by default. ?unmask=true re-composes only through the gate ladder: a valid unmask param → the workspace toggle on → resolveWorkspaceRole + the dlp:unmask permission. Every miss arm returns masked text (200) — the endpoint is never an entitlement oracle. The UI toggle renders only for permitted users on documents with entities (DOM-absence), is per-view, and is never persisted.
The audit matrix (SC-4)
Every read path is pinned by a behavioral test (18 rows: the text view, the OCR preview, synthesis, citations, widget RAG, MCP tools, FTS, exports, the KB copy-from-doc, the admin re-embed). Widget/citations/MCP carry hard-never negative pins (masked, never re-composed).
This guide covers the operator surface for the document DLP pipeline; the engineering deep dive lives in the twin post:
Provenance
| Section | Source |
|---|---|
| The 4-phase pipeline / detect / mask / reembed / encrypt | docs/DLP_DOCUMENT_PIPELINE.md (Overview — Phase 192 DLP-01..06) — F-DLP-1 |
| Admin controls / toggle / eval / backfill | docs/DLP_DOCUMENT_PIPELINE.md (§ Admin controls) |
| Chat re-composition / preview unmask / audit | docs/DLP_DOCUMENT_PIPELINE.md (§ DLP-03/04, SC-4) — F-AUDIT-1 for the audit class |
| The "10 PII" count | NOT cited: the count is untraceable in this pass (F-DLP-2 = UNTRACED-IN-THIS-PASS) — this page names the actual classes, never an untraced number |
---
Operator reference — Simmetric Chat v0.28.1 (docs refreshed 2026-10-02, census SHA `aca5b8ff`).