From 963d72ed9f2bdebfe117e7a3a712f7e8d9a21278 Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Wed, 2 Sep 2026 13:49:19 +0800 Subject: [PATCH 1/4] spec: v0.6.0 terminal flag; add FMSG-005 Reactions standard Specification v0.6.0: - Flag bit 6 "terminal": a terminal message is a leaf of its thread. A reply to, or add-to batch of, a terminal message is rejected with code 1 by the Receiving Host, and a Sending Host must not transmit one. Add-to messages carrying the bit are rejected on header validation alone since they duplicate the original's flags. - Reserved flag bits (message bit 7, attachment bits 2-7) set on the wire are now rejected with code 1, so a host that predates a new bit fails consistently rather than accepting a message it does not understand. FMSG-005 Reactions: a single-emoji reaction is an ordinary message with has pid, no reply and terminal set, type application/vnd.fmsg.reaction, data one RGI_Emoji or empty to clear, sent to every participant of the subject. Latest by time wins per reactor per subject. Hosts need not understand reactions to carry them; the terminal flag is what stops anything being built on top of one. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MW4FXDGacoWXeKPU5cQTHS --- SPEC.md | 14 +- SPECIFICATION.md | 35 ++++- STANDARDS.md | 1 + mkdocs.yml | 1 + standards/fmsg-005-reactions.md | 268 ++++++++++++++++++++++++++++++++ 5 files changed, 309 insertions(+), 10 deletions(-) create mode 100644 standards/fmsg-005-reactions.md diff --git a/SPEC.md b/SPEC.md index 695e6b3..d35fcb9 100644 --- a/SPEC.md +++ b/SPEC.md @@ -52,7 +52,8 @@ The hash MUST be computed over the full message bytes: message header fields exa | 3 | important | Sender flags message as important. | | 4 | no reply | Sender will discard any reply. | | 5 | zlib-deflate | Message data compressed with zlib/deflate (RFC 1950/1951); _expanded size_ field present. | -| 6–7 | reserved | Must be 0. | +| 6 | terminal | Message is a leaf: no message may reference it via _pid_. A reply to, or add-to of, a terminal message → code 1. Enforced by hosts, unlike advisory _no reply_. | +| 7 | reserved | MUST be 0; set → code 1. | ## 4. Common Media Types @@ -101,7 +102,7 @@ Each attachment header, in order: | Field | Type | Notes | |-------|------|-------| -| flags | uint8 | Bit 0 = common type (same lookup as §4). Bit 1 = zlib-deflate. Bits 2–7 reserved. | +| flags | uint8 | Bit 0 = common type (same lookup as §4). Bit 1 = zlib-deflate. Bits 2–7 reserved, MUST be 0; set → code 1. | | type | uint8 + [ASCII string] | Same encoding rule as message type, using this attachment's own common type flag. | | filename | uint8 length + UTF-8 | < 256 bytes. Unicode letters/numbers, plus `-` `_` ` ` `.` non-consecutively, not at start/end. Unique per message (case-insensitive). | | size | uint32 | Byte length of this attachment's data on the wire (after compression, if zlib-deflate set). | @@ -175,7 +176,7 @@ One message per connection. Two TCP connections used: Connection 1 (message tran ### 10.2 Sending (Host A perspective) -Host A delivers iff _from_ or _add to from_ belongs to Host A's domain. +Host A delivers iff _from_ or _add to from_ belongs to Host A's domain. Host A MUST NOT send a message whose _pid_ references a message it holds with _terminal_ set. When _has add to_ is NOT set: perform the steps below for each unique recipient domain. @@ -207,6 +208,8 @@ When _has add to_ IS set: perform the steps below for each unique participant do - If _has add to_ not set: ≥ 1 recipient in _to_ belongs to Host B's domain. If _has add to_ set: ≥ 1 participant (_from_, _to_, _add to from_ or _add to_) belongs to Host B's domain. - Common type IDs (message and attachment) are mapped. - _expanded size_ fields are present iff the corresponding zlib-deflate flag is set. + - If _has add to_: _terminal_ is NOT set (an add-to copies the original's flags, so _terminal_ means the original is terminal and cannot be referenced). + - No reserved flag bit is set (message bit 7, attachment bits 2–7). 4. DNS-verify sender IP: resolve `fmsg.`, check Connection 1 source IP is in result set. Fail → TERMINATE. 5. If _size_ + attachment sizes > MAX_SIZE, or total expanded size > MAX_EXPANDED_SIZE → respond code 4, close. Total expanded size uses _expanded size_ for compressed parts and _size_ for uncompressed parts. 6. Compute DELTA = now − _time_: @@ -218,10 +221,11 @@ When _has add to_ IS set: perform the steps below for each unique participant do - Verify parent stored (§11). Not found → respond code 6, close. - Parent time − MAX_TIME_SKEW must be before incoming time. Fail → respond code 9, close. - _from_ must be a participant of the parent. Fail → respond code 1, close. + - Parent must not be terminal. Fail → respond code 1, close. - **add-to set** (adding recipients): - pid MUST also be set. Fail → respond code 1, close. - Check if parent stored (§11): - - **Stored**: check time travel (code 9 if fail). + - **Stored**: parent must not be terminal (code 1 if fail); check time travel (code 9 if fail). - **Not stored**: if ≥ 1 recipient in _to_ or _add to_ belongs to Host B's domain, treat as full message delivery. Otherwise (Host B hosts only non-recipient participants) respond code 6 (parent not found), close. 8. Optionally issue a CHALLENGE on Connection 2 (see §10.5). @@ -289,7 +293,7 @@ An add-to message is a duplicate of the original message with these differences: An add-to message MUST be sent to every participant domain per §10.2, so all participants of the message being added to — including the original sender, when not themselves the _add to from_ — learn of the added recipients, not only the domains hosting the new recipients. This is required because a subsequent reply may reference this add-to message via _pid_, and a host can only accept a reply whose parent it holds. -Add-to batches do not chain: recipients are always added to the original message; an add-to message's _pid_ MUST NOT reference another add-to message. A message therefore has 0 or more add-to batches, each a sibling branch under the original — the thread evolves as a tree. +Add-to batches do not chain: recipients are always added to the original message; an add-to message's _pid_ MUST NOT reference another add-to message. An add-to message MUST NOT reference a terminal message (§3). A message therefore has 0 or more add-to batches, each a sibling branch under the original — the thread evolves as a tree. A recipient added by a batch and not already in _to_ is a participant of that batch message only, not of the original: their replies MUST reference the batch message via _pid_ (referencing the original would fail the participant check, §10.3 step 7) and extend the batch's branch. An address in both _to_ and _add to_ was already a participant of the original and may reply on either branch. diff --git a/SPECIFICATION.md b/SPECIFICATION.md index 500fe07..6cffe43 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -12,6 +12,7 @@ | v0.4.0 | 2026-08-02 | Mark Mennell | Add-to messages delivered to all participant domains; notification-only delivery completes at code 11 | | v0.4.1 | 2026-08-03 | Mark Mennell | Hosts must retain stored messages in full, including complete recipient lists | | v0.5.0 | 2026-08-10 | Mark Mennell | Clarified: stored messages include those the host sent; exactly one header-response code is sent; _add to_ may overlap _to_, with one response code per recipient entry; add-to copies omit _topic_; add-to batches are sibling branches whose added recipients reply to the batch message; batch identity is the batch message hash. Changed: an unsupported version TERMINATES rather than responding, retiring code 2 (numbering unchanged) | +| v0.6.0 | 2026-09-02 | Mark Mennell | Added: _terminal_ flag (bit 6) — no message may reference a terminal message via _pid_; hosts reject a reply to, or add-to batch of, a terminal message with code 1. Changed: reserved flag bits set on the wire (message bit 7, attachment bits 2–7) are rejected with code 1 so future bits degrade consistently | ## Contents @@ -24,6 +25,7 @@ - [Message](#message) - [Notes on Message Definition](#notes-on-message-definition) - [Notes on Adding Recipients](#notes-on-adding-recipients) + - [Notes on Terminal Messages](#notes-on-terminal-messages) - [Notes on Time](#notes-on-time) - [Flags](#flags) - [Common Media Types](#common-media-types) @@ -91,6 +93,8 @@ _"recipients"_ the addresses in a message's _to_ and _add to_ fields _"sender"_ the address in a message's _from_ field when _has add to_ not set; otherwise the address in the _add to from_ field. +_"terminal message"_ a message with the _terminal_ flag bit set. No message may reference it via _pid_, see [Notes on Terminal Messages](#notes-on-terminal-messages). + _"thread"_ is a linked hierarchy of messages where messages relate to previous messages using the _pid_ field _"UTF-8"_ is for the unicode standard: Unicode Transformation Format – 8-bit. @@ -151,6 +155,7 @@ In programmer friendly JSON a message could look like (once decoded from the bin "version": 1, "important": false, "noreply": false, + "terminal": false, "pid": null, "from": "@user@example.com", "to": [ @@ -198,6 +203,7 @@ On the wire messages are encoded thus: * Square brackets "[ ]" indicate fields or part thereof may not exist on a message. Where the brackets surround the name, e.g. _pid_, the whole field may not be present (which in the case of pid is only valid if the message is the first in a thread). Where they surround part of the type, that part may not be present, e.g. list of attachment headers will not be present if uint8 prefix is 0. * _topic_ only exists on the first message in a thread, i.e. on a message with no _pid_. This makes _topic_ immutable because it cannot be changed by subsequent replies. (Presentations of message threads MAY use a local mutable field for display purposes). * It is not possible to accept a message _from_ an address that wasn't a participant in the message referenced by _pid_ following the [Protocol Steps](#protocol-steps). +* It is not possible to accept a message whose _pid_ references a message with the _terminal_ flag bit set, see [Notes on Terminal Messages](#notes-on-terminal-messages). ### Notes on Adding Recipients @@ -218,6 +224,19 @@ A recipient added in a batch who was not already in the original's _to_ is a par A batch is identified by its message hash (see [Computing Message Hash](#computing-message-hash)), which covers _time_. Re-issuing an add-to with the same _add to_ addresses at a new _time_ is a new, distinct batch — a new sibling branch — not a duplicate. +An add-to message MUST NOT reference a terminal message, see [Notes on Terminal Messages](#notes-on-terminal-messages). + + +### Notes on Terminal Messages + +A message with the _terminal_ flag bit set is a leaf of its thread: nobody can reply to it and recipients cannot be added to it, because no subsequent message may reference it via _pid_. Receiving Hosts enforce this per [Protocol Steps](#protocol-steps) and Sending Hosts MUST NOT transmit a message that violates it, so every host holding a thread agrees on its shape — a terminal message never gains children on one host that another host rejected. + +_terminal_ differs from _no reply_. _no reply_ is advisory: the sender states it will discard replies, but a reply is still a valid message that hosts accept and other participants see. _terminal_ is enforced: a reply is rejected by every host before it is stored. The two MAY be combined. + +_terminal_ MAY be set on any message, including the first in a thread, and applies only to the message it is set on; it does not affect the message's parent or siblings. Because an add-to message is an exact duplicate of the original apart from the fields listed in [Notes on Adding Recipients](#notes-on-adding-recipients), an add-to message carrying the _terminal_ flag bit necessarily references a terminal original and is therefore invalid. + +This flag exists so standards can define kinds of message that MUST NOT be built upon — for example a reaction to a message — without every host needing to understand the standard: a host enforces _terminal_ without knowing why it was set. A host that predates this flag treats it as a reserved bit and rejects the message (see [Flags](#flags)), which is preferable to accepting it as an ordinary reply that could then be replied to. + ### Notes on Time @@ -236,8 +255,8 @@ fmsg includes some time checking and controls, rejecting messages too far in fut | 3 | important | Sender indicates this message is IMPORTANT! | | 4 | no reply | Sender indicates any reply will be discarded. | | 5 | zlib-deflate | Message data is compressed using the zlib structure (defined in RFC 1950), with the deflate compression algorithm (defined in RFC 1951). | -| 6 | TBD | Unused, reserved for future use | -| 7 | TBD | Unused, reserved for future use | +| 6 | terminal | This message is a leaf: no subsequent message may reference it via _pid_. A Receiving Host rejects any reply to, or add-to batch of, a terminal message with REJECT code 1 (invalid). See [Notes on Terminal Messages](#notes-on-terminal-messages). | +| 7 | reserved | Unused, reserved for future use. MUST be 0; a Receiving Host MUST respond REJECT code 1 (invalid) if set. | #### Common Media Types @@ -337,7 +356,7 @@ Each attachment header consists of flags, type, filename, size, and conditional |----------:|-------------|------------------------------------------------------------------------------------------------------------------------------------| | 0 | common type | Indicates this attachment's type field is just a uint8 value and Media Type can be looked up per [Common Media Types](#common-media-types). | | 1 | zlib-deflate | Attachment data is compressed using the zlib structure (defined in RFC 1950), with the deflate compression algorithm (defined in RFC 1951). | -| 2 — 7 | TBD | Unused, reserved for future use | +| 2 — 7 | reserved | Unused, reserved for future use. MUST be 0; a Receiving Host MUST respond REJECT code 1 (invalid) if any is set. | filename MUST be: @@ -411,7 +430,7 @@ Other codes 100 and above are per recipient in the same order as recipients for | code | name | description | |-----:|-----------------------|-------------------------------------------------------------------------| -| 1 | invalid | the message header fails verification checks, i.e. not in spec | +| 1 | invalid | the message header fails verification checks, i.e. not in spec, including a _pid_ referencing a terminal message | | 3 | undisclosed | no reason is given | | 4 | too big | total size or expanded size exceeds host's maximum permitted size of messages | | 5 | insufficient resources | such as disk space to store the message | @@ -492,6 +511,8 @@ The following variables corresponding to host defined configuration are used in 6. Each attachment _type_ number, when that attachment's _common type_ flag is set, exists in [Common Media Type](#common-media-types) mapping. 7. The message _expanded size_ field MUST exist if and only if the message _zlib-deflate_ flag bit is set. 8. Each attachment _expanded size_ field MUST exist if and only if that attachment's _zlib-deflate_ flag bit is set. + 9. If the _has add to_ flag bit is set, the _terminal_ flag bit MUST NOT be set. An add-to message duplicates the original's flags, so _terminal_ here means the original is terminal and cannot be referenced, see [Notes on Terminal Messages](#notes-on-terminal-messages). + 10. No reserved flag bit is set: message flags bit 7, and each attachment's flags bits 2 through 7, MUST be 0. 2. Receiving Host B MUST perform a DNS lookup on the fmsg subdomain of the senders domain to verify that the IP address of the incoming connection is in those authorised by the sending domain. If the incoming IP address is not in the authorised set, Host B MUST TERMINATE the message exchange. See [Domain Resolution](#domain-resolution) for more. * If the _has add to_ flag bit set the sender's domain is the domain part of the _add to from_ address. * Otherwise, the sender's domain is the domain part of the _from_ address. @@ -506,12 +527,14 @@ The following variables corresponding to host defined configuration are used in 1. The message _pid_ refers to MUST be verified to be stored already on Host B per [Verifying Message Stored](#verifying-message-stored); otherwise Host B MUST respond with REJECT code 6 (parent not found) completing the message exchange. 2. The stored message for _pid_'s _time_ minus MAX_TIME_SKEW MUST be before _time_ on the incoming message header; otherwise Host B MUST respond with REJECT code 9 (time travel) completing the message exchange. 3. _from_ MUST have been a participant in the stored message referred to by _pid_; otherwise Host B MUST respond with REJECT code 1 (invalid) completing the message exchange. + 4. The stored message referred to by _pid_ MUST NOT have the _terminal_ flag bit set; otherwise Host B MUST respond with REJECT code 1 (invalid) completing the message exchange. _NOTE_ Verifying Message Stored checks the host has the parent message, not that every recipient still has it in their message store. Implementations MAY consider restoring the parent message to a recipient's message store if that _recipient_ no longer has the message, so that the incoming reply has proper thread context for all recipients. 3. Else _add to_ exists; 1. _pid_ field MUST exist too, otherwise Host B MUST respond REJECT code 1 (invalid) and close the connection completing the message exchange. 2. [Verifying Message Stored](#verifying-message-stored) is performed for message referred to by _pid_; 3. If original message referred to by _pid_ is verified to be stored AND; - 1. The stored message for _pid_'s _time_ minus MAX_TIME_SKEW MUST be before _time_ on the incoming message header; otherwise Host B MUST respond with REJECT code 9 (time travel). + 1. The stored message MUST NOT have the _terminal_ flag bit set; otherwise Host B MUST respond with REJECT code 1 (invalid) completing the message exchange. + 2. The stored message for _pid_'s _time_ minus MAX_TIME_SKEW MUST be before _time_ on the incoming message header; otherwise Host B MUST respond with REJECT code 9 (time travel). 4. Otherwise (original message has not been found, possible because Host B was never a participant of the message, or the message referenced by _pid_ is no longer held): 1. If at least one recipient in _to_ or _add to_ belongs to Host B, the message is treated as a full message delivery. 2. Otherwise Host B hosts only non-recipient participants of the message; Host B MUST respond REJECT code 6 (parent not found) then close the connection completing the message exchange. @@ -584,6 +607,8 @@ _NOTE_ When recipients for Host B are added using the _add to_ functionality to A Sending Host (Host A) delivers a message if and only if _from_ or _add to from_ belongs to Host A's domain. When the _has add to_ flag bit is not set, the message is sent to each unique recipient domain exactly once, regardless of how many recipients share that domain. When the _has add to_ flag bit is set, the message is sent exactly once to each unique participant domain — the domains of _from_ and of every address in _to_ and _add to_, omitting _from_'s domain when _from_ is the _add to from_ (the adder is the original sender, whose host is the Sending Host) — so that all participants of the message being added to learn of the added recipients, not only the domains hosting the new recipients. This section describes the steps Host A performs for each domain. If multiple domains exist, Host A performs these steps independently for each domain without regard to the others. +Host A MUST NOT transmit a message whose _pid_ references a message Host A holds with the _terminal_ flag bit set, and SHOULD refuse to create such a message for its clients, see [Notes on Terminal Messages](#notes-on-terminal-messages). + 1. Host A resolves the authorised IP addresses via [Domain Resolution](#domain-resolution) for Host B. 1. Host A initiates a connection (Connection 1) to the first authorised IP address for the Receiving Host (Host B). 2. If the first IP address is unresponsive within an implementation-defined timeout, and multiple IP addresses were returned during domain resolution, Host A SHOULD attempt to connect to each address in the order provided, one at a time, until a responsive host is reached. diff --git a/STANDARDS.md b/STANDARDS.md index c98c413..888bb0b 100644 --- a/STANDARDS.md +++ b/STANDARDS.md @@ -23,6 +23,7 @@ Each standard is prefixed with a unique identifer following the format: "FMSG-## | [FMSG-002](standards/fmsg-002-id.md) | HTTP API spec for recipient lookup and quota limits | | [FMSG-003](standards/fmsg-003-webapi.md) | HTTP API spec client to perform CRUD operations on messages for a specific address | | [FMSG-004](standards/fmsg-004-a2a-binding.md) | A2A protocol binding over fmsg | +| [FMSG-005](standards/fmsg-005-reactions.md) | Emoji reactions to messages, carried as terminal fmsg messages | diff --git a/mkdocs.yml b/mkdocs.yml index a2319a1..ad6bc69 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ nav: - FMSG-002 — Address API: standards/fmsg-002-id.md - FMSG-003 — Web API: standards/fmsg-003-webapi.md - FMSG-004 — A2A binding: standards/fmsg-004-a2a-binding.md + - FMSG-005 — Reactions: standards/fmsg-005-reactions.md - White paper: white-paper.md - Implementations: IMPLEMENTATIONS.md - Background: show-hn.md diff --git a/standards/fmsg-005-reactions.md b/standards/fmsg-005-reactions.md new file mode 100644 index 0000000..c214f89 --- /dev/null +++ b/standards/fmsg-005-reactions.md @@ -0,0 +1,268 @@ +# FMSG-005 Reactions Standard + +## Status + +| Revision | Date | Summary | +|----------|------------|---------------| +| v0.1.0 | 2026-09-02 | Initial draft | + +This standard defines how a participant reacts to a message with a single +emoji. A reaction is an ordinary fmsg message constrained so that it can be +verified, delivered, and stored by any conforming host, and so that no message +can be built on top of it. + +This revision requires fmsg Specification v0.6.0 or later for the _terminal_ +flag. + +## Requirements Language + +The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, +**SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **NOT RECOMMENDED**, **MAY**, and +**OPTIONAL** in this document are to be interpreted as described in BCP 14 +([RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and +[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174)) when, and only when, they +appear in all capitals. + +## Scope + +This standard defines: + +- the shape of a reaction message on the wire; +- which emoji a reaction may carry; +- who a reaction is delivered to; +- how a participant's effective reaction on a message is determined, including + changing and clearing it; and +- how hosts and clients that do not implement this standard behave. + +It does not change the host-to-host protocol, define a client API, or require +a host to understand reactions in order to carry them. Client API exposure of +reactions is expected in a future revision of +[FMSG-003](fmsg-003-webapi.md). + +## Normative References + +- [fmsg Specification](../SPECIFICATION.md) v0.6.0 or later +- [UTS #51: Unicode Emoji](https://www.unicode.org/reports/tr51/) +- [RFC 3629: UTF-8](https://www.rfc-editor.org/rfc/rfc3629) + +## Terminology + +**subject** is the message a reaction refers to via _pid_. It may be a +thread's first message, a reply, or an add-to batch message. + +**reactor** is the address in a reaction message's _from_ field. + +**reaction message** is an fmsg message meeting the requirements in +[Reaction Message](#reaction-message). + +**effective reaction** is the reaction, if any, that a reactor currently has +on a subject, determined per [Effective Reaction](#effective-reaction). + +Other terms have the meanings given in the fmsg Specification. + +## Introduction + +This section is non-normative. + +Reactions are lightweight acknowledgements such as 👍 or ❤️ attached to a +message rather than sent as a reply. Modelling them as messages rather than as +a separate side channel means they inherit everything fmsg already provides: +reliable store-and-forward delivery with retries, duplicate detection, the +participant rule (only a participant of the subject can react to it), the +challenge, and immutable, hash-verifiable storage. + +The one property reactions need that an ordinary reply lacks is that nothing +can be built on top of them: a reaction cannot be replied to, reacted to, or +have recipients added. The Specification's _terminal_ flag provides exactly +that, and every host enforces it whether or not it knows what a reaction is. + +Because messages are immutable, changing or removing a reaction is done by +sending a newer reaction whose data replaces the earlier one. + +## Reaction Message + +A reaction message is an fmsg message with all of the following properties. A +message that has the reaction Media Type but violates any of them is malformed; +see [Malformed Reactions](#malformed-reactions). + +### Flags + +| Flag bit | Name | Requirement | +|---------:|------|-------------| +| 0 | has pid | MUST be set. | +| 1 | has add to | MUST NOT be set. | +| 2 | common type | MUST NOT be set. | +| 3 | important | MUST NOT be set. | +| 4 | no reply | MUST be set. | +| 5 | zlib-deflate | MUST NOT be set. | +| 6 | terminal | MUST be set. | + +_terminal_ is what prevents chaining. _no reply_ is set in addition so that +hosts and clients that predate the _terminal_ flag, but recognise _no reply_, +still present the reaction as something not to be replied to. + +### Fields + +| Field | Requirement | +|-------|-------------| +| _pid_ | The message hash of the subject. | +| _from_ | The reactor. Per the Specification the reactor MUST be a participant of the subject. | +| _to_ | Every participant of the subject other than the reactor, see [Recipients](#recipients). | +| _topic_ | Absent, because _pid_ is present. | +| _type_ | The US-ASCII string `application/vnd.fmsg.reaction`, exactly, with no parameters. | +| _size_ | 0, or the byte length of _data_, which MUST NOT exceed 64. | +| _attachment headers_ | Count MUST be 0. | +| _data_ | Empty, or exactly one emoji encoded as UTF-8, see [Emoji](#emoji). | + +### Emoji + +Non-empty _data_ MUST be the UTF-8 encoding of exactly one element of the +Unicode `RGI_Emoji` set defined by UTS #51, that is a single Recommended for +General Interchange emoji: a basic emoji, keycap sequence, flag sequence, tag +sequence, modifier sequence, or ZWJ sequence. Fully-qualified sequences +SHOULD be used. + +Implementations SHOULD validate against the `RGI_Emoji` set of the most recent +Unicode version they support. Because that set grows over time, an +implementation encountering non-empty _data_ that is a single extended +grapheme cluster (UAX #29) whose code points all have the `Emoji`, +`Emoji_Component`, or `Emoji_Modifier` property, or are ZWJ or a variation +selector, SHOULD treat it as a reaction it cannot render rather than as +malformed. + +Empty _data_ (_size_ 0) means the reactor has no reaction on the subject, see +[Effective Reaction](#effective-reaction). + +### Recipients + +_to_ MUST contain every participant of the subject except the reactor, so that +all participants observe the reaction. The subject's participants are its +_from_, every address in its _to_, and, when the subject is an add-to batch +message, its _add to from_ and every address in its _add to_. + +Where the reactor is the only participant of the subject this standard cannot +apply, because a message requires at least one recipient; a reaction to a +message one sent only to oneself is not sent. + +## Effective Reaction + +A reactor has at most one effective reaction on a subject. + +Among all reaction messages held by a host whose _from_ is the reactor and +whose _pid_ is the subject, the one with the greatest _time_ determines the +effective reaction. If two such messages share the same _time_, the one whose +message hash is greater when compared as an unsigned big-endian integer +determines it. If that message's _data_ is empty, the reactor has no effective +reaction on the subject. Otherwise its _data_ is the effective reaction. + +Changing a reaction is therefore sending a new reaction message with the new +emoji, and clearing a reaction is sending one with empty _data_. Earlier +reaction messages remain stored and verifiable; they simply no longer +determine the effective reaction. + +Because the Specification rejects a reply whose _time_ precedes its parent's, +every reaction message has a _time_ after the subject's. + +## Sending + +A reaction message is created, sent, retried, and recorded exactly as any +other message per the Specification. Nothing about its delivery is different. + +A client or host MUST NOT create a reaction whose subject has the _terminal_ +flag bit set. In particular a reaction cannot itself be the subject of a +reaction. The Specification's Sending Host rule enforces this for hosts that +implement v0.6.0. + +## Receiving + +A Receiving Host applies the Specification unchanged. This standard adds no +header validation: a host need not recognise the reaction Media Type to accept, +challenge, store, and deliver a reaction message. + +A host or client that does implement this standard SHOULD validate +[Reaction Message](#reaction-message) after the message is stored and handle +violations per [Malformed Reactions](#malformed-reactions). + +### Malformed Reactions + +A stored message whose _type_ is `application/vnd.fmsg.reaction` but which +violates [Reaction Message](#reaction-message) MUST NOT be treated as a +reaction: it MUST NOT contribute to any effective reaction. Implementations +MAY present it as an ordinary message or hide it. + +Rejecting such a message on the wire is not possible after the header exchange +without inventing response codes, and this standard does not do so. + +## Presentation + +This section is non-normative. + +Clients that implement this standard are expected to: + +- render, on each message, the set of effective reactions grouped by emoji + with the reactors of each, rather than rendering reaction messages as + replies in the thread; +- offer a way to add, change, and clear the viewer's own reaction on any + message they are a participant of that is not terminal; +- keep the viewer's own reactions consistent across devices by reading the + stored reaction messages rather than local state; and +- treat an incoming reaction message as a low-priority event that does not + warrant the notification an ordinary message would. + +Clients that do not implement this standard see a short message of an +unfamiliar type containing an emoji, or nothing, with _no reply_ set. That is +the intended degradation. + +## Interoperability + +| Peer | Behaviour | +|------|-----------| +| Host implementing Specification v0.6.0 and this standard | Accepts, stores, and delivers the reaction; exposes it to its clients as a reaction. | +| Host implementing Specification v0.6.0 only | Accepts, stores, and delivers the reaction as an ordinary terminal message. Its clients see it as a small message. Nobody can reply to it. | +| Host implementing a Specification version before v0.6.0 | Rejects the reaction with code 1 (invalid) because the _terminal_ bit is a reserved bit set. The reactor's host records the rejection like any other and its clients can show that the recipient host does not support reactions. | + +## Security Considerations + +- **Volume:** each reaction is a stored message and a connection per recipient + domain. The Specification's per-connection and per-IP rate limits and + per-user storage quotas apply. Hosts MAY apply stricter rate limits to + messages of the reaction Media Type. +- **Rendering:** implementations MUST NOT render _data_ of a malformed + reaction as an emoji. Validating against `RGI_Emoji` prevents arbitrary text + being displayed in a reaction slot. +- **Participant rule:** reactions inherit the Specification's participant + check, so an address that is not a participant of the subject cannot react + to it, and a host cannot be tricked into showing one. +- **Disclosure:** a reaction discloses to all participants of the subject that + the reactor reacted and with what. It discloses nothing they could not + already infer from being participants. + +## Examples + +This section is non-normative. + +A reaction by `@bob@example.com` to a message from `@alice@example.com` sent +to Bob and `@chris@example.edu`: + +```JSON +{ + "version": 1, + "important": false, + "noreply": true, + "terminal": true, + "pid": "3f8a…c21e", + "from": "@bob@example.com", + "to": [ + "@alice@example.com", + "@chris@example.edu" + ], + "time": 1788393600.120384, + "type": "application/vnd.fmsg.reaction", + "size": 4, + "data": "👍", + "attachments": [] +} +``` + +Bob later clears the reaction by sending the same message with a later +_time_, _size_ 0, and no data. From fb368fa07e8f576c16271ec87da7495552e2d535 Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Wed, 2 Sep 2026 14:09:12 +0800 Subject: [PATCH 2/4] FMSG-005: define reactions structurally, common type 56 Drop the dedicated application/vnd.fmsg.reaction Media Type. A Media Type describes representation, and a reaction's data is UTF-8 text; the intent is already carried by the flags. A reaction is now any message with has pid, no reply, terminal and common type set, type ID 56 (text/plain;charset=UTF-8), no attachments, and data that is a single RGI_Emoji or empty. A single-emoji terminal reply was meant as a reaction anyway, unaware clients render it as the short text it is, and the "malformed reaction" category disappears: a message either has the shape of a reaction or is simply a reply. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MW4FXDGacoWXeKPU5cQTHS --- standards/fmsg-005-reactions.md | 76 ++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/standards/fmsg-005-reactions.md b/standards/fmsg-005-reactions.md index c214f89..7f1803f 100644 --- a/standards/fmsg-005-reactions.md +++ b/standards/fmsg-005-reactions.md @@ -7,9 +7,10 @@ | v0.1.0 | 2026-09-02 | Initial draft | This standard defines how a participant reacts to a message with a single -emoji. A reaction is an ordinary fmsg message constrained so that it can be -verified, delivered, and stored by any conforming host, and so that no message -can be built on top of it. +emoji. A reaction is an ordinary plain-text fmsg message, recognised by its +shape rather than by a dedicated type, constrained so that it can be verified, +delivered, and stored by any conforming host, and so that no message can be +built on top of it. This revision requires fmsg Specification v0.6.0 or later for the _terminal_ flag. @@ -79,11 +80,19 @@ that, and every host enforces it whether or not it knows what a reaction is. Because messages are immutable, changing or removing a reaction is done by sending a newer reaction whose data replaces the earlier one. +A reaction has no Media Type of its own. Its data is UTF-8 text, and a Media +Type describes representation, not intent; the intent is already carried by +the flags. A reaction is therefore any terminal, no-reply, plain-text reply +whose whole body is a single emoji. A person who sends exactly that as a reply +meant it as a reaction, and clients that know nothing of this standard render +it as the short text message it is. + ## Reaction Message -A reaction message is an fmsg message with all of the following properties. A -message that has the reaction Media Type but violates any of them is malformed; -see [Malformed Reactions](#malformed-reactions). +A message is a reaction message if and only if it has all of the following +properties. There is no separate reaction type: a message is recognised as a +reaction by its shape, and a message lacking any property is an ordinary +message, see [Recognising Reactions](#recognising-reactions). ### Flags @@ -91,7 +100,7 @@ see [Malformed Reactions](#malformed-reactions). |---------:|------|-------------| | 0 | has pid | MUST be set. | | 1 | has add to | MUST NOT be set. | -| 2 | common type | MUST NOT be set. | +| 2 | common type | MUST be set. | | 3 | important | MUST NOT be set. | | 4 | no reply | MUST be set. | | 5 | zlib-deflate | MUST NOT be set. | @@ -109,7 +118,7 @@ still present the reaction as something not to be replied to. | _from_ | The reactor. Per the Specification the reactor MUST be a participant of the subject. | | _to_ | Every participant of the subject other than the reactor, see [Recipients](#recipients). | | _topic_ | Absent, because _pid_ is present. | -| _type_ | The US-ASCII string `application/vnd.fmsg.reaction`, exactly, with no parameters. | +| _type_ | Common Media Type ID 56, `text/plain;charset=UTF-8`. | | _size_ | 0, or the byte length of _data_, which MUST NOT exceed 64. | | _attachment headers_ | Count MUST be 0. | | _data_ | Empty, or exactly one emoji encoded as UTF-8, see [Emoji](#emoji). | @@ -127,8 +136,8 @@ Unicode version they support. Because that set grows over time, an implementation encountering non-empty _data_ that is a single extended grapheme cluster (UAX #29) whose code points all have the `Emoji`, `Emoji_Component`, or `Emoji_Modifier` property, or are ZWJ or a variation -selector, SHOULD treat it as a reaction it cannot render rather than as -malformed. +selector, SHOULD treat it as a reaction it cannot render rather than as an +ordinary message. Empty _data_ (_size_ 0) means the reactor has no reaction on the subject, see [Effective Reaction](#effective-reaction). @@ -176,22 +185,27 @@ implement v0.6.0. ## Receiving A Receiving Host applies the Specification unchanged. This standard adds no -header validation: a host need not recognise the reaction Media Type to accept, -challenge, store, and deliver a reaction message. +header validation: a host need not recognise reactions to accept, challenge, +store, and deliver one, because on the wire a reaction is a plain-text +terminal reply. -A host or client that does implement this standard SHOULD validate -[Reaction Message](#reaction-message) after the message is stored and handle -violations per [Malformed Reactions](#malformed-reactions). +### Recognising Reactions -### Malformed Reactions +A host or client that implements this standard determines whether a stored +message is a reaction by testing it against [Reaction Message](#reaction-message) +after the message is stored. A message meeting every property is a reaction +and contributes to the effective reaction of its reactor on its subject. Any +other message, including a terminal plain-text reply whose data is not a +single emoji, is an ordinary message and MUST NOT contribute to any effective +reaction. -A stored message whose _type_ is `application/vnd.fmsg.reaction` but which -violates [Reaction Message](#reaction-message) MUST NOT be treated as a -reaction: it MUST NOT contribute to any effective reaction. Implementations -MAY present it as an ordinary message or hide it. +There is consequently no malformed reaction and nothing to reject on the wire: +a message either has the shape of a reaction or is simply a reply. -Rejecting such a message on the wire is not possible after the header exchange -without inventing response codes, and this standard does not do so. +Implementations SHOULD treat a _type_ encoded as the string +`text/plain;charset=UTF-8` with the _common type_ flag bit not set as +equivalent to Common Media Type ID 56 when recognising reactions; senders +MUST use ID 56. ## Presentation @@ -209,9 +223,9 @@ Clients that implement this standard are expected to: - treat an incoming reaction message as a low-priority event that does not warrant the notification an ordinary message would. -Clients that do not implement this standard see a short message of an -unfamiliar type containing an emoji, or nothing, with _no reply_ set. That is -the intended degradation. +Clients that do not implement this standard see a short plain-text reply +containing an emoji, or an empty one, with _no reply_ set. That is the intended +degradation, and it is why reactions carry no type of their own. ## Interoperability @@ -226,10 +240,10 @@ the intended degradation. - **Volume:** each reaction is a stored message and a connection per recipient domain. The Specification's per-connection and per-IP rate limits and per-user storage quotas apply. Hosts MAY apply stricter rate limits to - messages of the reaction Media Type. -- **Rendering:** implementations MUST NOT render _data_ of a malformed - reaction as an emoji. Validating against `RGI_Emoji` prevents arbitrary text - being displayed in a reaction slot. + messages having the shape of a reaction. +- **Rendering:** implementations MUST NOT place _data_ that is not a single + `RGI_Emoji` in a reaction slot. The shape test is what prevents arbitrary + text from being displayed as a reaction. - **Participant rule:** reactions inherit the Specification's participant check, so an address that is not a participant of the subject cannot react to it, and a host cannot be tricked into showing one. @@ -257,12 +271,14 @@ to Bob and `@chris@example.edu`: "@chris@example.edu" ], "time": 1788393600.120384, - "type": "application/vnd.fmsg.reaction", + "type": "text/plain;charset=UTF-8", "size": 4, "data": "👍", "attachments": [] } ``` +On the wire _type_ is the single byte 56 with the _common type_ flag bit set. + Bob later clears the reaction by sending the same message with a later _time_, _size_ 0, and no data. From 25651fca5e6d950d4ab7daba7a28f14dfa54906a Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Wed, 2 Sep 2026 14:29:27 +0800 Subject: [PATCH 3/4] FMSG-003 v0.2.0: terminal flag and reactions in the client API - Message objects gain `terminal`, `reaction` and `reactions`. - POST /fmsg and PUT accept `terminal`; a pid referencing a terminal message is 409, as is add-to on one. - New POST /fmsg/:id/react sets or clears the caller's reaction by sending an FMSG-005 reaction message; idempotent when unchanged. - New `reaction` WebSocket event carrying the refreshed subject; a reaction is never delivered as new_msg and produces no Web Push. - Conformance tests extended. FMSG-005: recognition MAY ignore the recipients requirement; point at FMSG-003 v0.2.0 for the client API. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MW4FXDGacoWXeKPU5cQTHS --- standards/fmsg-003-webapi.md | 77 ++++++++++++++++++++++++++++++++- standards/fmsg-005-reactions.md | 9 ++-- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/standards/fmsg-003-webapi.md b/standards/fmsg-003-webapi.md index 07bc9af..cd4e38c 100644 --- a/standards/fmsg-003-webapi.md +++ b/standards/fmsg-003-webapi.md @@ -5,6 +5,7 @@ | Revision | Date | Summary | |----------|------------|---------------| | v0.1.0 | 2026-08-07 | Initial draft | +| v0.2.0 | 2026-09-02 | `terminal` flag on messages; FMSG-005 reactions: `reaction`/`reactions` fields, `POST /fmsg/:id/react`, `reaction` event | This standard defines an authenticated HTTP and WebSocket API through which one fmsg identity creates, sends, receives, and manages messages on an fmsg host. It @@ -29,6 +30,7 @@ It defines: - optional API keys and API-access grants; - draft, message, recipient, attachment, and read-state operations; - delivery-status reporting; +- emoji reactions per [FMSG-005](fmsg-005-reactions.md); - plain-text thread rendering; - live event delivery over WebSocket; and - optional Web Push subscription management. @@ -40,6 +42,7 @@ database schemas, host-to-host message transport, or a user interface. - [fmsg Specification](../SPECIFICATION.md) - [FMSG-002 Id Standard](fmsg-002-id.md) +- [FMSG-005 Reactions Standard](fmsg-005-reactions.md) - [RFC 6455: The WebSocket Protocol](https://www.rfc-editor.org/rfc/rfc6455) - [RFC 7517: JSON Web Key](https://www.rfc-editor.org/rfc/rfc7517) - [RFC 7519: JSON Web Token](https://www.rfc-editor.org/rfc/rfc7519) @@ -234,6 +237,7 @@ Core routes are always present. Grant and Web Push routes are conditional. | `POST` | `/fmsg/:id/send` | Core | Send a draft | | `POST` | `/fmsg/:id/read` | Core | Mark a received message read | | `POST` | `/fmsg/:id/add-to` | Core | Add recipients | +| `POST` | `/fmsg/:id/react` | Core | Set or clear the caller's reaction | | `GET` | `/fmsg/:id/data` | Core | Download message data | | `GET` | `/fmsg/:id/thread` | Core | Render direct ancestor lineage | | `POST` | `/fmsg/:id/attach` | Core | Upload an attachment | @@ -262,6 +266,7 @@ A message metadata object has this shape: "important": false, "no_reply": false, "deflate": false, + "terminal": false, "pid": null, "from": "@alice@example.com", "to": ["@bob@example.com"], @@ -296,6 +301,10 @@ A message metadata object has this shape: "time_read": null, "attachments": [ { "filename": "notes.pdf", "size": 12345 } + ], + "reaction": null, + "reactions": [ + { "emoji": "👍", "from": ["@bob@example.com"] } ] } ``` @@ -310,6 +319,7 @@ The fields are: | `important` | Boolean | Sender importance indication | | `no_reply` | Boolean | Sender indicates replies will be discarded | | `deflate` | Boolean | Stored data was detected as compressed content for fmsg wire handling | +| `terminal` | Boolean | The fmsg _terminal_ flag: a leaf no message may reference via _pid_ | | `pid` | integer or null | Parent message ID in this API deployment | | `from` | string | Sender fmsg address | | `to` | string array | Primary recipients | @@ -323,10 +333,22 @@ The fields are: | `read` | Boolean | Effective recipient's read state | | `time_read` | number or null | Effective recipient's first-read time | | `attachments` | object array | Attachment names and sizes; never their contents | +| `reaction` | string or null | When this message is an FMSG-005 reaction, the emoji it carries, or `""` for a clearing reaction; `null` otherwise | +| `reactions` | object array | Effective FMSG-005 reactions on this message; `[]` when none | List and WebSocket message objects additionally contain an integer `id`. A single `GET /fmsg/:id` response does not contain `id`. +A message is a reaction when it has the shape defined by FMSG-005 and its body +is a single emoji or empty. The server MUST recognise reactions by that shape +and MAY ignore the FMSG-005 requirement on `to` when recognising them. Each +`reactions` entry is `{ "emoji": string, "from": string array }`: one entry per +distinct emoji, in order of first reaction, listing the participants whose +effective reaction it is in time order. A participant whose latest reaction is +a clearing reaction has no entry. Servers SHOULD populate `reaction` and +`reactions` on every message object so clients can hide reaction messages from +message lists and render reactions on their subjects. + `short_text` MAY be omitted. When enabled, it MUST appear only for a body whose media type is `text/*` and whose preview is valid UTF-8. It MUST be truncated only at a UTF-8 code-point boundary. Clients MUST use the data-download route @@ -396,6 +418,7 @@ Creates a draft. The request is JSON: | `size` | integer | yes | Client's body byte count; server-computed value is authoritative | | `important` | Boolean | no | Defaults to false | | `no_reply` | Boolean | no | Defaults to false | +| `terminal` | Boolean | no | Defaults to false; sets the fmsg _terminal_ flag | | `data` | string | no | UTF-8 message body; defaults to empty | Recipients can be added only through the add-to route. A supplied `add_to` @@ -405,6 +428,10 @@ The server MUST derive the stored `size` from the UTF-8 bytes of `data`, rather than trust the request's `size`. The body and total message size MUST remain within deployment limits. +A `pid` referencing a message whose `terminal` is true MUST be rejected with +`409 Conflict`: no message may reference a terminal message. The same applies +to `PUT /fmsg/:id`. + Success is `201 Created`: ```json @@ -498,12 +525,51 @@ permitted by the fmsg protocol's re-delivery semantics. The server MUST atomically create the batch, its recipient rows, and any participant-domain notifications required by the fmsg protocol. +Recipients cannot be added to a terminal message; the server MUST reject with +`409 Conflict`. + Success is: ```json { "id": 123, "added": 2 } ``` +### `POST /fmsg/:id/react` + +Sets or clears the authenticated identity's reaction on message `:id` per +[FMSG-005](fmsg-005-reactions.md). The identity MUST be a participant of the +message. The message MUST be sent and MUST NOT be terminal; otherwise the +server MUST reject with `409 Conflict`. + +Request: + +```json +{ "emoji": "👍" } +``` + +`emoji` MUST be a single emoji: an element of the Unicode `RGI_Emoji` set, or +another single well-formed emoji sequence as permitted by FMSG-005. An absent, +`null`, or empty `emoji` clears the caller's reaction. Any other value MUST be +rejected with `400 Bad Request`. + +The server MUST create and send, in one step, a reaction message as defined by +FMSG-005: a reply to `:id` with _no reply_ and _terminal_ set, type +`text/plain;charset=UTF-8`, body `emoji`, addressed to every participant of +the message other than the caller. If the message has no other participant the +server MUST reject with `409 Conflict`. The reply-domain check of +`POST /fmsg/:id/send` applies. + +Setting a reaction equal to the caller's current effective reaction MUST NOT +send another message; the server responds `200 OK` with the existing reaction +message's `id` and `time`. Clearing when the caller has no effective reaction +responds `200 OK` with `null` for both. + +Success when a reaction message was sent is `201 Created`: + +```json +{ "id": 124, "time": 1786064500.000001 } +``` + ## Content Routes ### `GET /fmsg/:id/data` @@ -778,6 +844,10 @@ message list. | `new_msg` | Message recipient | A sent or federated message became available | | `delivered` | Message sender | Delivery state changed; `data` is the refreshed message | | `recipients_added` | Message participant | An add-to batch was recorded; `data` is the refreshed message | +| `reaction` | Subject participant | An FMSG-005 reaction arrived; `data` is the refreshed subject message with its `reactions` | + +A reaction message MUST NOT be delivered as `new_msg`: the server MUST route it +as a `reaction` event on its subject instead. Clients MUST ignore unknown event types. The server SHOULD send WebSocket ping frames and disconnect clients that do not respond or cannot consume events @@ -825,7 +895,8 @@ Deletion is idempotent and scoped to the authenticated identity. Success is ### Push Payload When `new_msg` occurs, the server MAY send an encrypted Web Push independently -of whether a WebSocket client is connected: +of whether a WebSocket client is connected. A reaction MUST NOT produce a Web +Push: ```json { @@ -921,6 +992,7 @@ An implementation pair SHOULD demonstrate: 4. Root and reply creation, including an undeliverable reply-domain conflict. 5. Primary and add-to recipient visibility and per-recipient read state. 6. Atomic add-to batches and refreshed `recipients_added` events. + Rejection of add-to, reply creation, and reaction on a terminal message. 7. Pending, failed, local, and successful remote delivery states plus `delivered` events. 8. Thread rendering with visible text, JSON, non-text, and invisible ancestors. @@ -931,3 +1003,6 @@ An implementation pair SHOULD demonstrate: deletion, source denial, token exchange, and revocation of an issued JWT. 12. If Web Push is supported: create, refresh, idempotent delete, root-thread payload grouping, and removal of dead subscriptions. +13. Reactions: set, change, clear, idempotent repeat, non-participant denial, + `reaction`/`reactions` on message objects, the `reaction` event, and no + `new_msg` event or Web Push for a reaction message. diff --git a/standards/fmsg-005-reactions.md b/standards/fmsg-005-reactions.md index 7f1803f..98a4f40 100644 --- a/standards/fmsg-005-reactions.md +++ b/standards/fmsg-005-reactions.md @@ -36,9 +36,8 @@ This standard defines: - how hosts and clients that do not implement this standard behave. It does not change the host-to-host protocol, define a client API, or require -a host to understand reactions in order to carry them. Client API exposure of -reactions is expected in a future revision of -[FMSG-003](fmsg-003-webapi.md). +a host to understand reactions in order to carry them. The client API for +reactions is defined by [FMSG-003](fmsg-003-webapi.md) v0.2.0 and later. ## Normative References @@ -205,7 +204,9 @@ a message either has the shape of a reaction or is simply a reply. Implementations SHOULD treat a _type_ encoded as the string `text/plain;charset=UTF-8` with the _common type_ flag bit not set as equivalent to Common Media Type ID 56 when recognising reactions; senders -MUST use ID 56. +MUST use ID 56. Implementations MAY ignore the [Recipients](#recipients) +requirement when recognising a stored reaction, since a host cannot always +know a subject's full participant set. ## Presentation From ada9a68b3073794d99c484f838ad798026f39c0e Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Wed, 2 Sep 2026 15:30:44 +0800 Subject: [PATCH 4/4] spec: concise v0.6.0 revision note Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MW4FXDGacoWXeKPU5cQTHS --- SPECIFICATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPECIFICATION.md b/SPECIFICATION.md index 6cffe43..26a8f83 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -12,7 +12,7 @@ | v0.4.0 | 2026-08-02 | Mark Mennell | Add-to messages delivered to all participant domains; notification-only delivery completes at code 11 | | v0.4.1 | 2026-08-03 | Mark Mennell | Hosts must retain stored messages in full, including complete recipient lists | | v0.5.0 | 2026-08-10 | Mark Mennell | Clarified: stored messages include those the host sent; exactly one header-response code is sent; _add to_ may overlap _to_, with one response code per recipient entry; add-to copies omit _topic_; add-to batches are sibling branches whose added recipients reply to the batch message; batch identity is the batch message hash. Changed: an unsupported version TERMINATES rather than responding, retiring code 2 (numbering unchanged) | -| v0.6.0 | 2026-09-02 | Mark Mennell | Added: _terminal_ flag (bit 6) — no message may reference a terminal message via _pid_; hosts reject a reply to, or add-to batch of, a terminal message with code 1. Changed: reserved flag bits set on the wire (message bit 7, attachment bits 2–7) are rejected with code 1 so future bits degrade consistently | +| v0.6.0 | 2026-09-02 | Mark Mennell | Added _terminal_ flag (bit 6); reserved flag bits set on the wire are rejected | ## Contents