Codex P1: the manager-path withheld claim landed only in
PrivateChatManager.sentReadReceipts, while the lifecycle read pass
dedups against ChatViewModel's persisted set — enabling receipts
before the next lifecycle pass could send a receipt for a message
read while the setting was off. The withheld branch now records into
the owner's persisted set too (markReceiptHandled, wired in the
bootstrapper); test strengthened to require both sets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read receipts were unconditional: on chat open and on didBecomeActive,
twice. A receipt is a presence oracle — it says the person is awake,
holding their phone, and opened the app at that exact moment, which is
precisely the metadata the stated threat model says someone should be
able to withhold. Every mainstream messenger ships this switch.
- ReadReceiptSettings (default ON = existing behavior), toggle in the
settings privacy section, reset on panic wipe.
- All four origination paths gated: mesh/nostr routing, direct mesh
receipts, geohash receipts, and PrivateChatManager's read pass.
Withheld receipts are still recorded locally as sent, so re-enabling
the setting never fires a retroactive burst disclosing past reads.
- Only outbound receipts are affected; receipts from others display.
- 2 strings x 30 locales; tests pin the default, the reset, and that
nothing reaches the transport while off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:17:55 +02:00
8 changed files with 526 additions and 1 deletions