Skip to main content

Module relay

Module relay 

Source
Expand description

Relay transport — the account-scoped op-stream devices push/pull against (slice B3 of docs/proposals/multi-device-sync.md, §“Transport” + §“Stragglers and the single coherence knob” + §“Sync protocol surface”).

The Relay trait is the proposal’s surface — push(ops), pull(since_frontier) → {ops, latest_checkpoint_ptr}, ack(frontier), checkpoint_put/get, roster() — as a library trait with two reference implementations: InMemoryRelay (tests, in-process coordination) and FsRelay (a shared directory — the realistic single-user two-Mac loopback). The network daemon surface (sync.* JSON-RPC + the Parslee-hosted backend) is B6; anything that speaks this trait is that surface’s contract.

§What the relay validates (and what it can’t)

The relay holds one chain per device and admits a pushed op only if it continues that chain exactly: contiguous seq, prev linking the relay-held head, HLC advancing (a re-push of an already-held op dedups on op_id; a different op claiming a held seq is a RelayError::Fork — the B1 permanent-fork hazard surfaced as a runtime error at the transport). A device pushes only its own chain (RelayError::ForeignOps). In B3 payloads are cleartext so the relay can run this full verification; under B6’s E2E encryption the same checks still work — op_id/hlc/seq/prev stay cleartext metadata by design (“the relay can route and dedup on op_id and hlc”). Device identity remains asserted, not authenticated, until B6 signing.

§Stable frontier, eviction horizon H, and GC

Straight from the proposal’s stragglers section:

  • Stable frontier = min(acked) over active (non-evicted) roster devicesNone (nothing droppable) while any active device has never acked, mirroring compact::AckTable’s refusal semantics.
  • Eviction: a device silent (no push/pull/ack) longer than RelayConfig::eviction_horizon_ms is marked DeviceStatus::Evicted on the roster; its ack no longer holds the frontier. An evicted device may still push/pull/ack (its re-entry path is cold bootstrap — checkpoint_get + pull(since checkpoint frontier) + resume_anchored, see crate::session), and it is reinstated when it acks at/above the current stable frontier (i.e. it has provably caught up) — or whenever nothing is GC-eligible anyway (stable frontier == None).
  • GC (Relay::gc): an op is droppable only when both (a) its HLC is at/below the stable frontier AND (b) a stored checkpoint covers it (checkpoint.frontier[device].seq >= op.seq) — “ops below the stable frontier are GC-eligible relay-side only after a covering checkpoint exists”. Both conditions are per-device chain prefixes (HLC-monotone chains; frontier seqs), so GC always drops a prefix and the retained chain stays gap-free; the relay keeps the last dropped op’s (seq, op_id, hlc) as the chain anchor for continuity checks and remembers the drop floor so a pull whose since frontier reaches into truncated space fails loudly (RelayError::FrontierTruncated) instead of silently serving a gapped log — the signal that sends the puller to cold bootstrap.

Deliberate deviation from the proposal, binding on B6: the proposal’s third H rule (“max op age the relay accepts = H; older ops are rejected”) exists to keep accepted ops out of time-truncated space (hlc.wall < now − H). This relay’s truncation is not time-based — it is per-device-seq + covering-checkpoint, under which a returning straggler’s late ops are structurally outside truncated space (its own chain frontier is behind them) and converge losslessly once pushed, which is strictly stronger than the proposal’s “loses only writes it made while >H-offline” degenerate case. So B3 accepts old ops rather than rejecting them. If B6’s hosted backend adopts wall-clock-bounded storage, it must reintroduce the age bound and the proposal’s surface-it-to-the-user story together.

§Checkpoints

checkpoint_put runs TWO checks, not one. Checkpoint::verify proves internal self-consistency (both hashes recompute) — but that alone lets a checkpoint built from a different chain merely CLAIMING a device’s name walk in and, via the coverage claim, make GC drop that device’s real ops (the kernel-review data-loss defect). So checkpoint_put also cross-checks the frontier against the relay-held chains ([RelayState::validate_frontier]): every frontier entry must name an op the relay actually holds/held for that device at that seq (or, for a seq already GC’d, be consistent with the remembered dropped head). A device the relay has never seen a chain for cannot be validated. Only validated checkpoints are stored, so gc — which counts stored checkpoints as coverage — counts only validated ones, and the bare-seq coverage test is then sound (a validated frontier head at seq S means the contiguous hash-linked prefix 0..S is the device’s real chain). op_id/seq/prev stay cleartext metadata under B6 E2E, so this cross-check survives encryption. Storage dedups on checkpoint_hash — the whole-record content address, never state_hash (binding contract from B4: two frontiers can fold to one state; keying on the state would keep the wrong checkpoint and fork chains on resume). The “latest” pointer served by pull/checkpoint_get advances only to a checkpoint whose frontier dominates the current latest (covers at least every device/seq it covers), so a stale or concurrent upload can never regress the bootstrap pointer.

Structs§

AckOutcome
What an ack did.
FsRelay
The filesystem loopback relay: two (or more) DeviceLogs syncing through a shared directory — the realistic single-user two-Mac case (a shared volume, an external disk, a user-managed synced folder).
GcReport
What GC dropped, per device.
InMemoryRelay
The in-process reference relay: [RelayState] + an injected wall clock.
PullResult
What a pull returned — the proposal’s {ops, latest_checkpoint_ptr}.
PushOutcome
What a push did. Not transactional: on an error mid-batch the already accepted prefix stays (retry-safe — a re-push of it dedups).
RelayConfig
Relay policy knobs.
RosterEntry
One device registry entry — roster()’s row.

Enums§

DeviceStatus
Roster liveness state — the proposal’s “devices+last_seen” with the eviction verdict made explicit.
RelayError
A relay operation failure.

Traits§

Relay
The relay contract — see the module docs. All methods take &mut self because every contact updates roster liveness (and runs the eviction sweep) even on logically-read-only calls.

Functions§

checkpoint_frontier
The pull cursor a checkpoint’s coverage corresponds to — the cold bootstrap’s pull(since = F).
frontier_of
The frontier of an op-set: max seq per device.

Type Aliases§

Frontier
A pull cursor: device_id → highest seq the puller already holds for that device. Seq-based (not HLC-based) on purpose: a straggler’s late ops carry old HLCs but new seqs, so a seq frontier still delivers them to every peer — an HLC cursor would silently skip them.