Files
amethyst/commons
Claude b3b4fdaf43 feat(marmot): honour disappearing messages
`marmot.group.message-retention.v1` decoded into `MarmotGroupState.retention`
and then nothing read it. In a group with disappearing messages enabled, every
member's copy vanished on schedule except Amethyst's, which kept the plaintext
indefinitely — not a wire incompatibility, the group still worked, but a
privacy divergence from what that group was told it had.

Expiry is pinned per message when it enters the log, never recomputed. That is
the component's rule and it is the easy one to get wrong: a message keeps the
retention of its OWN source epoch, so changing the setting later must not
shorten, extend, or restore the expiry of a message that already exists.
Recomputing from the current setting would let one member retroactively
shorten everyone's history, or resurrect what should already be gone. First
write wins for the same reason — the ratchet rewinds on restart and relays
replay recent kind:445s, so the same message really is persisted twice, and a
second write that re-timed it would let a message postpone its own expiry
every time it was replayed.

The retention itself is read from the `0x8005` component with a fallback to a
legacy group's `0xF2EE` field, because the two profiles express the same
setting in different places and reading only one would silently treat half the
groups as having no expiry.

Expiring deletes rather than hides. This store is the only copy — the ratchet
moved past the ciphertext it came from long ago — so a message that is merely
filtered out of a read is still on disk, and a disappearing message that is
gone from disk but still on screen has not disappeared either. Both stores
rewrite their logs, reads prune first so a restart cannot show something that
fell due while the app was closed, and the front end is told what went so it
can drop those rows from a conversation already open.

Traffic is the clock: a group being read is a group whose expired messages
should already be gone. There is no timer, so a group nobody opens keeps its
messages until someone does — worth knowing, and better than a wakeup that
exists only to delete.

Expiry stays advisory by design, as the component says: the duration is
authenticated but the base is the sender's own `created_at`, so it inherits
the trust already placed in an MLS-authenticated sender and is not a guarantee
against a hostile one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
2026-09-09 23:15:18 +00:00
..