Consume control kinds this build has never heard of (§6.10)

`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 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-29 11:07:32 -04:00
co-authored by Claude Opus 4.8
parent cc8f4ca1d0
commit c9e386ec0d
2 changed files with 83 additions and 1 deletions
@@ -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 {
@@ -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"}""")