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 devices —None(nothing droppable) while any active device has never acked, mirroringcompact::AckTable’s refusal semantics. - Eviction: a device silent (no push/pull/ack) longer than
RelayConfig::eviction_horizon_msis markedDeviceStatus::Evictedon 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, seecrate::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 apullwhosesincefrontier 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.
- InMemory
Relay - The in-process reference relay: [
RelayState] + an injected wall clock. - Pull
Result - What a pull returned — the proposal’s
{ops, latest_checkpoint_ptr}. - Push
Outcome - What a push did. Not transactional: on an error mid-batch the already accepted prefix stays (retry-safe — a re-push of it dedups).
- Relay
Config - Relay policy knobs.
- Roster
Entry - One device registry entry —
roster()’s row.
Enums§
- Device
Status - Roster liveness state — the proposal’s “devices+last_seen” with the eviction verdict made explicit.
- Relay
Error - A relay operation failure.
Traits§
- Relay
- The relay contract — see the module docs. All methods take
&mut selfbecause 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
seqthe 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.