From a09f47690aac978c2c00fd56cae3125f36e12859 Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Thu, 10 Sep 2026 15:38:18 +0800 Subject: [PATCH 1/3] Define finalized message hashes and hash-based API references --- standards/fmsg-003-webapi.md | 79 ++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/standards/fmsg-003-webapi.md b/standards/fmsg-003-webapi.md index cd4e38c..4fa32c9 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.3.0 | 2026-09-10 | Hashes finalized at send time; SHA-256 message references and batch parents | | 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 @@ -106,13 +107,40 @@ address. ### Identifiers -Message IDs, parent IDs, and add-to batch IDs are positive signed 64-bit -integers local to one API deployment. A `pid` in this API is a message ID, not -the SHA-256 parent hash used on the fmsg wire. The host maps between them when -encoding or decoding fmsg messages. - -An `:id` path parameter MUST be a positive base-10 integer. An invalid value -MUST produce `400 Bad Request`. +Message IDs, response parent IDs, and add-to batch IDs are positive signed +64-bit integers local to one API deployment. Numeric identifiers remain supported. +A message's `sha256` is its protocol identity across deployments: a lowercase +64-character hexadecimal SHA-256 digest computed by the fmsg specification's +message-hash rules. It is not a hash of API JSON or mutable delivery/read state. + +A message `:id` path parameter MUST accept either a positive base-10 integer or +exactly 64 hexadecimal characters. Hexadecimal input is case-insensitive. A +64-character all-digit value is a hash, not an overflowing integer. Invalid +references MUST produce `400 Bad Request`; an unknown hash produces `404 Not +Found`. Hash references MUST preserve every authorization and state restriction +of the corresponding numeric route. Drafts have no hash; draft-only operations +continue to reject sent messages. Non-message identifiers such as batch IDs and +pagination values remain numeric. + +Create/update request `pid` MAY be a numeric message ID or a 64-character hash +string. Response `pid` remains a local numeric message ID. A hash-valued parent +may identify an original message or one of its add-to batch messages. The server +MUST retain that exact protocol parent hash in `psha256`, even when both resolve +to the same local message row. The parent MUST be sent and non-terminal, and the +caller MUST participate in the specifically referenced original or batch. A +recipient added only through a batch MUST use that batch's hash to reply. + +When a message becomes sent, its timestamp and hash MUST become visible atomically, +including local-only messages and reactions. Header encoding, compression metadata, +recipient order, and attachment order MUST be settled before hashing and preserved +for subsequent federation. Finalization failure MUST leave a draft unchanged. +Concurrent content mutations and send MUST NOT change the content after its hash +has been assigned. Read/delivery state and independently hashed add-to batches +remain mutable bookkeeping or separate messages respectively. + +Every sent or received message MUST have a hash; drafts expose `null`. During an +upgrade, pre-existing unhashed records MAY temporarily expose `null` pending +backfill. Backfill MUST preserve timestamps and already established hashes. ### Time Values @@ -268,6 +296,8 @@ A message metadata object has this shape: "deflate": false, "terminal": false, "pid": null, + "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "psha256": null, "from": "@alice@example.com", "to": ["@bob@example.com"], "to_delivery": [ @@ -280,6 +310,7 @@ A message metadata object has this shape: "add_to": [ { "batch_id": 42, + "sha256": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", "add_to_from": "@bob@example.com", "to": ["@carol@example.com"], "to_delivery": [ @@ -318,9 +349,11 @@ The fields are: | `has_add_to` | Boolean | Whether `add_to` contains at least one batch | | `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 | +| `deflate` | Boolean | Protocol zlib-deflate flag chosen at finalization or received on the wire | | `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 | +| `sha256` | string or null | Lowercase protocol SHA-256; null for drafts or unbackfilled legacy records | +| `psha256` | string or null | Exact parent protocol hash, including a referenced batch; null without a parent | | `from` | string | Sender fmsg address | | `to` | string array | Primary recipients | | `to_delivery` | object array | Delivery state corresponding to `to` | @@ -412,7 +445,7 @@ Creates a draft. The request is JSON: | `version` | integer | yes | Currently `1` | | `from` | string | yes | MUST equal the authenticated identity | | `to` | string array | yes | At least one valid fmsg address | -| `pid` | integer | no | Existing parent message ID | +| `pid` | integer or string | no | Existing parent message ID, original hash, or batch hash | | `topic` | string | no | Root topic; MUST be empty when `pid` is present | | `type` | string | yes | Complete body media type | | `size` | integer | yes | Client's body byte count; server-computed value is authoritative | @@ -422,7 +455,8 @@ Creates a draft. The request is JSON: | `data` | string | no | UTF-8 message body; defaults to empty | Recipients can be added only through the add-to route. A supplied `add_to` -property MUST NOT add recipients. +property MUST NOT add recipients. Client-provided `sha256` and `psha256` +MUST NOT override server-derived identities. 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 @@ -483,7 +517,7 @@ thread. Success is: ```json -{ "id": 123, "time": 1786064400.654321 } +{ "id": 123, "time": 1786064400.654321, "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" } ``` HTTP success means the API accepted the message for local or federated @@ -523,7 +557,10 @@ batch MUST be rejected. An original primary recipient MAY be added again, as 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. +participant-domain notifications required by the fmsg protocol. Each batch object +includes `sha256`, its protocol identity (or null while its original is a draft +or during legacy backfill). A sent message's new batch MUST have its hash before +it becomes visible. Recipients cannot be added to a terminal message; the server MUST reject with `409 Conflict`. @@ -531,7 +568,7 @@ Recipients cannot be added to a terminal message; the server MUST reject with Success is: ```json -{ "id": 123, "added": 2 } +{ "id": 123, "added": 2, "batch_id": 42, "sha256": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" } ``` ### `POST /fmsg/:id/react` @@ -1006,3 +1043,19 @@ An implementation pair SHOULD demonstrate: 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. + +## Hash Fields in Mutation and Thread Responses + +A successful send response includes `sha256` alongside its existing numeric `id` +and `time`. A newly created or idempotently returned reaction includes its hash; +a no-op clear with no reaction message returns `sha256: null`. + +A successful add-to response includes `batch_id` and the batch's `sha256` alongside +`id` and `added`. A batch created on a draft exposes `null` until the original is +sent; its timestamp cannot precede the finalized original timestamp. On a sent +message, batch recipients, timestamp and hash MUST commit together, even if all +added recipients are local. Creating a batch MUST NOT change the original hash. + +Structured thread message entries include `sha256` and `psha256` when applicable. +Implementations exposing `message_sha256` MUST retain it as a compatibility alias. +Hashes and batch details MUST be omitted from inaccessible ancestor placeholders. From 732d2178770a686308b3e05bab9b0e5be7593f41 Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Thu, 10 Sep 2026 15:39:54 +0800 Subject: [PATCH 2/3] Keep Web API revision history chronological --- standards/fmsg-003-webapi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standards/fmsg-003-webapi.md b/standards/fmsg-003-webapi.md index 4fa32c9..c8af9c4 100644 --- a/standards/fmsg-003-webapi.md +++ b/standards/fmsg-003-webapi.md @@ -5,8 +5,8 @@ | Revision | Date | Summary | |----------|------------|---------------| | v0.1.0 | 2026-08-07 | Initial draft | -| v0.3.0 | 2026-09-10 | Hashes finalized at send time; SHA-256 message references and batch parents | | v0.2.0 | 2026-09-02 | `terminal` flag on messages; FMSG-005 reactions: `reaction`/`reactions` fields, `POST /fmsg/:id/react`, `reaction` event | +| v0.3.0 | 2026-09-10 | Hashes finalized at send time; SHA-256 message references and batch parents | 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 From 461c0a5d9a391b7d5895560a3a15e8df2ced8c2d Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Thu, 10 Sep 2026 16:11:44 +0800 Subject: [PATCH 3/3] Require offline finalization before serving hash-based API --- standards/fmsg-003-webapi.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/standards/fmsg-003-webapi.md b/standards/fmsg-003-webapi.md index c8af9c4..5f7d830 100644 --- a/standards/fmsg-003-webapi.md +++ b/standards/fmsg-003-webapi.md @@ -138,9 +138,9 @@ Concurrent content mutations and send MUST NOT change the content after its hash has been assigned. Read/delivery state and independently hashed add-to batches remain mutable bookkeeping or separate messages respectively. -Every sent or received message MUST have a hash; drafts expose `null`. During an -upgrade, pre-existing unhashed records MAY temporarily expose `null` pending -backfill. Backfill MUST preserve timestamps and already established hashes. +Every sent or received message MUST have a hash; drafts expose `null`. Existing +records MUST be finalized before serving this API version. Offline migration MUST +preserve timestamps and already established hashes. ### Time Values @@ -352,7 +352,7 @@ The fields are: | `deflate` | Boolean | Protocol zlib-deflate flag chosen at finalization or received on the wire | | `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 | -| `sha256` | string or null | Lowercase protocol SHA-256; null for drafts or unbackfilled legacy records | +| `sha256` | string or null | Lowercase protocol SHA-256; null for drafts | | `psha256` | string or null | Exact parent protocol hash, including a referenced batch; null without a parent | | `from` | string | Sender fmsg address | | `to` | string array | Primary recipients | @@ -558,8 +558,8 @@ 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. Each batch object -includes `sha256`, its protocol identity (or null while its original is a draft -or during legacy backfill). A sent message's new batch MUST have its hash before +includes `sha256`, its protocol identity (or null while its original is a draft). +A sent message's new batch MUST have its hash before it becomes visible. Recipients cannot be added to a terminal message; the server MUST reject with