feat(insights): seal the cached AI insights payload at rest

users.insights_cached_text held the model's briefing, readings and
recommendations about the account in the clear. Migration 0411 adds
insights_cached_text_encrypted (AES-256-GCM, binary layout, purpose-bound
label) and a partial discovery index; the free-text encryption backfill
seals the existing rows one at a time, fail-closed, carrying updatedAt and
guarded on the value it read. Every reader, writer and clear goes through
cached-text-cipher.ts, and a guard keeps the column names there. The
column joins the encrypted-column registry and the key-rotation script,
stays out of backups as operational, and is reset by the data wipe.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 07:30:00 +02:00
1 parent eae2138f3e
commit 00dfc5a0c0
37 files changed
+709 -64

No files matched your search

+9
View File
@@ -863,6 +863,15 @@ async function main() {
} as unknown as CorpusClient),
);
// ───── v1.43.7 cached AI insights payload (Bytes, binary2, batched) ─────
// The model's briefing and readings on the user row. NULL on a row without
// a cache, or one the free-text backfill has not sealed yet; both skipped.
results.push(
await rotateRegistryColumn("User", "insightsCachedTextEncrypted", {
user: prisma.user,
} as unknown as CorpusClient),
);
// ───── v1.39.3 custom-metric reading note (Bytes column) ─────
// NULL on a reading without a note, and on a row the free-text backfill has
// not reached yet; `rotateBytesColumn` skips both.