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.
The three tiers
Section titled “The three tiers”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.
What a tier-3 carrier must be
Section titled “What a tier-3 carrier must be”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.
How a row is read
Section titled “How a row is read”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-HISTandSOVM-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.
The modules
Section titled “The modules”| 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 |
SOVM-BASE
Section titled “SOVM-BASE”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.
SOVM-SYNC
Section titled “SOVM-SYNC”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.
SOVM-OBJ
Section titled “SOVM-OBJ”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.
SOVM-SYNC-RT
Section titled “SOVM-SYNC-RT”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.
SOVM-SEQ
Section titled “SOVM-SEQ”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.
SOVM-DEL
Section titled “SOVM-DEL”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.
SOVM-HIST
Section titled “SOVM-HIST”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.
SOVM-BLOB
Section titled “SOVM-BLOB”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.
SOVM-IROH
Section titled “SOVM-IROH”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.
Conformance vectors
Section titled “Conformance 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.
Adding a module
Section titled “Adding a module”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.