Skip to content

Normative · Part

Errors, limits and timers

Every bound below is normative. Each links to the mechanism it constrains, because a constant without its mechanism is not implementable.

The single most important table in this specification: every boundary where SSP/1 chooses safety over availability.

Condition Behaviour Defined in
Invalid event signature Drop and surface — never hold, so a forger cannot stall the impersonated node’s stream semantics
Unknown sender kid Drop and surface data model
Unknown critical COSE header or content type Reject registry
Clock skew beyond bound Reject, never clamp data model
Watermark advance past a skew-rejected event Never advance — the rejected HLC is above every honest one, so advancing would acknowledge the site’s whole unapplied backlog data model
No HLC high-water mark covering every event the node has released, or a mark that fails its integrity check Refuse to stamp further events under that site_id — never resume from the wall clock. Surface the two as distinct conditions data model
Out-of-scope event Drop and log, never raise semantics
row_key null, empty, or naming any column the destination lacks Drop — never partially resolve a key, which would address every row sharing the surviving prefix semantics
No matching policy row Deny scope
Incomparable maximal policy rows Deny and log, never guess open scope
Purge requested Error, never downgrade to soft delete scope
Identity conflict on existing site_id Hard fail, never overwrite identity
Pairing signature failure Abort, no downgrade identity
Pairing ceremony deadline exceeded Abort — never extend the deadline, and never resume the ceremony that ran past it identity
Node below a policy-required key_protection level Refuse, never admit at a lower level identity
hardware_attested claimed with absent, malformed, expired, revoked or mismatched evidence Treat as software — never as hardware_unattested SOP/1 §5.2
Attestation revocation data unobtainable or stale beyond the configured bound Treat the attestation as unverified SOP/1 §5.2
Non-compressed encoding of a node root public key Reject — never convert signature profile
Non-low-S signature Reject — never normalise signature profile
DER-encoded or non-64-byte signature Reject signature profile
Public key failing point validation Reject before use signature profile
Hardware signer’s verify-after-sign check fails Abort — never release the signature SOP/1 §5.4
Conformance claim naming a module identifier the registry does not register Reject the claim — never ignore the identifier and evaluate the rest registry
supports_realtime: true about to be advertised by an implementation that does not claim SOVM-SYNC-RT, or on a binding that cannot carry unsolicited one-way bodies Advertise false — never advertise a capability not held messages
Unknown ssp_version Refuse the exchange messages
Malformed sync body Reject messages
Unsolicited push without mutual supports_realtime Reject messages
NaN or infinity in canonical CBOR Refuse to serialize data model

Eight of the signature-path rows exist because a randomised signature algorithm with an ambiguous public-key encoding has more boundaries than a deterministic one with a unique encoding — the honest cost of a hardware-rooted identity, not a hidden one. The two conformance-claim rows are where the module-set assertion of registry §55.1 becomes checkable on the wire, because an assertion nothing checks is not an assertion.

The one deliberate exception is eviction, which fails open and keeps the row. The asymmetry is the justification: failing open wastes local storage, failing closed destroys data.

Volume is deliberately absent from the table, and its absence should not read as an oversight. An authorized peer that floods a receiver with well-formed events — including the existence-only upserts that semantics §13.6 makes cheap to produce — crosses no boundary at all: it is inside the trust boundary, its signatures verify, and every row above passes it through. Fail-closed has nothing to close on. The bound is §37 instead, whose receiver-side limits — materialization batch size and backfill rows per cycle — a receiver enforces on its own authority rather than trusting the sender’s events-per-response, together with whatever rate control the binding supplies.

Bound Value Rationale
Clock skew tolerance 300 000 ms Bounds a hostile clock’s ability to win every future comparison
Vouch validity 86 400 s Long enough to seat a node across a brief outage; short enough to bound replay
Vouch future-dating tolerance 300 s Accommodates ordinary clock drift between honest nodes
Vouch quorum k 2 where the mesh has 3+ nodes, else 1 A quorum is meaningless below three participants
SAS strength ~39.9 bits log₂(10¹²); see SAS
Fingerprint display First 4 bytes of site_id Display aid only, never an authentication input

These MUST be documented but are not fixed by the protocol, because the right value depends on deployment rather than on correctness.

Limit Constraint
Events per sync response Finite; MUST be enforced by the sender
Materialization batch size Finite
Backfill rows per cycle Finite
Sync cycle interval The retry unit; there is no separate backoff protocol. MUST keep running in realtime mode
Realtime push cadence Implementation- or policy-chosen; a fixed cadence trades latency for timing-channel flatness
HLC reservation window Finite, and below the clock skew bound with margin. A node resumes at the reserved physical component, so a window at or above the bound resumes it into a value its peers MUST reject
Mailbox retention Finite and stated
Mailbox quota Optional; MUST reject rather than silently drop
KeyPackage stock size The number of single-use packages a node maintains in the §25.2 directory
KeyPackage replenishment low-water mark The stock level below which a node MUST replenish; MUST be above zero so last-resort is not the first response to a depleted stock in the normal case
Pairing ceremony deadline Finite, fixed when the machine enters idle, and spanning the whole ceremony rather than restarting per transition. The only limit here gated on human reaction time rather than on a round trip, which is why nothing on the wire carries it and why too tight a value is its failure mode

SSP/1 distinguishes three responses, and conflating them is the common implementation error:

  • Reject — refuse this item, surface it, retain nothing, continue processing others.
  • Hold — refuse this group for now, do not advance the watermark past it, retry next cycle. For conditions that may resolve.
  • Drop — discard permanently, advance the watermark. For conditions that will not resolve, and for anything a hostile peer could otherwise use to stall the apply loop.

Hold and drop each settle the watermark; reject does not settle it on its own, so every rejecting condition MUST state its own. A rejection that is a property of the bytes alone can never resolve, so the watermark advances past it. The clock skew bound is the one rejection that turns on receiver-local state rather than on the bytes — it resolves as the receiver’s clock advances — so there the watermark does not advance and the next re-offer succeeds.

An implementation MUST NOT raise an exception that halts the apply loop on peer-supplied input. A single malformed event from one peer must not stop replication with every other peer.