Skip to content

Normative

Conformance profiles

The conformance clause states what an implementation must build, as core plus the modules it claims, and it takes those identifiers from this page. This page is the other half: the capabilities that are allowed to be optional, each one named, tiered, and bounded by what omitting it costs.

It is an index, not a definition. Every normative sentence stays exactly where it already lives. A row below names sections and states an omission behaviour; it moves nothing, and no section acquires a conditional because a module exists. That is the same relationship SOVM/1 registry has to the values it lists, one level up: there the unit is an identifier, here it is a whole capability.

This page also fixes the mechanism it instantiates: the three tiers, the test that assigns a capability to one, and the rule that modularity is an index over whole capabilities rather than an axis threaded through the text. Keeping the list of modules true to what the specification actually defines is this page’s obligation, and the checks in make drift-checks are what enforce it.

Optionality has exactly three tiers, and a capability MUST NOT become optional by any fourth mechanism. What distinguishes them is not how important a capability feels; it is who bears the cost when it is absent.

Tier Name Enforced by Granularity
1 Core requirement Nothing — always required SOVM/1
2 Negotiated capability The SSP capability handshake Per session
3 Profile requirement A mesh-scoped requirement in authenticated, ordered state, consulted by whatever enforces membership Per mesh or per domain

The assignment is made by one test, and it is the load-bearing safety rule of the whole construct: a capability may be tier 2 only if omitting it cannot weaken a security or policy guarantee for any participant other than the node omitting it. Anything else is tier 3, and an unclear case is tier 3 by default. The handshake is unsigned and precedes authentication, so a tier-2 bit is strippable on-path by an attacker who forges nothing and leaves no signature to check. A guarantee placed there is not a guarantee.

The tier-3 row above states a requirement, not a mechanism. A carrier is whatever holds a mesh’s requirement and makes it enforceable, and SOVM/1 has two: the capability declaration over the MLS control group, and the capability-requirement chain over the scope layer. A mesh running both is subject to both, and registry §55.3 is where the two are read together.

Two carriers are two, not many. Adding a third would be a change to this page, and it may be made only by something with all four of these properties. A carrier MUST be:

  • Authenticated — signed under the substrate signature profile, or by the library that owns group membership. An unsigned statement is a tier-2 statement whatever it is carried in.
  • Mesh-scoped — bound to the mesh, or to the group standing for it, and not transferable to another mesh.
  • Ordered and fork-detectable — changed only by an authorized, ordered transition, with two conflicting transitions surfaced as evidence rather than silently resolved in favour of either.
  • Consulted by the component that enforces membership — not by a checker running alongside it. A requirement that can be renegotiated per session, per connection or per member is the tier-2 mechanism under a different name.

The properties are what the tier test needs to hold. Drop the first and the requirement is strippable on-path; drop the second and it is replayable into a mesh that never adopted it; drop the third and a fork picks the weaker floor; drop the fourth and the enforcement point never sees it. None of the four is about which library carries the bytes, which is why the tier does not name one.

Each module below states four things and nothing else:

  • Tier — which of the three, and the sentence of the tier test that assigns it. Where the classification is the fail-safe default rather than a settled answer, the row says so and names the question a later revision must resolve.
  • Comprises — the whole sections the module covers. A candidate that cannot be described as a set of whole sections plus a single omission behaviour is not a module; it is an axis wearing a module’s clothes.
  • Omitting it — the one behaviour an implementation without the module must exhibit. One behaviour, not a branch.
  • Notes — only where a consequence is product-visible or a gate is still open. Two rows below carry consequences a reader deserves in plain words rather than smoothed over; they are SOVM-HIST and SOVM-DEL.

A module may also carry its own specification, and where one exists the row names it. What a row cannot hold is the other side of the claim — what an implementation that claims the module owes, and the gates that must close before either statement binds anybody. Those need a document with a status, and the reference chain stays one hop at each level: SOVM/1 registry reserves the identifier and points here, the row here points at the specification. Two carry one so far: SOVM-SYNC-RT, and SOVM-IROH, whose specification is the binding page itself.

Identifier Tier Capability Omitting it
SOVM-BASE 1 The floor: substrate, identity, host interface Not expressible — in every module set by construction
SOVM-SYNC — The synchronization layer, whole The host-only profile: supply the host interface from elsewhere and claim SOVM-OBJ
SOVM-OBJ — The object plane, whole — including deletion machinery’s base rules A sync-only mesh: mutable state, no encrypted object plane
SOVM-SYNC-RT 2 Realtime push mode Advertise supports_realtime: false; both peers run batch
SOVM-SEQ 3 Blind Commit sequencer Cannot hold the sequencer role for any MLS group; no participation in sequenced domains
SOVM-DEL 3 APPEND_DELETE domains: deletion, GC, compaction, placement Fail closed on the delete object kind; refused admission to an APPEND_DELETE domain
SOVM-HIST 3 Historical access beyond FORWARD_ONLY Every admission it performs is FORWARD_ONLY
SOVM-BLOB — Unstructured objects as chunked-AEAD blobs Fail closed on blob and SOP-BLOB-1
SOVM-IROH — The iroh transport binding Run another binding satisfying transport §24 — at least one transport role is required

The floor. Tier 1.

SOVM-BASE names what may never become optional: the shared substrate in full — the signature profile, the hash convention, the context principle, the chained-record schema — plus root identity and its custody rules, and the host interface obligations a node owes whichever layers it carries. It is in every module set by construction (registry §55.1); there is no set that omits it and no conformance claim that survives its absence. It exists as an identifier so that a module set is a complete statement rather than a statement with an implied preamble, and so the corpus has a partition for the cases every implementation must pass.

The synchronization layer, whole. Not tiered — a layer claim, not a capability inside a specification: omitting it is the host-only profile, in which an implementation supplies the host interface from elsewhere and claims SOVM-OBJ alone.

Comprises everything in SSP/1 that no other module names: identity, pairing and vouching, the change event, canonical encoding, event identity and authentication, the hybrid logical clock, the apply chokepoint and the merge semantics above it, the scope floor, the batch sync exchange, the applied watermark, the COSE profile, and the fail-closed inventory in its entirety.

Omitting it is not expressible. Tier 1 is not a module that happens to be mandatory; it is the set of things no revision may reclassify at all, and a stack missing any part of it is not an SSP/1 implementation and must not claim to be one. The four shared conventions and everything the fail-closed inventory names are tier 1 by construction.

SOVM-SYNC is listed here for the same reason a floor is drawn on a plan: so that the boundary between what is optional and what is not is a line on this page rather than an inference from the absence of a row.

The object plane, whole. Not tiered — a layer claim: omitting it is a sync-only mesh, mutable state with no encrypted object plane.

Comprises everything in SOP/1 that no other module names: identity, trust and MLS, cryptographic domain groups, application domain KEKs and COSE, control-plane messaging, the Parquet modular encryption profile, the object model, append-only tables, the at-rest invariant, compaction, and garbage collection, revocation and purge. It also comprises the obligation every member carries in a sequenced mesh — see SOVM-SEQ, which is the role, not the obligation.

Omitting it is not expressible, on the same reasoning as SOVM-SYNC.

Realtime push mode. Tier 2 — the only tier-2 module, and the reason the tier exists.

Comprises realtime mode — §33.1 through §33.4 and the push behaviour of the preamble — together with the entitlement to answer the capability handshake’s supports_realtime member true, and the requirement in transport §24 that a binding claiming realtime mode carry unsolicited one-way bodies.

The member itself, the realtime value in the enumerations, and §33’s rule that a receiver MUST reject an unsolicited body where the capability was not mutually established are all SOVM-SYNC — the last because the fail-closed inventory carries it and the inventory is tier 1 entire. An omitting node needs that refusal more than anyone; the module’s §2.1 draws the cut.

Omitting it: an implementation that does not hold live subscriptions MUST advertise supports_realtime: false — the member is answered, not withheld — and every session it takes part in runs in batch mode. No peer loses anything it was relying on, because batch is already the correctness backstop: a node in realtime mode must run its periodic batch cycle anyway, and an implementation whose only delivery path is push has taken on an exactly-once obligation the transport cannot meet.

Why this one is tier 2. Run it through the test. Stripping supports_realtime on-path degrades one session to batch. Delivery semantics, cursors, idempotent apply and convergence are unchanged; the cost is latency, and it falls on the two peers in that session. Nobody else’s guarantee moves. That is what a legitimate local trade looks like, and it is why the handshake is the right home for exactly this kind of switch and the wrong home for every other module on this page.

Specification: SOVM-SYNC-RT — what claiming and omitting the module each oblige an implementation to do, where the boundary against SOVM-SYNC falls, and three open gates. Until the first of those closes, omitting realtime is unmentioned by SSP/1 rather than permitted by it, and this row is a reservation like every other on the page.

Blind Commit sequencer. Tier 3 — by the fail-safe default, not by a settled answer. See the note below.

Comprises the sequencer itself, its state, bootstrap, singularity and recovery rules, and relay unavailability.

Omitting it: an implementation that omits SOVM-SEQ MUST NOT hold the sequencer role for any MLS group. It can still be an ordinary member of a sequenced mesh, because the member-side obligation — routing Commits through the group’s bound sequencer, and never around it — is not part of this module. That obligation is SOVM-OBJ, and it stays there deliberately: a member that bypasses the sequencer forks the group for everyone, so the obligation fails the tier test outright and can never be optional. What is optional is the capacity to serve the role.

APPEND_DELETE domains. Tier 3.

Comprises the APPEND_DELETE workload class and append-and-delete tables in full — delete objects, permanent identity tombstones, permanent tombstone knowledge, delete pruning and row-identity filters, query state, re-insertion, and delete acknowledgement — plus placement, GC and compaction: placement travels inside this module, because GC’s correctness assumes deletes exist and a capability whose correctness invariant depends on another is declared together with it. Its text is the placement subsection below.

Omitting it: an implementation MAY omit SOVM-DEL and still conform. It then simply cannot participate in domains whose workload class is APPEND_DELETE. A node that has not declared SOVM-DEL over the MLS control group MUST be refused admission to such a domain — refused, not admitted in a degraded mode. SOVM-DEL MUST NOT be an SSP-handshake capability.

The implementation burden may be modular. The policy guarantee may not be.

That sentence is the whole of this row, and it is worth spelling out why it is not a slogan. Deletion in SOP/1 is not a local operation: permanent identity tombstones are permanent logical knowledge, and every newly published or newly discovered object must be checked against applicable tombstone knowledge before its rows enter queryable state. Deletion therefore holds only if every participant honours it. One node that accepts writes without checking tombstone knowledge resurrects deleted rows for the whole mesh, and does so with no cryptographic evidence that anything went wrong. A capability whose absence does that to other people cannot live in an unsigned handshake bit, and the rule that follows is narrower than “deletion is optional”: the node may decline the work, and declining the work excludes it from the domains where the guarantee is owed.

Admission is the gate; it is not the whole of the enforcement. Once a node is admitted to an APPEND_DELETE domain, tombstone enforcement is a domain-level floor for every admitted member: the obligation to check each newly published or discovered object against applicable tombstone knowledge is not further negotiable per session, per connection or per object. SOVM-DEL gates who gets in; it does not create a mode in which an admitted member checks less.

Placement and blind replicas, inside SOVM-DEL

Section titled “Placement and blind replicas, inside SOVM-DEL”

Placement is not a module of its own: GC’s correctness assumes deletes exist and placement is where GC bites, and coupled capabilities are declared together as one requirement.

Placement and blind replicas. Tier 3.

Comprises placement and replication in full: scope is not placement, blind replicas, and metadata-only knowledge.

Omitting it is described by the three paragraphs below, which are the capability’s normative account.

Placement is the capability in which a deployment expresses where ciphertext physically resides, as a set formally distinct from who may decrypt it (SOP/1 §16). An implementation that omits it has no vocabulary in which to express a constraint on the physical storage location of its ciphertext.

A deployment that asserts, within this protocol, a physical-placement constraint on ciphertext (for example, “ciphertext for this domain is stored only on nodes in region R”) MUST implement this capability — that is, MUST claim SOVM-DEL. This is a profile-conformance requirement enforced at admission, not a per-session negotiation: a placement constraint a peer can decline is not a constraint.

The placement capability provides the vocabulary to express and enforce placement; it does not, by itself, establish that any placement configuration satisfies a given jurisdiction’s data-localisation or cross-border-transfer law. Whether a configuration meets a legal obligation — for example EU/UK GDPR or Swiss FADP transfer rules, or storage-localisation mandates such as Russia’s Federal Law 242-FZ or China’s PIPL — depends on the operator’s deployment and is determined in operator documentation, not in this specification. In particular, the distinction between holding ciphertext and holding decryption keys, though load-bearing for this design, is not treated as dispositive by every regime: several localisation laws attach to processing operations and to the location of readable personal data, and under GDPR/UK GDPR an encrypted cross-border replica remains a “transfer.” A deployment MUST NOT represent implementation of the placement capability as, in itself, compliance with any such law.

The third paragraph is not decoration and does not ship separately from the first. It is the same honesty rule SOP/1 §19.7 already imposes on this specification’s own language, applied to the one place where a reader is most likely to hear a legal conclusion in a protocol statement.

What the guarantee actually is, and what it is not. The residency-relevant guarantee is the conjunction of two things, not either one alone: a placement constraint on where ciphertext resides, and the separation of key custody from that placement — the blind-replica model, in which a replica holds bytes it has no path to read (§16.1, §16.3). Placement alone is materially weaker: a deployment that pins bytes to jurisdiction X while the keys that open them remain reachable in X has constrained storage location and nothing else, and it should not be described as though it had constrained who can read the data. This capability is where both halves are expressed, and a deployment claiming residency on the strength of one half is claiming more than it holds.

The claim is also scoped to content ciphertext. Object metadata is a separate question with its own account (§16.4) and is not covered by a placement constraint on content; a deployment answering a residency question MUST address it on its own terms rather than folding it into the ciphertext claim.

Historical access beyond FORWARD_ONLY. Tier 3.

Comprises FULL_HISTORY, BOUNDED_HISTORY, COSE key-history packages, and historical access and revocation.

That historical access is explicit and FORWARD_ONLY itself stay in SOVM-OBJ. Every implementation must be able to admit a node without granting it the past; only the modes that grant more are modular.

Omitting it: an implementation that omits SOVM-HIST MUST admit every node FORWARD_ONLY. It retains no historical KEK generations for provisioning and cannot admit a member to a domain whose retention policy promises more than the current generation.

This is the right default and the worse recovery story, and both halves are true. Making historical access optional means FORWARD_ONLY is the default and FULL_HISTORY is opt-in. On the secure-defaults lens that is plainly correct: the default retains less key material, silent unbounded key retention is the worse failure of the two, and a node that joins late simply cannot read what came before it. But it is also, and unavoidably, the worse recovery story. A mesh that never opts into SOVM-HIST and loses its only long-lived member has lost that data permanently. No later admission recovers it, because the key generations that would have unwrapped it were never retained by anyone else.

That consequence is product-visible rather than an implementation detail, and it is stated here in those words rather than smoothed over, because a deployment choosing the default is choosing it — and someone has to have written down what the choice costs.

SOVM-HIST is a retention and entitlement module, not a cryptographic one. Historical access is already parameterised on the wire as an entitlement class; nothing in this module alters how keys are derived or wrapped, and nothing in it may acquire that power.

Unstructured objects as chunked-AEAD blobs. Not tiered — see below.

Comprises the SOP/1 blob extension in full, and the two registered values it defines: the blob object kind and the SOP-BLOB-1 encrypted-object format.

Omitting it: a node that has not adopted the extension fails closed on blob and SOP-BLOB-1, which is the behaviour the registry already requires of it for any unregistered value in either column. It refuses the object; it does not guess, and it does not silently drop it.

Why it carries no tier, and why that is not a fourth mechanism. The three tiers govern how a capability inside a specification is allowed to be optional. An annex is not inside one. It is a separate hand-authored document layered on the extension points its host reserves, carrying its own status and its own decision gates — so omission is the default state and adoption is the act, with no mechanism needed to make omission safe beyond the fail-closed rule SOVM/1 already applies to every unregistered value. It is listed here because a reader asking “what must I build?” needs to see it in the same table as everything else, not because it is a fourth way to be optional.

The iroh transport binding. Not tiered — a binding claim, not a capability inside a specification: a binding specification maps SOVM/1’s messages onto a named transport and adds no semantics, so what is claimable is the mapping, whole, and the three tiers do not reach it.

Comprises the binding page in full: the sovm/0 ALPN, channel-key connection identity and the in-band presentation of the transport binding record, the stream mapping and framing, object transfer over iroh-blobs, the relay posture, and the registered error codes.

Omitting it: run another binding satisfying transport §24, or run mailbox-only. Transport §23 requires a mesh to run at least one transport role; this binding is one way of meeting that requirement, never the requirement itself. An implementation states what its binding provides either way (registry §55.1, item 6).

Specification: SOVM-IROH — how each row of transport §24 is met, the streams and frames, what a relay operator sees, and its three gates, all of which are now closed. The gates having closed retires the reservation on the binding; the claim itself is earned the same way as every other on this page, by the mapping and the vectors.

A conformance claim is checkable only against something. These are the corpora an implementation is checked against, served as data so a reader can count the cases rather than take a page’s word for them.

Corpus Covers Served at
sop1-conformance-v1 Registry literals, deterministic CBOR, BLAKE3 identifiers, frame construction, transport binding /vectors/manifest.json
ssp1-conformance-v1 Change-event canonical encoding, COSE_Sign1 construction over synthetic keys, event identity, the section 13 merge — the row a delivery order materializes, and the row every order of a set converges on — and the fail-closed inventory — every row of it — as executable negatives /vectors/manifest.json

/vectors/index.json reports each corpus’s case count, broken down by the module the cases belong to. A corpus with no cases is reported as zero rather than omitted: a corpus that is empty and a corpus that is absent look the same from outside, and only one of them is the honest description. Neither corpus is complete, and what each one does not yet reach is stated where it bites: SSP/1’s limitations name convergence over the event sets the corpus does not enumerate as the gap that blocks production use.

Every case names the module it belongs to, so an implementation is checked against the partition it actually claims rather than against the whole corpus. A case whose module an implementation does not claim is reported as skipped and named, never silently dropped — a claim narrowed to nothing would otherwise pass by covering nothing.

Each case also pins the specification anchor it evidences. A section that moves without the case following it fails the corpus rather than the page, which is what keeps the vectors and the prose describing the same protocol.

A capability becomes a module only if all of the following hold. They are restated here rather than linked alone because this is the page an author will be looking at when the question arises.

  • It passes the tier test, and the tier is recorded. A capability may be tier 2 only if omitting it cannot weaken a security or policy guarantee for a participant other than the node omitting it. Unclear cases are tier 3.
  • It is a set of whole sections plus a single omission behaviour. A candidate that needs conditional normative text inside a section is not a module; it is a profile axis threaded through the sections, which makes every sentence conditional on deployment state. That shape is forbidden, and admitting a module in it re-opens the question this page exists to close.
  • Its dependencies travel with it. Where a capability’s correctness invariant depends on another capability being present, both MUST be declared together as a single tier-3 profile requirement, never listed as independent optional capabilities. A declaration that is true in isolation and false in combination is worse than no declaration.
  • Its identifier is registered in the same change. SOVM/1 registry is the one namespace, and registering there is what makes an unknown module a fail-closed miss rather than a new failure mode.
  • Redefinition takes a new identifier. A change to what a module comprises that an existing implementation could not satisfy is a new identifier, never a silent redefinition of the old one. A conformance identifier that means different things at different times is the one thing a conformance identifier may not be.

Adding, retiring or re-tiering a module is an edit to this page and to the sections it names, and to nothing else. Changing the mechanism — the tiers, the test, or the floor — is a different and much larger act, and this page is not where it happens.