Disappearing messages, symmetric and from send time (§6.11)
The relay has had per-conversation lifetimes since the port and **no client could set one** — its own comment says so. For E2E conversations the server timer would not have helped anyway: it expires rows in the legacy plaintext table, not the envelopes these apps actually exchange. So this is client-side, which is also the only place it can be honest. **Symmetric.** Either party sets the timer; the other is told inside the ratchet and adopts it. A timer only the setter honoured would be worse than none — they would believe their words were gone from a phone still holding them. **Inside the ratchet**, so the relay never learns that a conversation is timed, let alone for how long. It rides as ordinary control traffic carrying the `ctl` flag from §6.10, which is what makes it safe to add at all: without that, this envelope would surface in every older install as an "[unsupported attachment]" bubble. **Expiry runs on read**, in `Vault.history`. A sweep on a timer leaves expired messages on disk between runs, and a disappearing message still sitting in the vault is a claim rather than a feature. Reading is the one path every caller shares, so nothing can see — or keep — an expired message. ⚠️ **Ages come from this device's own timestamp**, not one the peer supplies. "From send time" in practice means when this device sent or received it, which differs by seconds and cannot be manipulated: a peer who chose the clock could backdate a message so it vanished from your phone immediately, or forward-date one so it never expired. ⚠️ **A message with no timestamp is kept.** Those predate the field, and an unknown age is not evidence of an old one — deleting somebody's history on a guess cannot be undone. The panel sits on the thread rather than in a menu, because "fewer taps to turn it on" and "clearer per-conversation state" are the same problem: a timer nobody can see is one nobody trusts. Presets only — arbitrary durations are the ones nobody can reason about later. engine 155, client 94, app 32. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c9e386ec0d
commit
f9f063a211
@@ -178,6 +178,45 @@ class ContentTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disappearing-message timers (§6.11).
|
||||
*
|
||||
* 🔴 The timer travels **inside the ratchet**. Sent any other way the relay
|
||||
* would learn that a conversation is timed and for how long — metadata this
|
||||
* feature exists to avoid producing, on a product whose claim is that the
|
||||
* server knows as little as possible.
|
||||
*/
|
||||
@Test
|
||||
fun `a disappearing timer round-trips and is control traffic`() {
|
||||
val parsed = Content.parse(Content.disappearing(86_400))
|
||||
assertEquals(Content.Kind.DISAPPEAR, parsed.kind)
|
||||
assertEquals(86_400, Content.disappearSeconds(parsed))
|
||||
assertTrue(parsed.isControl, "the timer is never shown as a message")
|
||||
assertFalse(parsed.isRenderable)
|
||||
}
|
||||
|
||||
/** Zero is how the timer is turned off, and must survive as zero. */
|
||||
@Test
|
||||
fun `turning the timer off round-trips as zero`() {
|
||||
val parsed = Content.parse(Content.disappearing(0))
|
||||
assertEquals(0, Content.disappearSeconds(parsed))
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚠️ `fnc` must stay the first key. The marker is a prefix test, so a
|
||||
* reordered top level makes the envelope decode as a plain text message —
|
||||
* raw JSON in somebody's conversation.
|
||||
*/
|
||||
@Test
|
||||
fun `the timer envelope starts with the marker`() {
|
||||
assertTrue(Content.disappearing(60).startsWith("""{"fnc":1,"""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `asking a non-timer envelope for its timer gives nothing`() {
|
||||
assertNull(Content.disappearSeconds(Content.parse(Content.text("hello"))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown attachment kind shows a label rather than disappearing`() {
|
||||
val parsed = Content.parse("""{"fnc":1,"kind":"hologram"}""")
|
||||
|
||||
Reference in New Issue
Block a user