Skip to content

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.

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.

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.

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.

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.

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.

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:

  1. SOVM-SYNC-RT is in its stated module set (registry §55.2); and
  2. 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.

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.

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.

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.

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.