fix(store): survive one corrupt body on bulk read paths #429

Merged
KofTwentyTwo merged 1 commits from fix/bulk-path-corrupt-body into dev 2026-08-15 02:14:37 +00:00
1 Commits
Author SHA1 Message Date
james.maes c484fb048e fix(store): survive one corrupt body on bulk read paths
A single unauthenticatable body ciphertext took down whole operations:
the FTS backfill runs inside migrate(), so one bit-rotted row stopped
the daemon booting at all, and listing, triage and export each failed
outright over one bad row out of fifty.

Single-item get_message stays fail-closed and is unchanged. It names one
message, so there is no other row left to hand back, and a message with
an empty body reads exactly like a genuinely empty one.

Bulk paths now return each row as MessageRead (Ok(email) or
Err(UnreadableBody)) instead of failing the call. The row keeps its slot,
which is what lets keyset cursors and chunk boundaries stay exact, and no
row ever comes back carrying a body the store could not authenticate.
Each failure is logged at warn! naming only the message key.

The daemon reports the omissions on the wire rather than swallowing them:
unreadable_message_ids on ListMessagesResponse and ExportResponse (an
archive outlives the store, so its gap is named), unreadable_count on
TriageProgress, and the CLI prints all three. Triage never evaluates
rules against a body that failed authentication. The FTS backfill skips
the row entirely rather than indexing an empty body, so every subsequent
open re-reports it and a repaired row heals itself.

Refs #265
2026-08-14 21:07:14 -05:00