Normative · Part
Message formats
Every message is a CBOR map. Deterministic encoding ([RFC 8949] section 4.2.1) is REQUIRED wherever bytes are hashed or signed; message envelopes themselves are neither, and need not be deterministic — though emitting them deterministically anyway is the simpler implementation.
30. Version negotiation
Section titled “30. Version negotiation”Every message carries an explicit ssp_version member, an integer. This
specification defines the value 1.
A receiver encountering an unknown ssp_version MUST fail closed — refuse the
exchange rather than attempt a best-effort parse.
The member is in-band deliberately. Delegating versioning to a transport identifier — on the reasoning that the transport already distinguishes versions — couples protocol identity to transport choice: the same protocol over a second transport becomes unversioned, and a version bump requires a transport change. An in-band member costs a few bytes and makes the message self-describing.
31. Capability handshake
Section titled “31. Capability handshake”Exchanged before the first sync of a session.
| Member | Type | Description |
|---|---|---|
ssp_version |
unsigned | 1 |
site_id |
text | Sender’s site_id |
have_max_hlc |
map | Highest HLC the sender holds: {physical_ms, logical}; advisory, never a selection bound (see sync exchange) |
supports_realtime |
bool | Whether the sender can hold a live subscription; see realtime mode |
secure_channel |
bool | Whether the binding authenticated the peer |
rtt_ms |
unsigned or null | Sender’s measured round-trip estimate, advisory |
A receiver MUST fail closed to batch mode on any unknown member value rather than guessing a capability.
secure_channel is advisory and MUST NOT be treated as a security input. A peer can
claim anything; the receiver knows what its own binding provided. It exists so a node
can warn when a peer believes the channel is authenticated and the local side
disagrees, which usually indicates a misconfigured binding.
32. Sync exchange
Section titled “32. Sync exchange”The same shape travels in both directions, which keeps a sync a single symmetric round trip rather than two asymmetric protocols.
| Member | Type | Description |
|---|---|---|
ssp_version |
unsigned | 1 |
from_site_id |
text | Sender’s site_id |
have_max_hlc |
map | Highest HLC the sender holds; advisory, never a selection bound |
events |
array of byte strings | Zero or more encoded COSE_Sign1 change events, forwarded byte for byte |
records |
array of byte strings | Zero or more encoded COSE_Sign1 control records — vouches, role assignments, transport bindings, event-signing subkeys, succession records, authorization records. MAY be empty; MUST be present |
applied_watermarks |
array | Zero or more watermark records |
Events travel as the exact bytes their producer signed — event identity depends on it — so the envelope carries them as opaque byte strings rather than re-encoding them.
records is the wire path for the durable control records the
registry defines. Each is
self-verifying, so a receiver processes them independently of the events in the same
exchange and in any order; a record that fails verification is dropped and surfaced
without affecting the rest of the body.
Selection is cursor-driven. A sender — requester and responder alike, the exchange being symmetric — MUST select the events it offers from its own durable per-peer cursor, bounded by an implementation-chosen batch limit.
have_max_hlc is advisory and MUST NOT be used as a selection lower bound. It is a
single scalar spanning every site the sender holds events from, so a peer whose
maximum came from one site says nothing about what it holds from any other. Selecting
at or above it would exclude every event a second site produced below that scalar —
and because a peer’s maximum only rises, the next cycle excludes them again. The gap
is permanent: a convergence failure, not a delay.
What a sender MAY do with have_max_hlc is size a first exchange and report how far
apart two nodes are. The mechanism that lets it safely stop re-offering data is the
applied watermark, which is per
(site_id, schema, table) and so states what a peer holds rather than only how far
its clock has run.
The cursor’s bound is inclusive — at or above — deliberately. An exclusive bound loses any event sharing the boundary HLC with one already delivered, and because apply is idempotent the cost of re-sending a boundary event is nil while the cost of skipping one is a permanent gap.
32.1. Self-echo suppression
Section titled “32.1. Self-echo suppression”A receiver MUST drop events whose protected kid equals its own site_id.
This is normative and unconditional. Without it, an event that a node authored and that returns via a peer is re-ingested, re-offered, and can circulate indefinitely — and the combination of an inclusive selection bound and at-least-once delivery makes that loop reachable in normal operation rather than only under attack.
The rule is stated here, where the exchange itself is defined, so it applies to batch and realtime alike: a rule stated only inside one mode’s description is a rule the other mode’s implementer omits.
32.2. Liberal parsing bound
Section titled “32.2. Liberal parsing bound”A receiver MUST reject a sync body that is not a CBOR map with exactly the members above.
A receiver that is liberal at this boundary — reading a bare array as an event list and anything else as empty — converts a malformed or hostile body into a silent no-op, which is indistinguishable from a peer with nothing to send. SSP/1 rejects explicitly instead, so a protocol error surfaces as one.
33. Realtime mode
Section titled “33. Realtime mode”The registry registers two transport modes,
batch and realtime. Everything above this section describes batch mode:
cycle-driven request/response synchronization. Realtime mode is the second, and it
is deliberately not a second protocol: a realtime push is a sync-exchange body
— the same CBOR map, the same members, the same rules — sent unsolicited over a
held live connection as events are produced, with no response awaited.
Realtime mode operates only when both peers advertised supports_realtime in
the capability handshake. A sender MUST NOT push unsolicited bodies to a peer that
did not advertise it, and a receiver MUST reject an unsolicited body on a session
where the capability was not mutually established — reject, not drop, so a
protocol error surfaces as one.
The advertisement itself is bound. An implementation MUST advertise
supports_realtime: true only where both hold:
SOVM-SYNC-RTis in its stated module set (registry §55.2); and- the session’s binding can in fact carry peer-initiated one-way bodies (transport §24).
Otherwise it MUST advertise false. The advertisement is a property of the
session and not of the build: the same implementation reached over
the mailbox advertises false there
and is not in breach.
An implementation that omits SOVM-SYNC-RT MUST still emit and parse the member, MUST
still reject an unsolicited body, and MUST still recognise realtime as a
registered transport mode. The member does not leave the protocol with the
capability — that is the boundary SOVM-SYNC-RT
§2.1 draws, and it is
why an omitting node stays interoperable rather than becoming a dialect.
The obligation has to bind the sender, because the handshake is unsigned and
precedes authentication, so a peer cannot tell a false true from a true one.
What a peer observes is pushes that never arrive, which is indistinguishable
from a quiet link. An advertisement nobody can check is worth exactly what the
advertiser’s conformance claim is worth — which is why this rule and
registry §55.2 are one rule stated twice,
once about a document and once about a wire member.
Realtime mode changes when a body is sent — at production time rather than on the next cycle — and nothing else:
- Delivery semantics are unchanged. At-least-once delivery, idempotent apply, self-echo suppression and the watermark rules apply to a pushed body exactly as to a requested one.
- Cursors remain authoritative. A sender advances its durable cursor only on a successful push, solicited or not. A push that fails, or whose outcome the sender never observes, is re-offered by the batch path.
- Ordering is still not guaranteed. A pushed body may arrive before, after or interleaved with overlapping batch bodies. Idempotent apply absorbs every interleaving, and a receiver MUST NOT infer order from the mode a body arrived through.
33.1. Batch remains the correctness backstop
Section titled “33.1. Batch remains the correctness backstop”Realtime mode is a latency optimization, never a correctness mechanism — the same posture as applied watermarks. A node operating in realtime mode MUST continue running its periodic batch cycle.
That rule is what allows the push path to be cheap. A push may be lost to disconnection, backpressure or a crash without threatening convergence, because everything it failed to deliver is re-offered from the durable cursor on the next cycle. An implementation whose only delivery path is push has quietly taken on an exactly-once delivery obligation the transport cannot meet, and will not converge.
33.2. Coalescing
Section titled “33.2. Coalescing”A sender MAY coalesce events produced faster than the link drains into fewer, larger pushed bodies, and the sender-enforced events-per-response limit applies to a pushed body unchanged. A receiver MUST NOT assume one event per push, one push per event, or any particular pacing.
33.3. What realtime mode reveals
Section titled “33.3. What realtime mode reveals”A push at production time is a timestamp on the wire. Whoever can observe the channel — a relay operator, a network-path observer — learns the mesh’s activity rhythm even with payloads encrypted and padded, because padding hides sizes, not timing. Where that matters, a sender SHOULD flush on a fixed cadence rather than on production, which converts the timing channel into a constant signal at the cost of one cadence interval of latency. The trade is a deployment policy, not a protocol member.
33.4. Scope of the mode
Section titled “33.4. Scope of the mode”Realtime mode exists only on a live binding; the mailbox is store-and-forward by definition and nothing in this section applies to it. A binding claiming realtime mode MUST carry peer-initiated one-way bodies (see framing), and trust resumption applies to a held session exactly as to a fresh one.
Where SOP/1 is deployed, the same held connection typically also carries the object plane’s MLS-protected control messages (SOP/1 section 10) — which is how a freshly published object’s catalog entry reaches online peers with the same immediacy. That path is the object plane’s, not this one’s; realtime mode carries only SSP/1 bodies.
34. Applied watermark
Section titled “34. Applied watermark”| Member | Type |
|---|---|
site_id |
text |
table_schema |
text |
table_name |
text |
hlc_physical_ms |
unsigned |
hlc_logical |
unsigned |
Semantics, monotonicity and the fail-closed rule are in semantics.
Watermarks are not signed. They are an efficiency hint, not an authorization input, and a peer that lies about its watermark harms only its own convergence. A receiver MUST NOT grant anything on the basis of a watermark.