Skip to content

Normative · Part

Data model

Every mutation to syncable state is represented as a change event. On the wire an event is a COSE_Sign1 message ([RFC 9052]) — the same container SOP/1 uses for object manifests and access records, so SOVM/1 carries one signature stack.

Event = COSE_Sign1(
protected = {
alg : ES256,
content type : "sovm/ssp-event-v1",
kid : site_id (16 bytes)
},
unprotected = {}, MUST be empty
payload = deterministic CBOR map (fields below),
external_aad = mesh_id (16 raw bytes)
)

The unprotected bucket MUST be empty: everything an event carries is covered by the signature, so there is nowhere for an unauthenticated field to hide.

The payload is a deterministic CBOR map with exactly these fields:

Field Type Description
table_schema text Destination schema name.
table_name text Destination table name.
row_key map Primary-key column names to values. Non-empty, for both operations.
operation text "upsert" or "delete". "purge" is reserved and MUST NOT be emitted; see deletion.
payload map or null Column values. Null for tombstones.
hlc_physical_ms unsigned HLC physical component, milliseconds since the Unix epoch.
hlc_logical unsigned HLC logical counter.

Every field is REQUIRED. Receivers MUST reject an event with a missing or extra payload field — a receiver cannot verify a signature over fields it had to invent, and an extra field it does not interpret is a field it is attesting to blindly.

row_key is the one field with no null form. Both operations address a row, and every materialization rule is keyed on that address, so a null or empty key names nothing any rule can act on; a receiver drops such an event rather than inventing a meaning for it (held and dropped). payload keeps its null form because a tombstone genuinely carries no columns.

Notes on what is deliberately absent:

  • No event_id member. The identifier is derived from the message bytes (section 9), so transmitting it inside the message would be self-referential. Deriving it also removes an entire failure class: there is no transmitted identifier to mismatch against.
  • No site_id in the payload. The authoring node travels once, in the protected kid header, where it is covered by the signature. Two locations for one fact is an invitation for them to disagree.
  • No encoding or version tags. The protected content type versions the whole construction. A future change ships as sovm/ssp-event-v2, and because the content type is inside the signed structure it cannot be swapped in flight.
  • No ts member and no operation synonyms. A timestamp member would duplicate hlc_physical_ms, and accepting "insert"/"update" as synonyms of "upsert" would widen the parser for no benefit; duplicated fields are a divergence hazard.

Where this specification calls an encoding canonical, it means the Core Deterministic Encoding Requirements of CBOR ([RFC 8949] section 4.2.1): definite lengths, shortest-form integers, preferred float serialization, and bytewise lexicographic map-key ordering.

Deterministic encoding is REQUIRED wherever bytes are signed or hashed — event payloads, record payloads, and every structure named in the registry. Message envelopes that are neither signed nor hashed SHOULD use it too, but interoperability does not depend on that.

Value conventions layered on top, since CBOR has no native types for them:

Logical type Canonical representation
Timestamp ISO-8601 text string, UTC, Z suffix
Decimal / arbitrary precision Text string, to avoid binary64 rounding
Binary Byte string

NaN and the infinities MUST NOT appear, even though CBOR can encode them. A serializer MUST fail closed rather than emit them.

Persist before release. A producer MUST durably persist the exact encoded COSE_Sign1 bytes of an event before those bytes are released to any peer, written to a mailbox, or applied locally, and MUST re-transmit exactly those bytes thereafter rather than re-encoding or re-signing.

Deterministic CBOR makes the payload bytes reproducible. The signature is not: ECDSA is randomised, and a second signing of the same payload yields a different COSE_Sign1 byte string (section 9). Exact-byte persistence, ordered before release, is therefore the only thing standing between the protocol and duplicate events, and the derived identifier in the next section depends on it.

The rule has a companion, stated with the clock it constrains. Persisting an event’s bytes is not enough on its own: a producer whose clock does not survive the same crash re-issues an HLC it has already released, which is the one thing convergence does not tolerate. Section 11.2 states that half.

event_id = lowercase hex BLAKE3 (32-byte output) of the
exact COSE_Sign1 bytes as received

event_id is derived, never transmitted. A receiver computes it by hashing the message it received; there is nothing to compare against and therefore nothing to mismatch.

This is SOVM/1’s one hashing convention, shared with SOP/1: an identifier is the BLAKE3 hash of the exact bytes of a signed structure, and no second hash namespace exists. Deriving from the signed message means there is no hand-assembled pre-image, no separator bytes, and no possibility of a pre-image and the message disagreeing.

event_id is a signing-instance identifier, not a content address, and an implementer MUST NOT rely on either regime holding. Deterministic nonces bind every signer not held in a secure element, which since section 10.1 includes the event-signing subkey that signs the ordinary node’s change events: those re-sign to identical bytes and therefore to an identical event_id. Where a change event is signed by the element directly — a node that has issued no subkey — the nonce is generated inside the element, is not reproducible outside it, and two signings of one payload produce two different 64-byte signatures, two different COSE_Sign1 byte strings and two different event_ids.

Both regimes are conforming and both are in the field. What follows from that is the rule below: the identifier deduplicates re-deliveries of one signed message, and a receiver MUST NOT read it as a content address in either regime.

event_id is an idempotency key, not a trust anchor. It deduplicates re-deliveries and nothing more — and de-duplicating re-deliveries of one signed message is exactly what a signing-instance identifier does. Authorship is established by the signature, and a receiver MUST NOT treat a familiar event_id as evidence of anything except having seen those bytes before. What the identifier does not tolerate is re-signing as a recovery action, which is the next section’s rule.

9.1. Never re-sign under an existing HLC or generation

Section titled “9.1. Never re-sign under an existing HLC or generation”

A producer MUST NOT sign a second COSE_Sign1 message under an HLC it has already signed under. Where a producer cannot establish that a signed message was never released — a crash between signing and persisting is the case that matters — it MUST tick the HLC and sign a new event rather than re-sign the old one.

The same rule reaches chained records. A producer MUST NOT re-sign a role assignment at an existing generation, or a transport binding at an existing generation, for that subject or node.

The chained case is the more dangerous of the two. Two distinct signed records at one generation are a chain fork, and SOVM/1 treats a fork as detectable evidence, never a silent divergence — that is, as evidence of compromise. A producer that re-signs after a crash therefore manufactures a first-order security alarm about itself, and an operator who learns that forks are usually benign has lost the signal entirely.

Determinism changes the consequence of a violation without relaxing the rule. Where change events are signed by an event-signing subkey, a crash-then-re-sign under the same HLC reproduces byte-identical output, so delivery deduplication absorbs it and no alarm fires. The rule still stands — a producer MUST still tick rather than re-sign, because it cannot know which regime a receiver will apply, and because the rule’s other half does not soften at all. Chained records, including sovm/ssp-eventkey-v1 records, remain IK-signed and randomised, so re-signing one still manufactures a first-order fork alarm about the producer itself. The two halves of §9.1 simply stop being symmetric.

9.2. Ticking instead of re-signing is safe

Section titled “9.2. Ticking instead of re-signing is safe”

The alternative looks cheaper, so the reason it is not is worth stating.

Convergence is a pure function of the delivered event set and rests on exactly one precondition: a node MUST NOT reuse an HLC across distinct events. Two signings of one payload are distinct events, so re-signing under the old HLC violates that precondition directly. Signing under a fresh HLC does not: the result is two distinct events with distinct HLCs and identical column values, which per-column last-writer-wins resolves to the same state, and which a duplicated delete resolves to the same tombstone clock.

The cost is one redundant event in the log. The cost of the alternative is the one precondition the merge rules have.

event_id’s other consumers are unaffected. It is derived, never transmitted, so there is no transmitted identifier to mismatch.

An implementer will reasonably ask why the specification does not simply require [RFC 6979] deterministic nonces everywhere and recover deterministic signing.

Because RFC 6979 is a rule for software signers and cannot be imposed on a secure element — the element generates the nonce under its own certification and exposes no interface to supply one. Requiring reproducible signing would therefore require the root key to be in software, which is the defect the choice of ES256 exists to avoid. A protocol whose correctness improved as the key moved out of hardware would have exactly the wrong gradient, and the same reasoning SOP/1 §5.2 applies to attestation downgrades applies here.

A determinism claim argued from a property of the signature algorithm rather than from a rule the protocol enforces would be fragile either way: a protocol that depends on an algorithm’s incidental determinism depends on something no future suite is obliged to provide.

Every event is signed by the authoring node, and a receiver MUST verify before apply.

Verification is COSE_Sign1 verification with:

  • the public key resolved for the authoring site_id — the protected kid — which is that node’s IK_pub from the receiver’s trusted-peer keyring, or, where the node has issued an event-signing subkey, the event_pub selected by section 10.3;
  • external_aad set to the receiver’s own 16-byte mesh_id.

Four properties this construction is chosen for:

  1. The whole message is covered, protected headers included. The content type and kid cannot be altered in flight, and the empty unprotected bucket leaves no unauthenticated surface.
  2. mesh_id is bound without being transmitted. The verifier supplies its own mesh_id as external AAD, so an event captured from one mesh fails verification in any other — including a mesh that happens to trust the same key.
  3. Domain separation is structural. COSE’s Signature1 context string plus the content type make an event signature unconfusable with a vouch, a role record, a transport binding, or any raw-transcript signature in the pairing ceremony.
  4. Event identity does not rest on signature determinism, in either direction. Whether a change event re-signs to identical bytes depends on where its key is held (section 9, section 10.1), and nothing here assumes an answer. What lets event identity be derived is the persist-before-release rule in section 8: a producer signs once, persists the exact bytes, and re-transmits those bytes thereafter.

Failure handling, each fail-closed and each surfaced:

  • Unknown kid — the sender is not in the keyring: drop the event and surface it. Unknown-sender events are not queued pending future trust.
  • Invalid signature under a known key: drop the event and surface it.
  • Above a retirement cutoff — the kid resolves, the signature is valid, and the event’s hlc_physical_ms is above the row’s retired_at_hlc: drop the event and surface it. What the cutoff means, and why a retained row answers nothing above it, is identity §20.4’s to state.

No case holds the sender’s group or stalls a watermark. That is deliberate: events are attributed by kid, not by connection, so if verification failures held the attributed site’s group, a forger could stall the victim’s stream by sending garbage carrying the victim’s kid. A forgery must cost the forger, not the node it impersonates.

Per-event signing and root-identity signing have opposite duty cycles. Transport §28 already states the premise: IK_priv “may live in a secure element that signs rarely and deliberately”. A change event is neither rare nor deliberate — a bulk import is tens of thousands of signatures — and a secure element that can produce a few signatures per second cannot carry that volume. Requiring it would make the specification unimplementable on exactly the nodes whose custody it most wants in hardware.

A node MAY therefore issue an event-signing subkey: a software ECDSA P-256 keypair, held outside the secure element, whose public part is bound to the node by a record the element itself signs. The subkey is a chained record and inherits the generation-only narrowing of the transport-binding chain unchanged: it narrows the substrate schema exactly as far as that chain does and no further, and widens nothing. The narrowing is restated at the end of this section, after the three fields that do depart from that precedent.

EventSigningKey = COSE_Sign1(
protected = {
alg : ES256,
content type : "sovm/ssp-eventkey-v1",
kid : site_id (16 bytes)
},
payload = deterministic CBOR {
event_pub : bstr 33,
generation : unsigned,
not_after : unsigned,
prior_revoked_at : unsigned / null
},
external_aad = mesh_id (16 raw bytes)
)
Field Rule
event_pub The subkey’s public part, SEC 1 compressed P-256, 33 bytes. An uncompressed or hybrid encoding MUST be rejected rather than converted (encoding canonicity).
generation Unsigned, strictly increasing per site_id. A record supersedes all lower generations for that node, and a successor need only exceed its predecessor rather than carry exactly one more — substrate §11’s density rule is inapplicable on a chain with no predecessor member (see the generation-only narrowing below).
not_after The HLC physical component after which the subkey is invalid for all purposes. A verifier MUST reject a record whose not_after is not strictly greater than the record’s own issuance time.
prior_revoked_at null at generation 0. For generation N > 0 it is the HLC physical component after which the generation N−1 subkey is invalid for new events. It MUST NOT be greater than the N−1 record’s not_after.

Issuance rules, each of which is a MUST:

  • The record is signed by IK, inside the element. It is a SOP/1 §5.4 hardware signature and carries that section’s verify-after-sign obligation at that section’s scope, which is not restated here. Signing it is a rare, deliberate operation, which is the property transport §28 relies on and this section reuses.
  • One hop, always terminating at the element. A subkey MUST NOT issue a successor subkey, and a verifier MUST reject an sovm/ssp-eventkey-v1 record whose signature does not verify under the IK_pub of a currently trusted, not retired peer in the trusted-peer keyring. There are no chains of delegation, only a chain of generations. A retired node’s row still holds its IK_pub, and this predicate is what refuses a record under it: that refusal is the freeze of identity §20.4, enforced at the one place a subkey record is admitted.
  • The subkey’s private part MUST NOT leave the node, MUST NOT be escrowed, and MUST NOT be transmitted in any SOVM/1 message. Only event_pub travels.
  • The subkey is a software signer and is therefore bound by deterministic nonces. This is not advisory: a repeated nonce leaks the subkey outright from two signatures.
  • Validity windows SHOULD be short. An implementation SHOULD set not_after no more than 90 days beyond issuance, and a mesh MAY require less by policy. The bound is what makes the exposure in limitations §40.7 bounded, so it is stated as a number rather than left to taste.
  • Records travel in the sync exchange’s records member, on the same path as transport bindings. They are self-verifying.

Three deliberate departures from the §28 precedent, each with its reason:

  • event_pub is bstr 33 (P-256), where channel_pub is bstr 32 (Ed25519). Transport §28’s asymmetry exists because the transport ecosystem carries Ed25519 and the channel key is bound by a signature rather than by its own custody. No such force acts here: the subkey signs COSE_Sign1 under the substrate profile, which has exactly one signature suite and it is ES256. Copying transport §28’s curve would add a second suite to the signing path for no reason. The channel-key precedent justifies the pattern, not the suite.
  • not_after is present, where transport §28 has no expiry. A stale channel key merely fails to connect. A stale event-signing key forges history, which is a failure that does not announce itself. Bounded validity is the failure-closed posture for that asymmetry.
  • prior_revoked_at is present. Transport §28 needs no revocation cutoff because a binding is wholly superseded. An event-signing subkey is not: events it signed before revocation must keep passing validity checks, or every rotation would destroy the history signed under the previous generation. See section 10.3.

Like the transport-binding chain, this chain is generation-only: the payload above is exhaustive and carries no predecessor member, so every record is a genesis record and a record supersedes all lower generations. That is a narrowing chained records §12 permits, stated here on the instantiation’s own page as that section requires. As on transport §28, substrate §11’s density rule is thereby inapplicable rather than weakened: it is stated relative to a record’s predecessor, and there is none to compare against.

10.2. Scope boundary of the event-signing subkey

Section titled “10.2. Scope boundary of the event-signing subkey”

This is the paragraph the rest of the mechanism rests on. It is exhaustive.

An event-signing subkey MAY sign change events — sovm/ssp-event-v1 — and nothing else.

An event-signing subkey MUST NOT sign any of:

A verifier MUST reject any record in that list whose signature verifies under an event_pub rather than under the node’s IK_pub. The rejection is structural as well as normative: each of these records carries its own protected content type, and COSE’s Signature1 context plus the content type make the domains unconfusable (section 10, property 3). The MUST is stated anyway, because a property that holds by construction and is not written down is a property the next revision can lose without noticing.

Everything that authorizes stays with IK_priv. Membership, roles, vouching and transport identity are unreachable from a compromised subkey, so an extracted subkey cannot escalate — it can only make false statements about its own node’s rows.

That is a bound on the key, and it becomes a bound on the adversary only where IK_priv is out of reach of whatever compromised the subkey. Where §19.1’s element holds it, it is: the scalar never enters the address space the subkey sat in, and the confinement above is what makes the exposure in limitations §40.7 acceptable rather than merely acknowledged. Where custody is software it is not, and this specification requires that case to exist rather than merely tolerating it: identity §19.3 declines to ban a software root, and identity §19.4’s vouch row records one normatively for every vouched join. An adversary that reads a software IK_priv out of the process it already holds signs every record in the MUST NOT list above — by using the root, not by escalating the subkey. The list is exhaustive about what the subkey reaches. It is not a statement about what an attacker reaches, and it MUST NOT be cited as one.

A change event’s signing key is selected by the event’s own HLC, against the subkey chain valid at that HLC. Selection is single-candidate and fail-closed.

A verifier presented with a change event MUST:

  1. Read kid from the protected header. It is the authoring site_id, unchanged from section 10.
  2. Decode the COSE_Sign1 payload and read its HLC physical component. The payload is cleartext within the signed structure, so reading it before verification is safe: the signature covers those bytes, so an attacker who lies about the HLC selects the wrong generation and fails at step 4 rather than misleading the verifier.
  3. Select the single generation G whose validity window covers that HLC, where the window is issued_at(G) ≤ hlc ≤ min( not_after(G), prior_revoked_at(G+1) if a generation-G+1 record is held, otherwise ∞ ).
  4. Verify the COSE_Sign1 signature against event_pub of generation G, with external_aad set to the verifier’s own mesh_id.
  5. Fail closed if no generation’s window covers the HLC, if more than one does, or if the signature does not verify: drop the event and surface it, exactly as section 10’s two failure cases require.

A verifier MUST select exactly one candidate generation and MUST NOT iterate over generations trying each in turn. Iteration would let an attacker who controls the HLC brute-force past the selection logic, which is the only thing binding an event to a validity window at all.

Where a node has issued no sovm/ssp-eventkey-v1 record, change events are verified against IK_pub from the keyring as section 10 describes, unchanged. Subkey issuance is optional; verifying it is not.

Revocation cascades downward and not upward. Revoking a node at the §22 level removes IK_pub from the trusted-peer keyring, at which point every sovm/ssp-eventkey-v1 record for that node stops verifying — the records are IK-signed, so the whole subkey chain fails with the keyring entry. Node revocation therefore reaches subkey revocation with no additional mechanism, and it fails closed. The converse does not hold: rotating or revoking an event-signing subkey is routine and says nothing about the node’s IK-level trustworthiness. The subkey mechanism needs no extension for either outcome of identity §22, and the two mechanisms MUST NOT share logic. That is a claim about this section, not about identity §22, which does distinguish removal from retirement.

Retirement retains the chain, bounded by the cutoff. Under retirement the keyring row survives, verification-only, with a retired_at_hlc cutoff. Because sovm/ssp-eventkey-v1 records are IK-signed, a retained row that resolved IK_pub alone would keep history checkable only for nodes that never issued a subkey; resolving the kid of a retired node’s change event therefore includes running the selection above over its subkey chain, under the retained ik_pub (identity §20.4). For a retired node the window of step 3 is additionally bounded by the cutoff — issued_at(G) ≤ hlc ≤ min( not_after(G), prior_revoked_at(G+1) if held, retired_at_hlc ) — so an event above the cutoff has no candidate generation and fails closed at step 5, unchanged.

The chain a verifier resolves is the chain it held when it retired the node, and no more: the freeze stated at identity §20.4, and enforced by section 10.1’s admission predicate, refuses every later sovm/ssp-eventkey-v1 record for that site_id, whatever its issuance time. The retained read is safe because, and only because, of that freeze; the two are one rule, and an implementation that has the read without the freeze is a forgery hole. Removal collapses the chain; retirement bounds it. Retention therefore extends what is verifiable, never what is authorized.

On what a passing validity check means. An event whose HLC is at or below prior_revoked_at passes validity checks. That is not the same claim as is authentic, and this specification does not make the second one. A party who extracted the subkey could have produced that event at any HLC inside the window. See limitations §40.7, which states the residual exposure rather than leaving a reader to infer it.

Forks. An sovm/ssp-eventkey-v1 record is subject to the same generation-monotonicity rule as every other chained record: two distinct records at one (site_id, generation) are a fork and are treated as detectable evidence, never a silent divergence. Because these records are IK-signed, such a fork is evidence of IK-level compromise or a node defect — a strictly more informative signal than a subkey-level fork would be. Normal rotation produces exactly one IK-signed record per generation and cannot raise a false alarm, and the crash-recovery rule in section 9.1 applies unchanged: a producer that cannot establish a generation-N record was never released issues generation N+1 rather than re-signing.

An HLC timestamp is the pair (hlc_physical_ms, hlc_logical), compared lexicographically. When two events carry equal HLC pairs the tie is broken by byte-wise comparison of the authoring site_id from the protected kid header. The comparison including the tiebreak is total: any two distinct events have a defined order, which is what makes the merge in semantics deterministic.

Each node maintains one local HLC, which it ticks to stamp a local event and observes to merge a received one. Both operations are normative. With last the stored local HLC and now the local wall clock in milliseconds:

tick — stamp a local event:

  • if now > last.physical_ms, the new HLC is (now, 0);
  • otherwise it is (last.physical_ms, last.logical + 1).

observe — merge a received remote HLC. The new physical component is max(last.physical_ms, remote.physical_ms, now). The logical component depends on which inputs share that maximum:

Inputs holding the maximum New logical component
Both last and remote max(last.logical, remote.logical) + 1
last only last.logical + 1
remote only remote.logical + 1
now alone 0

In both operations the result is stored as the new local HLC. Together they guarantee that a causally later local event sorts after everything the node has already seen, without any coordination.

Receivers MUST reject events and watermarks whose hlc_physical_ms exceeds the receiver’s current wall clock by more than 300 000 ms (5 minutes).

Without this bound a node with a broken or hostile clock can stamp an event far in the future and win every subsequent last-writer-wins comparison indefinitely — a denial-of-replication attack that no amount of correct merge logic recovers from. The bound is one-sided: timestamps arbitrarily far in the past are accepted, since a stale clock only loses comparisons and cannot deny service to others.

Rejection is fail-closed and MUST be surfaced. An implementation MUST NOT clamp the offending timestamp and apply the event anyway.

A rejected event was not applied, so the receiver’s applied watermark for that (site_id, schema, table) MUST NOT advance to or past the rejected event’s HLC. The rejection is also not terminal: a receiver MUST NOT retain the event and MUST NOT record a standing verdict against it, and MUST apply the identical bytes when they are re-offered after its own wall clock has advanced to within the bound. Because the apply chokepoint runs this check before the idempotent insert, a rejected event_id never enters the duplicate index, which is what leaves the re-offer able to succeed.

That is what makes a transient skew self-healing rather than lossy. A receiver’s clock advances monotonically, so an event stamped d ms beyond the bound becomes acceptable after d ms of real time, and the next sync cycle re-offers it. Under the other reading — advance the watermark past the rejection — the event is lost the first time an honest peer’s clock runs fast, and lost silently, because the watermark tells the sender the receiver has it.

Advancing would in fact be worse than lossy. A rejected HLC is by construction above every honestly stamped HLC from that site: an honest stamp is at or below the receiver’s present, and a rejected one is more than five minutes beyond it. Since a watermark asserts that everything from that site for that table at or below its HLC has been applied, advancing past a single skewed event would acknowledge the site’s entire unapplied backlog in one step.

Pinning the watermark stalls nothing an attacker chooses. Signature verification precedes this check, so a skew-rejected event is authentically attributed to the site whose watermark it pins and the stall is self-inflicted. That is the reasoning of section 13.6’s signature row reaching the opposite conclusion: there the cost of not advancing would fall on the impersonated node, here it falls on the author.

Self-healing bounds the receiver, not the author. The tick rule never lowers physical_ms, so a node whose clock jumps beyond the bound stamps every subsequent event at or above the jumped-to value and stays rejected by every receiver until real time reaches it — and it cannot simply set its clock back, because lowering a stored HLC risks the reuse convergence forbids.

A watermark carrying an out-of-bound hlc_physical_ms is rejected under the same rule and is simply not recorded: the receiver’s view of that peer’s progress stays where it was, which is what watermark monotonicity requires anyway, and the peer re-reports on a later cycle.

A node MUST durably persist an HLC high-water mark: a stored HLC pair no lower than every HLC it has used to stamp an event. The mark MUST reach durable storage before the event carrying that HLC is released, on the same ordering persist before release imposes on the event bytes themselves. An event released under an HLC the mark does not yet cover is an event the node cannot promise never to re-issue.

On restart a node MUST initialize its local HLC from the persisted mark and then tick as section 11 already defines: the next stamp is (now, 0) where now exceeds the mark’s physical component, and (mark.physical_ms, mark.logical + 1) otherwise. No new operation is introduced — restart is the tick applied to a restored last. Initializing last from the wall clock alone is not permitted, and the case that breaks it is not an exotic one: a node that stamps several events in the final millisecond before it stops, and restarts inside that same millisecond, re-issues logical 0 with a perfectly correct clock and no fault anywhere.

A node MAY persist a mark strictly ahead of the highest HLC it has stamped — a reservation — and stamp freely below it without a durable write per event, provided it MUST NOT stamp an HLC above the persisted mark and MUST extend the reservation durably before crossing it. The trade is a bounded forward jump after a crash, which no merge rule is sensitive to, against a synchronous durable write on every local mutation. The window is bounded from above by the clock skew bound: a reservation of 300 000 ms or more resumes the node at a physical component its peers MUST reject, so an implementation MUST keep the window below that bound with margin for the drift the bound also has to absorb.

The mark MUST be integrity-protected, and a node MUST verify that protection before it initializes a clock from the mark. This is the one piece of node-local state whose lower values are the dangerous ones, and no other state on the node contradicts it, so a mark stored as a bare pair trusts whoever can write that storage to have been honest about it. The protecting key MUST NOT be recoverable from the mark storage itself; where the platform provides a secure element, the construction already available is a signature made by the root identity element over the mark, since section 19.1 delegates all signing to that element and exposes no export operation. An implementation MUST NOT attempt to derive protection material by extracting IK_priv — that operation does not exist. The cost lands where the reservation already put it, on the durable write rather than on every stamp.

A mark that fails verification MUST take the same path as a mark that is absent, and MUST be surfaced as a condition distinct from it: a missing mark is evidence of an interrupted write, a failing one of tampering or storage corruption, and an operator answers those differently.

Integrity protection bounds forgery, not rollback. A mark replaced by an earlier value the node itself wrote verifies correctly, and the node retains nothing above it to contradict the value with. Where the platform exposes rollback-resistant storage or a monotonic counter alongside the signer, an implementation MUST bind the mark to it, so that a replayed mark fails against a counter that only moves forward. Where it does not, what remains is the exposure the snapshot case below describes, reached by a narrower path.

Where the mark is lost, a node MAY reconstruct it as the highest HLC over the events it authored in its own retained log, but only where it can establish that no self-authored event has been evicted — eviction removes exactly the evidence the reconstruction depends on, which is why the mark is stored in its own right rather than derived on demand. Failing that, the node MUST NOT stamp further events under that site_id and MUST surface the condition rather than resume from the wall clock. Peers are not the fallback: self-echo suppression requires a receiver to drop events carrying its own kid, and have_max_hlc is one value over everything the sender holds rather than a per-site maximum, so nothing on the wire returns a node its own floor.

Restoring node state from a snapshot taken before events were released rolls the mark backwards, and no rule here detects it — the restored node reads a mark it wrote itself and holds no record of what it did afterwards. Such a node MUST be treated as a new site: re-keyed under a fresh root identity key, and therefore a fresh site_id (host interface §1), rather than resumed. Its predecessor’s rows survive and resolve by per-column last-writer-wins like any other site’s.

Re-keying the successor is only half of that instruction: the predecessor site_id MUST be revoked before the re-keyed node joins. A restore rolls back the storage around an identity key, not the key — section 19.1 puts IK_priv inside a secure element precisely so that nothing outside it can — so a predecessor still present in peer keyrings can go on stamping under the old site_id and re-open the reuse the re-key was meant to close. The successor also inherits no key_protection standing: a vouched join records software whatever the predecessor held (section 19.4), so a deployment that depends on hardware_attested MUST re-establish it by direct pairing. Limitations §40.6 records what that leaves exposed.