Internet-Draft M. Toomim Updates: 5789, 9110 (if approved) Invisible College Intended status: Proposed Standard 2 November 2026 Expires: 6 May 2027 Replayable Updates for HTTP draft-toomim-httpbis-braid-updates-00 Abstract HTTP provides a suite of requests and responses for communicating changes to resource state, but they are limited in symmetry and reproducibility. PATCH is sent client-to-server, but a server cannot send a PATCH to a client. POST can change server state flexibly, but its behavior is server-defined, and cannot be reproduced by a naive server, client, or intermediary. This document extends ordinary HTTP requests and responses into a suite of symmetric, reproducible *updates*. A sequence of updates can be replayed on any client, server, or intermediary to reproduce identical changes to state. There are three types of updates: snapshots, patches, and deletions, and this document defines how they occur symmetrically in both requests and responses. Arbitrary patch formats can be defined with a new Patch-Type header, but most patches can be expressed as a replacement of a range expressed via an extended syntax of Range Unit. This document is one of the Braid-HTTP extensions, which work alone or together; together they make HTTP a state synchronization protocol. About This Document This note is to be removed before publishing as an RFC. Discussion of this document takes place on the HTTP Working Group mailing list (ietf-http-wg@w3.org). The source of this document, and an issue tracker, are at https://github.com/braid-org/braid-spec. Status of this Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 6 May 2027. Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction ...................................................4 1.1. Terminology ..................................................5 2. Updates have symmetric requests and responses ..................6 3. A Snapshot is a PUT request or 200 response ....................7 4. Patch formats specify deterministic changes ....................8 4.1. Patch-Type allows any patch format, in both directions .......8 4.2. Range patches are enough for most changes ...................10 4.2.1. This document extends the syntax of Content-Range .........10 4.2.2. This document defines the text range unit .................11 4.3. An update can contain several patches .......................11 5. A deletion is a DELETE request, or a 410 response .............12 6. Peers replay updates to reproduce the same state ..............13 7. Implementation Status .........................................13 8. Security Considerations .......................................14 9. IANA Considerations ...........................................14 9.1. HTTP Status Code ............................................14 9.2. HTTP Field Names ............................................14 9.3. HTTP Range Unit .............................................15 9.4. Accept-Patch and Accept-Ranges ..............................15 10. References ...................................................15 10.1. Normative References .......................................15 10.2. Informative References .....................................16 1. Introduction Many HTTP messages change the state of a resource. A client can PUT a new representation, PUT part of one with Content-Range, PATCH it [RFC5789], POST to it, or DELETE it. A server can respond with a representation in a 200, or with part of one in a 206. These messages lack two properties that synchronization needs. Some are not symmetric: a client can send a server a PATCH, but a server cannot send a PATCH to a client. And some are not reproducible: a POST can change state in any way, but each server defines what its POSTs do, so no other peer can apply one and get the same result. This document chooses a subset of HTTP messages and extends them into a suite of messages called "updates" that are symmetric and reproducible. For example, a client can edit a chat by replacing the text from position 6 to position 11 using a PUT with Content-Range and a "text" range unit: Request: PUT /chat Version: ("2") Parents: ("1") Content-Type: text/plain Content-Range: text [6:11] Content-Length: 8 everyone The chat said "Hello world" at version "1", so it says "Hello everyone" at version "2". The server can then send the same change to another client, in a response: Response: HTTP/1.1 206 Partial Content Version: ("2") Parents: ("1") Content-Type: text/plain Content-Range: text [6:11] Content-Length: 8 everyone We call each of these symmetric, reproducible messages an "update". The request and the response mean the same thing: replace this range of the text at version "1" with "everyone", to make version "2". So the update is symmetric: a client can send it to a server, and a server can send it to a client. And it is reproducible: if any client, server, or intermediary applies it to "Hello world", it gets "Hello everyone". A sequence of updates is a history that any peer can replay, to reproduce the same states, so we call them "replayable". This document defines three kinds of updates: snapshots, patches, and deletions. Range patches, like the one above, are one class of patch, which HTTP's Content-Range header already expresses. The other Braid-HTTP extensions [BRAID-HTTP] build on updates. With Versions [VERSIONS], each update says which point in time it applies to, and which it creates. With Merge Types [MERGE-TYPES], peers merge updates made in parallel. 1.1. Terminology 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 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. This document uses the Augmented Backus-Naur Form (ABNF) notation of [RFC5234], and the rules range-unit and field-content of [RFC9110]. This document uses these terms from Versions [VERSIONS]: Version, Parents, and current version. It also uses these: Update: A request or response that changes the state of a resource, from the state at its Parents to the state at its Version, in a way that any naive peer can reproduce. An update is a snapshot, a patch, or a deletion. Snapshot: An update whose content is the whole representation at its Version. Patch: An update whose content, in a patch format, changes the prior snapshot into the next one. Range patch: A patch that replaces one range of the prior snapshot with its content. Deletion: An update that removes the resource. Prior snapshot: The representation at an update's Parents, which the update applies to. Naive peer: A client, server, or intermediary that knows the specifications an update uses, and the past sequence of updates, but nothing private to the application that sent it. Replay: To apply a sequence of updates, in order, and so reproduce the states they describe. 2. Updates have symmetric requests and responses Each kind of update has a request that a client sends, and an equivalent response that a server sends. The two mean the same thing, on opposite sides of the client/server divide: +-------------+------------------------+-----------------------+ | Update | Request | Response | +-------------+------------------------+-----------------------+ | Snapshot | PUT | 200 (OK) | | Patch | PUT with Patch-Type | 210 (Patch) | | Range patch | PUT with Content-Range | 206 (Partial Content) | | Deletion | DELETE | 410 (Gone) | +-------------+------------------------+-----------------------+ A client sends an update to change the resource on a server. A server sends one in response to a request for the resource's state: for instance, a GET with Parents, which asks for the updates since that version ([VERSIONS], Section 2.3.5), or a subscription [SUBSCRIPTIONS]. A response is an update only when it responds to a GET, whether a client sent the GET, or the GET is a sub-response's implied request [MULTIRESPONSE]. So a 200 or 204 that acknowledges a client's PUT is not an update. An update's Version header says which version it creates, and its Parents header says which version it applies to [VERSIONS]. If an update has no Parents, it applies to the recipient's current version. If it has no Version, the recipient can assign one ([VERSIONS], Section 2.2). A request or response is an update only if a naive peer can apply it. So a POST is not an update, since its server defines what it does. Neither is a 3xx, which changes no state, or a 404, which does not say whether the resource was deleted or never existed. A client says which patches it can apply. It lists the patch formats it accepts in an Accept-Patch header [RFC5789], and the range units it accepts in an Accept-Ranges header (Section 14.3 of [RFC9110]), with the same syntax that a server uses in a response. A server MUST NOT respond with a patch in a format, or a range unit, that the request does not list. When the request lists none that fits, the server responds with a snapshot. 3. A Snapshot is a PUT request or 200 response A snapshot replaces the whole state of a resource. A client sends one in a PUT: Request: PUT /chat Version: ("3") Parents: ("2") Content-Type: text/plain Content-Length: 11 Hi everyone A server sends the same snapshot in a 200, in response to a request for version "3", or for the updates since version "2": Response: HTTP/1.1 200 OK Version: ("3") Parents: ("2") Content-Type: text/plain Content-Length: 11 Hi everyone A snapshot needs no prior snapshot, so any peer can apply it, whatever state it had before. The next patch then applies to it. 4. Patch formats specify deterministic changes A patch changes part of the state, and its patch format specifies how. A patch computes the next snapshot from the prior snapshot, and from nothing else. So a patch MUST NOT depend on anything but the prior snapshot, and its patch format has no merging, no locking, and no preconditions. When peers make updates in parallel, Merge Types [MERGE-TYPES] specify how to merge them. For example, JSON Patch [RFC6902] has a "test" operation, which makes the whole patch fail if a value does not match. That is a precondition, so a JSON Patch sent as an update MUST NOT contain a "test" operation. If a server does not support a patch's format or range unit, it responds with 415 (Unsupported Media Type), and lists the ones it supports in Accept-Patch [RFC5789] or Accept-Ranges. If the patch is malformed, the server responds with 400 (Bad Request), and if it cannot apply to the prior snapshot, with 422 (Unprocessable Content). If a client cannot apply an update, it discards the update, and raises an error. 4.1. Patch-Type allows any patch format, in both directions With Patch-Type, a specification can define any patch format, and peers can send its patches in either direction. A client sends a patch in a PUT, with Patch-Type set to the patch format's media type: Request: PUT /profile Version: ("6") Parents: ("5") Content-Type: application/json Patch-Type: application/json-patch+json Content-Length: 53 [{"op": "replace", "path": "/name", "value": "Mike"}] Content-Type still says what kind of resource this is: a JSON document. Patch-Type says how to read the content: as a JSON Patch to that document. A server sends the same patch in a 210 (Patch): Response: HTTP/1.1 210 Patch Version: ("6") Parents: ("5") Content-Type: application/json Patch-Type: application/json-patch+json Cache-Control: must-understand, no-store Content-Length: 53 [{"op": "replace", "path": "/name", "value": "Mike"}] The 210 (Patch) status code means that the content is a patch to the state at its Parents, in the patch format of its Patch-Type, or, with a Patches header, several patches (Section 4.3). If a client does not know 210, it treats the response as a 200 (Section 15 of [RFC9110]), and takes the patch for the resource itself. So a server sends a 210 only in response to a request whose Accept-Patch lists that patch format (Section 2). A 210 is not heuristically cacheable. A server SHOULD send it with "Cache-Control: must-understand, no-store", so that only a cache that understands 210 stores it (Section 5.2.2.3 of [RFC9111]). An ETag identifies a representation, and the content of a 210 is a patch. So the ETag of a 210 identifies the representation that results from applying the patch. With delta encoding [RFC3229], a server can send a patch in a 226 (IM Used), when the client asks for an "instance manipulation" by name, from a registry of delta formats. Patch-Type uses media types, which a PUT and a 210 share, so each patch format works in both directions. Patch-Type is a Simpler Field [SIMPLER-FIELDS]: an Item whose value is a Token, the media type of the patch format, which a recipient compares case-insensitively. Its parameters are the media type's parameters. If a recipient does not know a patch format, or one of its parameters, it cannot apply the patch, and treats the update as an error (Section 4). 4.2. Range patches are enough for most changes A range patch replaces one range of the prior snapshot with its content. The content can be longer or shorter than the range, or empty: an empty range inserts, and empty content deletes. So range patches are enough for most changes, since any edit to text or bytes is one or more replaced ranges. Range patches are the class of patch that HTTP's Content-Range header already expresses, as a range unit and a range. A client sends one in a Partial PUT (Section 14.5 of [RFC9110]), and a server sends one in a 206 (Partial Content), as in Section 1. When a 206 is an update, it differs from the 206 of [RFC9110] in two ways: its range is a range of the prior snapshot, and its content can have a different length. A server sends it with Parents, so that a recipient knows that it is an update. Its ETag, if any, identifies the representation after the patch applies. That representation differs from any that a cache has stored, so a cache does not combine the 206 with a stored response under Section 3.4 of [RFC9111]. A server sends a 206 update with "Vary: Version, Parents", so that a cache keys it by the version it applies to ([VERSIONS], Section 2.7). If a cache does not implement this document, it does not use the 206 to answer a GET without a Range header (Section 3.3 of [RFC9111]). If it does, it can store the 206 as an update. 4.2.1. This document extends the syntax of Content-Range In RFC 9110, a range unit can define its own syntax in the Range header, with other-range (Section 14.1.1 of [RFC9110]). This document extends Content-Range the same way, and so updates Section 14.4 of [RFC9110]. For a range unit other than "bytes", a Content-Range value is the unit, a space, and a range in the unit's own syntax, which need not end with a length: Content-Range =/ other-content-range other-content-range = range-unit SP other-range-resp other-range-resp = field-content The specification of a range unit defines the syntax of its ranges, and what they mean. The "bytes" unit keeps the syntax of Section 14.4 of [RFC9110]. 4.2.2. This document defines the text range unit The "text" range unit addresses the text of a representation by its Unicode code points [UNICODE]. A recipient decodes the representation with its charset, or as UTF-8 [RFC3629] if it has none, and numbers its code points from zero. A text range is a start position and an end position, in brackets, separated by a colon: text-range = "[" text-pos ":" text-pos "]" text-pos = [ "-" ] 1*DIGIT The range starts at the code point at its start position, and ends just before its end position. So "[6:11]" covers the five code points from 6 to 10, and "[6:6]" covers none, to insert text at position 6. A position that starts with "-" counts back from the end of the text: "-0" is the end itself, so "[-0:-0]" appends text, and "[-1:-0]" covers the last code point. If the end is before the start, or either position is past the end of the text, the patch cannot apply (Section 4). 4.3. An update can contain several patches An update can contain several patches. Its Patches header says how many, and its content is that many patches, one after another, each with its own header fields and content: Request: PUT /chat Version: ("4") Parents: ("3") Content-Type: text/plain Patches: 2 Content-Length: 111 Content-Range: text [0:2] | Patch Content-Length: 5 | | Hello | Content-Range: text [3:11] | Patch Content-Length: 5 | | world | All the patches in one update have the same patch type. Either every patch is a range patch, with its own Content-Range, or every patch is in the format of the update's Patch-Type. Each patch has a Content-Length, which tells the recipient where it ends. A sender SHOULD put at least one blank line between patches, and MAY put more, and a recipient skips them. The update's own Content-Length counts all of its content, blank lines included. A server sends several patches in a 210 (Patch), with a Patches header. Every patch applies to the prior snapshot, in parallel, without knowledge of the others. So in this example, the second range, "[3:11]", is a range of "Hi everyone", even though the first patch makes the text longer, and the update makes "Hello world". The patches in one update MUST NOT change the same part of the state. A future revision of this document might specify when they can, and what happens then. Patches is a Simpler Field [SIMPLER-FIELDS]: an Item whose value is an Integer, the number of patches, which is at least 1. A recipient ignores parameters that it does not know. 5. A deletion is a DELETE request, or a 410 response A deletion removes the resource. A client sends one in a DELETE: Request: DELETE /chat Version: ("5") Parents: ("4") A server sends the same deletion in a 410 (Gone), in response to a request for the state at version "5", or for the updates since version "4": Response: HTTP/1.1 410 Gone Version: ("5") Parents: ("4") Content-Length: 0 A 404 (Not Found) is not a deletion, since it does not say whether the resource was deleted, never existed, or is hidden from this client. If a server wants clients to replay a deletion, it responds with 410. A later snapshot can create the resource again. 6. Peers replay updates to reproduce the same state A peer replays a sequence of updates by applying each one to the state at its Parents. If the updates have no versions, the peer applies each one to the state left by the one before it, in the order it received them. Since every update is reproducible, if two naive peers replay the same sequence, they reach the same state. If an update has a Version, applying it twice has no further effect. If a recipient already has that version, it discards the duplicate, so a client can resend a PUT after losing its response. Two updates are parallel when neither one descends from the other ([VERSIONS], Section 2.1). Merge Types [MERGE-TYPES] specify how a peer merges them, so that every peer reaches the same state. If a peer cannot apply an update, it stops replaying and raises an error, since every later state depends on that update. A peer can send updates one at a time, in requests and responses. A server can also send many in one multiresponse [MULTIRESPONSE], for a span of history, or in a subscription [SUBSCRIPTIONS], as each one happens. 7. Implementation Status This section records the known implementations of this document, as [RFC7942] describes. It is to be removed before publishing as an RFC. - braid-text [BRAID-TEXT], a library for synchronizing text, implements range patches in the text range unit, including positions that count back from the end, and several patches in one update. - The reference JavaScript library [BRAID-HTTP-JS] sends and parses range patches, alone or with a Patches header, in requests and in responses, with a blank line between patches. With a Patches header, it sends Content-Type "application/http-patches", where this document keeps Content-Type for the media type of the resource. - Editor plugins for Emacs [BRAID-EMACS] and Neovim [BRAID-NEOVIM] send versioned PUTs with patches, from HTTP clients of their own, written in Emacs Lisp and Lua. - No implementation yet sends Patch-Type, 210 (Patch), or Accept-Patch and Accept-Ranges in requests. 8. Security Considerations If a server does not implement this document, it can ignore Patch-Type and Patches, and store the patch as the whole resource. It might do the same with a Content-Range that it does not support, although Section 14.5 of [RFC9110] tells it to respond with a 400 (Bad Request). So a client sends patches only to servers that it knows implement this document. Each patch format has its own security considerations, which apply to its patches when they are updates. A patch can be small while its effect is large, and applying it costs the recipient work. A recipient can limit the number of patches in an update, and the size of the state that a patch produces, and treat an update past those limits as an error. When an intermediary applies updates, it computes the resource's state, so it has the same access to that state as a cache that stores it, and the same obligations. Decoding text to count its code points fails on an invalid encoding. If it does, the range patch cannot apply (Section 4). 9. IANA Considerations 9.1. HTTP Status Code IANA is asked to register this status code in the "Hypertext Transfer Protocol (HTTP) Status Code Registry": Value: 210 Description: Patch Reference: Section 4.1 of this document 9.2. HTTP Field Names IANA is asked to register these fields in the "Hypertext Transfer Protocol (HTTP) Field Name Registry": Field Name: Patch-Type Status: permanent Structured Type: Item Reference: Section 4.1 of this document Field Name: Patches Status: permanent Structured Type: Item Reference: Section 4.3 of this document 9.3. HTTP Range Unit IANA is asked to register this range unit in the "HTTP Range Unit Registry": Name: text Description: a range of the Unicode code points in the text of a representation Reference: Section 4.2.2 of this document 9.4. Accept-Patch and Accept-Ranges Section 2 defines the use of Accept-Patch and Accept-Ranges in requests. IANA is asked to add this document as a reference for both fields in the "Hypertext Transfer Protocol (HTTP) Field Name Registry". 10. References 10.1. Normative References [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997. [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003. [RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, January 2008. [RFC5789] Dusseault, L. and J. Snell, "PATCH Method for HTTP", RFC 5789, March 2010. [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, May 2017. [RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, June 2022. [RFC9111] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Caching", STD 98, RFC 9111, June 2022. [SIMPLER-FIELDS] Toomim, M., "Simpler Fields for HTTP", draft-toomim-httpbis-simpler-fields-00, work in progress, November 2026. [UNICODE] The Unicode Consortium, "The Unicode Standard", . [VERSIONS] Toomim, M., "Resource Versioning for HTTP", draft-toomim-httpbis-braid-versions-00, work in progress, November 2026. 10.2. Informative References [BRAID-EMACS] braid.org, "braid-emacs", . [BRAID-HTTP] Toomim, M., Little, G., Walker, R., Bellomy, B., and J. Gentle, "Braid-HTTP: State Synchronization for HTTP", draft-toomim-httpbis-braid-http-05, work in progress, November 2026. [BRAID-HTTP-JS] braid.org, "braid-http", . [BRAID-NEOVIM] braid.org, "braid-neovim", . [BRAID-TEXT] braid.org, "braid-text", . [MERGE-TYPES] Toomim, M., Milutinovic, M., and B. Bellomy, "Consistent Merge Types for HTTP", draft-toomim-httpbis-braid-merge-types-00, work in progress, November 2026. [MULTIRESPONSE] Toomim, M., "Multiresponses for HTTP", draft-toomim-httpbis-braid-multiresponse-00, work in progress, November 2026. [RFC3229] Mogul, J., Krishnamurthy, B., Douglis, F., Feldmann, A., Goland, Y., van Hoff, A., and D. Hellerstein, "Delta encoding in HTTP", RFC 3229, January 2002. [RFC6902] Bryan, P., Ed. and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Patch", RFC 6902, April 2013. [RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, July 2016. [SUBSCRIPTIONS] Toomim, M., "Subscriptions for HTTP", draft-toomim-httpbis-braid-subscriptions-00, work in progress, November 2026. Acknowledgements Range Patch (draft-toomim-httpbis-range-patch), by Mitar Milutinovic, Michael Toomim, and Bryn Bellomy, is the basis for the range patches in this document. Author's Address Michael Toomim Invisible College, Berkeley 2053 Berkeley Way Berkeley, CA 94704 EMail: toomim@gmail.com Web: https://invisible.college/@toomim