Apply inbound edits and pins instead of filing them as messages #68

Merged
paulhitt merged 3 commits from feat/apply-inbound-edits-and-pins into main 2026-09-04 20:49:23 +00:00
paulhitt commented 2026-09-04 17:53:15 +00:00 (Migrated from gitlab.com)

This was an interop bug, not a missing feature. The engine has parsed
editOf, pins and fwd for as long as they have existed, and it exposes
isRenderable — "does this belong in a thread" — as the single answer.
Session's inbound loop never consulted it.

So three things arrived from the iPhone app and were filed as ordinary bubbles.
An edit carries its new wording in text, so it drew as a DUPLICATE beside the
typo it was meant to replace — a correction that made the thread worse than the
mistake did. A pin carries only an id, so it drew as a blank bubble. And a
forward was drawn as the sender's own words.

Worst of the three is the one nobody had hit yet: a control envelope from a
NEWER peer carries ctl and a kind this build has never heard of, and would
have drawn a bubble full of JSON in somebody's conversation. That is precisely
the failure isControl was written to prevent, defeated by nobody calling it —
and it would have shipped to every older Android install the first time a
control kind was added on iOS. The guard is isRenderable rather than a list of
kinds this build knows, because an allowlist is only correct until the other
platform adds one.

What is applied now:

  • an edit rewrites the message it names and stamps edited, so a bubble can
    say so — silently replacing the text is worse than the typo, because the
    reader cannot tell a correction from their own misremembering;
  • a pin is stored per thread, sealed in the vault like the transcript, as an ID
    and never a copy: a copy goes stale against an edit and outlives a deletion;
  • fwd reaches the transcript so second-hand words are not drawn as the
    sender's own — THAT it was forwarded, never from whom.

An inbound edit is applied only when it comes from the person whose message
it is. Without that check any contact sharing a session could rewrite anybody's
words in the reader's transcript — a better attack than sending a message of
their own, because the words would carry somebody else's name.

Clearing a thread clears its pin. A banner pointing at a message that was
just cleared reads as the clear not having worked.

fwd is read as both a boolean and a number, for the same reason isControl
accepts both: org.json's optBoolean does not coerce a number, and that is
how this class of bug shipped once already.

9 unit tests. App builds, every JVM suite green. The three PARITY rows stay :
the inbound half is done, originating each still needs the UI.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

This was an interop bug, not a missing feature. The engine has parsed `editOf`, `pins` and `fwd` for as long as they have existed, and it exposes `isRenderable` — "does this belong in a thread" — as the single answer. `Session`'s inbound loop never consulted it. So three things arrived from the iPhone app and were filed as ordinary bubbles. An edit carries its new wording in `text`, so it drew as a DUPLICATE beside the typo it was meant to replace — a correction that made the thread worse than the mistake did. A pin carries only an id, so it drew as a blank bubble. And a forward was drawn as the sender's own words. Worst of the three is the one nobody had hit yet: a control envelope from a NEWER peer carries `ctl` and a kind this build has never heard of, and would have drawn a bubble full of JSON in somebody's conversation. That is precisely the failure `isControl` was written to prevent, defeated by nobody calling it — and it would have shipped to every older Android install the first time a control kind was added on iOS. The guard is `isRenderable` rather than a list of kinds this build knows, because an allowlist is only correct until the other platform adds one. What is applied now: - an edit rewrites the message it names and stamps `edited`, so a bubble can say so — silently replacing the text is worse than the typo, because the reader cannot tell a correction from their own misremembering; - a pin is stored per thread, sealed in the vault like the transcript, as an ID and never a copy: a copy goes stale against an edit and outlives a deletion; - `fwd` reaches the transcript so second-hand words are not drawn as the sender's own — THAT it was forwarded, never from whom. An inbound edit is applied only when it comes from the person whose message it is. Without that check any contact sharing a session could rewrite anybody's words in the reader's transcript — a better attack than sending a message of their own, because the words would carry somebody else's name. Clearing a thread clears its pin. A banner pointing at a message that was just cleared reads as the clear not having worked. `fwd` is read as both a boolean and a number, for the same reason `isControl` accepts both: `org.json`'s `optBoolean` does not coerce a number, and that is how this class of bug shipped once already. 9 unit tests. App builds, every JVM suite green. The three PARITY rows stay : the inbound half is done, originating each still needs the UI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
paulhitt commented 2026-09-04 17:53:15 +00:00 (Migrated from gitlab.com)

assigned to @paulhitt

assigned to @paulhitt
paulhitt (Migrated from gitlab.com) changed target branch from feat/pin-the-relay-identity to main 2026-09-04 20:49:21 +00:00
paulhitt commented 2026-09-04 20:49:23 +00:00 (Migrated from gitlab.com)

mentioned in commit 8466fc1b57

mentioned in commit 8466fc1b5773df4d85598e2f0e353689be595a1a
paulhitt (Migrated from gitlab.com) merged commit 8466fc1b57 into main 2026-09-04 20:49:23 +00:00
paulhitt commented 2026-09-09 19:01:34 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
Sign in to join this conversation.