feat(privacy): gate rag_queries.answer persistence behind RAG_PERSIST_ANSWER_TEXT (PIA-3)#531
Conversation
…_ANSWER_TEXT (PIA-3) rag_queries.answer stored the full generated answer text un-redacted — PHI-derived content at rest even though the query itself is hashed. Gate answer-text persistence behind a new RAG_PERSIST_ANSWER_TEXT flag (default off), mirroring the existing RAG_PERSIST_RAW_QUERY_TEXT precedent. - env: new RAG_PERSIST_ANSWER_TEXT (default false) - query-privacy: answerTextForStorage() + answerPrivacyMetadata() helpers - rag.ts insertRagQuery: central chokepoint nulls the answer and records metadata.answer_retained; covers every logRagQuery caller at once - eval-cases route: answer persistence moved onto the dedicated flag so one switch governs answer text everywhere (free-text note stays on raw-query flag) - production-readiness: block RAG_PERSIST_ANSWER_TEXT=true in production - docs: .env.example + privacy-impact-assessment (PIA-3 -> Resolved) The offline eval pipeline reads the in-memory answer (logQuery:false) and never reads the persisted column, so default-off does not affect eval. Tests: answerTextForStorage/answerPrivacyMetadata unit tests; rag_queries insert nulls the answer by default at the real chokepoint; eval-cases decoupling test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Warning Review limit reached
Next review available in: 26 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
Summary
rag_queries.answerwas persisting the full generated answer un-redacted — PHI-derived content at rest even though the query itself is hashed, owner-scoped, and 30-day purged.RAG_PERSIST_ANSWER_TEXTflag (default off), mirroring the existingRAG_PERSIST_RAW_QUERY_TEXTprecedent. With the flag off,answeris written asnulland each row recordsmetadata.answer_retained = false.insertRagQuery) so everylogRagQuerycaller is covered, plus the promoted-eval-case write, so one switch governs generated-answer persistence everywhere.Changes
src/lib/env.ts— newRAG_PERSIST_ANSWER_TEXT(defaultfalse).src/lib/query-privacy.ts— newanswerTextForStorage()+answerPrivacyMetadata()helpers.src/lib/rag.ts—insertRagQuerynullsanswerand foldsanswer_retainedinto metadata by default (covers all fourlogRagQuerycall sites at once).src/app/api/eval-cases/route.ts— itsmetadata.answergate moved from the raw-query flag to the dedicated answer flag (free-textnotestays on the raw-query flag).scripts/production-readiness.ts— blocksRAG_PERSIST_ANSWER_TEXT=truein a production-like environment (mirrors the raw-query guard)..env.example+docs/privacy-impact-assessment.md— documented; PIA-3 marked Resolved.Why default-off is safe (the task's "confirm the eval pipeline's real need")
Traced before shipping: no eval path depends on the persisted answer. All eval scripts call the RAG functions with
logQuery: falseand read the in-memoryanswer.answer;scripts/promote-query-misses.tsselects specific columns, nevermetadata.answer. So persistence-off does not affect eval.rag_response_cache.payload(ephemeral ~5-min cache) is intentionally out of scope — nulling it would break caching; PIA-3 was only about the 30-day log column.Verification
typecheck— cleaneslint(changed files) — cleanprettier --check(changed files) — cleanvitesttargeted:tests/privacy.test.ts,tests/eval-cases-route.test.ts,tests/rag-answer-fallback.test.ts— all greennpm run check:production-readiness— ran; new answer-flag guard is correctly quiet (default off, non-prod). Remaining FAILs are the expected missing-secret / no-.env.localones for this demo-mode worktree, unrelated to this change.npm run eval:rag/eval:quality— not run: this is answer persistence (logging), not synthesis or answer post-processing; generated answer content is byte-identical. These gates also require live OpenAI + Supabase keys (provider-confirmation boundary), so left to a maintainer if desired.Tests added
answerTextForStorage/answerPrivacyMetadataunit tests (default →null; on → pass-through).answerQuestionWithScopeis written torag_queries.answerasnullwithanswer_retained: false.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy) — unchangedenv.ts/query-privacy.tsareserver-only; no client exposureNotes
rag_queries.answerlog column.answer_retainedmetadata flag mirrors the existingraw_query_retained, so each logged row self-documents its retention posture.🤖 Generated with Claude Code