feat(coach): store a turn's result tables encrypted beside the message

A new nullable column, coach_messages.results_encrypted, holds the tables
of values a turn read as AES-GCM ciphertext; only their metadata rides the
plaintext provenance. readMessageResults serves them to the owner, each
table whole or withheld with a reason. The provenance restore learns the
steps, method, table metadata, chips, clarification and the forced-answer
marker, entry by entry against the wire schema.

The column is registered for key rotation, carried through both backup
formats (ciphertext in a disaster-recovery file, readable JSON in a
portable one) and goes with its row on conversation delete and wipe.
This commit is contained in:
Marc-André Bombeck committed 2026-09-27 10:36:27 +02:00
1 parent cde7af3cf8
commit a8b3e5d7f4
9 files changed
+714 -1

No files matched your search

+11 -1
View File
@@ -418,7 +418,8 @@ async function main() {
}
// ───── Coach (Bytes columns) ─────
// "encryptedContent" "summaryEncrypted" "titleEncrypted" "factEncrypted"
// "encryptedContent" "resultsEncrypted" "summaryEncrypted" "titleEncrypted"
// "factEncrypted"
results.push(
await rotateBytesColumn(
"CoachMessage",
@@ -426,6 +427,15 @@ async function main() {
prisma.coachMessage,
),
);
// v1.39.4 — the tables of values a turn read. NULL on every turn without a
// table, which `rotateBytesColumn` skips.
results.push(
await rotateBytesColumn(
"CoachMessage",
"resultsEncrypted",
prisma.coachMessage,
),
);
results.push(
await rotateBytesColumn(
"CoachConversation",