From c9e386ec0d2e3e749865d6331c2aae5d37f1f865 Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Sat, 29 Aug 2026 11:07:32 -0400 Subject: [PATCH] =?UTF-8?q?Consume=20control=20kinds=20this=20build=20has?= =?UTF-8?q?=20never=20heard=20of=20(=C2=A76.10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `isRenderable` asked `kind !in Kind.CONTROL` — an allowlist of the four control kinds that existed when it was written. A control kind from a newer peer is in no set here, so it fell through and rendered as an "[unsupported attachment]" bubble: a message the user can neither read nor act on, for something addressed to a layer rather than a person. Every control kind added from here would have shipped that to every older install. `ctl: 1` is the flag that stops it; `isControl` trusts the flag as well as the list, since the four that predate it do not carry it and never will. ⚠️ `needsNewerClient` is deliberately NOT part of `isRenderable`. Unknown *content* already parses to a placeholder and is shown, which is right — the first cut of this change suppressed it and an existing test caught that immediately. Dropping a real message silently is the one failure a messenger cannot make, so the flag decides rather than the unknown-ness. engine 144, client 90, app 32. Co-Authored-By: Claude Opus 4.8 --- .../main/kotlin/io/flamenet/e2e/Content.kt | 43 ++++++++++++++++++- .../kotlin/io/flamenet/e2e/ContentTest.kt | 41 ++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/io/flamenet/e2e/Content.kt b/engine/src/main/kotlin/io/flamenet/e2e/Content.kt index 215f292..b2d1f1f 100644 --- a/engine/src/main/kotlin/io/flamenet/e2e/Content.kt +++ b/engine/src/main/kotlin/io/flamenet/e2e/Content.kt @@ -127,7 +127,35 @@ data class Content( * and control messages are consumed silently. Everything else is a message. */ val isRenderable: Boolean - get() = reaction == null && call == null && kind !in Kind.CONTROL + get() = reaction == null && call == null && !isControl + + /** + * Control traffic: consumed by a layer, never shown. + * + * 🔴 **An allowlist of known kinds was not enough.** `kind !in CONTROL` is + * only correct for kinds this build has heard of; a control message from a + * NEWER peer has a kind that is in no set here, so it fell through and was + * rendered — a bubble full of JSON in somebody's conversation. Every future + * control kind would have shipped that bug to every older install. + * + * So new control kinds carry `"ctl":1`, and this trusts that flag as well + * as the list. The list stays because the kinds that predate the flag do + * not carry it and never will. + */ + val isControl: Boolean + get() = kind in Kind.CONTROL || raw?.optBoolean("ctl", false) == true + + /** + * A kind this build does not know, which is not control traffic. + * + * ⚠️ Deliberately **not** part of [isRenderable]. Unknown content already + * parses to a `[unsupported attachment]` placeholder and is shown, which is + * right: a peer sent the user something real, and silently dropping a + * message is the one failure a messenger cannot make. This exists so the UI + * can say something better than that placeholder if it wants to. + */ + val needsNewerClient: Boolean + get() = kind !in Kind.KNOWN && !isControl object Kind { const val TEXT = "text" @@ -169,6 +197,19 @@ data class Content( * chat. */ val CONTROL = setOf(DELIVERY_KEY, KEY_LOG_HEAD, SENT_COPY, AVATAR) + + /** + * Every kind this build can act on. + * + * ⚠️ Anything outside this set came from a newer peer. It is either + * control traffic (swallowed, via the `ctl` flag) or content we cannot + * draw (shown as a placeholder) — never raw JSON in a bubble. + */ + val KNOWN = CONTROL + setOf( + TEXT, REACT, STICKER, + Attachment.IMAGE, Attachment.AUDIO, Attachment.STORY, + CallSignal.OFFER, CallSignal.ANSWER, CallSignal.END, + ) } companion object { diff --git a/engine/src/test/kotlin/io/flamenet/e2e/ContentTest.kt b/engine/src/test/kotlin/io/flamenet/e2e/ContentTest.kt index 82363f7..25900ae 100644 --- a/engine/src/test/kotlin/io/flamenet/e2e/ContentTest.kt +++ b/engine/src/test/kotlin/io/flamenet/e2e/ContentTest.kt @@ -137,6 +137,47 @@ class ContentTest { assertEquals("to everyone", parsed.text) } + /** + * 🔴 Forward compatibility for control traffic. + * + * Control messages ride the same channel as chat and are recognised by + * NAME — an allowlist of kinds this build knows. A control kind from a + * newer peer is in no such set, so it fell through and became a + * `[unsupported attachment]` bubble: a message the user can neither read + * nor act on, for something never meant for them to see. + * + * `ctl: 1` is what lets every kind added from here on stay quiet. + */ + @Test + fun `a flagged unknown control kind is consumed, not rendered`() { + val parsed = Content.parse("""{"fnc":1,"kind":"something-new","ctl":true}""") + assertTrue(parsed.isControl, "a ctl-flagged envelope is control traffic") + assertFalse(parsed.isRenderable, "and must never reach the thread") + } + + /** + * ⚠️ The opposite has to stay true. Unknown *content* is still shown, with + * a placeholder — silently dropping a real message is the one failure a + * messenger cannot make, so the flag is what decides, not the unknown-ness. + */ + @Test + fun `an unknown kind without the flag is still shown`() { + val parsed = Content.parse("""{"fnc":1,"kind":"hologram"}""") + assertTrue(parsed.needsNewerClient) + assertFalse(parsed.isControl) + assertTrue(parsed.isRenderable) + } + + /** Kinds that predate the flag keep working without carrying it. */ + @Test + fun `known control kinds are still control without the flag`() { + for (kind in listOf("delivery_key", "kt_head", "sent", "avatar")) { + val parsed = Content.parse("""{"fnc":1,"kind":"$kind"}""") + assertTrue(parsed.isControl, "$kind must stay control traffic") + assertFalse(parsed.isRenderable, "$kind must not render") + } + } + @Test fun `an unknown attachment kind shows a label rather than disappearing`() { val parsed = Content.parse("""{"fnc":1,"kind":"hologram"}""")