feat: no raw PII/PHI in stored transcripts (secured data §15, QQQ RC.7) #44

Merged
KofTwentyTwo merged 3 commits from feature/transcript-redaction into develop 2026-10-15 00:03:21 +00:00
KofTwentyTwo commented 2026-10-14 16:22:59 +00:00 (Migrated from github.com)

This implements secured-data §15 for assistant transcripts (QRun-IO/qqq docs/SECURED-DATA.md): stored transcripts redact the PII/PHI values the model saw.

DRAFT until QQQ 4.1.0-RC.7. It needs ClassifiedValueTokens and the log redaction hook (qqq#1182), which are merged but not yet released. It is green against a local build of QQQ develop: ./mvnw verify -Dqqq.snapshot.version=4.1.0-RC7PRE-SNAPSHOT runs 383 tests, 1 skipped (Docker).

What is stored now

  • Tool results: in the stored copy, each value of a field classified PII or PHI becomes a ClassifiedValueTokens.forRecordField(table, field, pk, value) token.
  • Everything else the turn persists has every classified value the turn saw replaced by its token:
    • model-written text and persisted text.delta events;
    • presentation parts;
    • tool inputs, such as a filter on a classified value or field;
    • scheduled run summaries;
    • data-audit reasons.
  • Unchanged: what the model receives (as the PHI routing allows) and the live SSE to the user, who is entitled to the value.
  • Logs: a core log redaction hook masks the values seen in each turn.
  • Limitation: a pending write that would carry a classified value is refused rather than stored. The follow-up is an encrypted, short-lived pending payload. Existing transcripts are not migrated.

Canary test

  • CANARY-PHI-<random> and CANARY-PII-<random> markers are planted in the fixture's patient table.
  • The test then exercises query, get, search, presentation, a write proposal and an error path, with logs captured.
  • It fails if any marker, or any substring of it longer than 4 characters, appears in any assistant_* table, the log output or the audits.
  • It also asserts that the model did receive the values in memory, so redaction is shown to happen at storage rather than by withholding data from the model.

🤖 Generated with Claude Code

This implements secured-data §15 for assistant transcripts (QRun-IO/qqq `docs/SECURED-DATA.md`): stored transcripts redact the PII/PHI values the model saw. **DRAFT until QQQ 4.1.0-RC.7.** It needs `ClassifiedValueTokens` and the log redaction hook (qqq#1182), which are merged but not yet released. It is green against a local build of QQQ develop: `./mvnw verify -Dqqq.snapshot.version=4.1.0-RC7PRE-SNAPSHOT` runs 383 tests, 1 skipped (Docker). ### What is stored now - **Tool results:** in the stored copy, each value of a field classified PII or PHI becomes a `ClassifiedValueTokens.forRecordField(table, field, pk, value)` token. - **Everything else the turn persists** has every classified value the turn saw replaced by its token: - model-written text and persisted `text.delta` events; - presentation parts; - tool inputs, such as a filter on a classified value or field; - scheduled run summaries; - data-audit reasons. - **Unchanged:** what the model receives (as the PHI routing allows) and the live SSE to the user, who is entitled to the value. - **Logs:** a core log redaction hook masks the values seen in each turn. - **Limitation:** a pending write that would carry a classified value is **refused** rather than stored. The follow-up is an encrypted, short-lived pending payload. Existing transcripts are not migrated. ### Canary test - `CANARY-PHI-<random>` and `CANARY-PII-<random>` markers are planted in the fixture's patient table. - The test then exercises query, get, search, presentation, a write proposal and an error path, with logs captured. - It fails if any marker, or any substring of it longer than 4 characters, appears in any `assistant_*` table, the log output or the audits. - It also asserts that the model did receive the values in memory, so redaction is shown to happen at storage rather than by withholding data from the model. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.