Split from the #323 audit (verified @ d1d724f) — needs Andres's call, not a unilateral patch. The same string is encrypted into documents.extracted_text (documents.py:1066-1070, migration 0030) and chunked into course_chunks.chunk_text in the clear (rag_service.py:190), cross-user shared by design (content-addressed ids). Coherent-by-necessity — pgvector similarity can't run over ciphertext — but the encryption boundary is byte-derivably defeated and 0017's encrypted-column list doesn't mention it. Options: searchable-encryption design, chunk-store isolation/scoping, or an explicit accepted-risk ADR. Recommend at minimum an ADR recording whichever stance is chosen.
Split from the #323 audit (verified @ d1d724f) — needs Andres's call, not a unilateral patch. The same string is encrypted into
documents.extracted_text(documents.py:1066-1070, migration 0030) and chunked intocourse_chunks.chunk_textin the clear (rag_service.py:190), cross-user shared by design (content-addressed ids). Coherent-by-necessity — pgvector similarity can't run over ciphertext — but the encryption boundary is byte-derivably defeated and 0017's encrypted-column list doesn't mention it. Options: searchable-encryption design, chunk-store isolation/scoping, or an explicit accepted-risk ADR. Recommend at minimum an ADR recording whichever stance is chosen.