Expand description
The device-side sync session — the pump that drives a DeviceLog +
OplogJournal pair against a Relay (slice B3 of
docs/proposals/multi-device-sync.md).
SyncSession::pump is one reconciliation round, sequenced so the
binding contracts from B1/B4 hold by construction, not by caller
discipline:
- Push — only ops read from (or appended through) the journal are
ever handed to the relay, and the journal is fsync’d
(
OplogJournal::sync) before the push, so an op is journal-durable before it is transmitted (B1 MUST —flushis only the page cache; a power loss in the writeback window would otherwise lose a transmitted op and re-mint its seq into a permanent Fork). A crash that loses the push cursor is harmless — a re-push dedups relay-side onop_id. - Pull —
pull(since = my per-device seq frontier), then verify before fold (B1 MUST): the union of held + pulled ops must passverify_log(orverify_anchoredagainst the session’s base checkpoint) before anything is folded or journaled. - Fold durably, then ack — verified remote ops are appended to the
journal, advancing
self.opsin lockstep (each op entersself.opsthe instant its append succeeds, so a mid-loop failure leavesself.ops == journaland the retry re-pull filters the already-journaled ops instead of duplicating them — a duplicate line wouldDuplicateSeq-brick the next open). The journal is then fsync’d and only then isacksent. The ack value is derived from the journal-held ops — there is no API to ack anything else, so acking merely-received (un-journaled) state is impossible by construction (B4 MUST). A crash between the fold and the ack leaves the relay’s ack table behind — the safe direction: GC can’t drop what we haven’t acked, and the next pump re-acks.
Idempotence: re-running pump after any crash point re-pushes
(relay dedups by op_id), re-pulls (already-held ops are filtered by
op_id; re-folding is a no-op), and re-acks (monotone) — the whole
round is retry-safe.
Cold bootstrap / straggler re-entry (SyncSession::bootstrap /
SyncSession::rebase): when the relay has GC’d past the session’s
frontier (RelayError::FrontierTruncated) or the device is brand new,
the path is exactly the proposal’s — checkpoint_get() → pull(since = checkpoint frontier) → verify → journal rewritten as checkpoint-anchored
tail (truncate_to, stamping the truncation marker so the naive
load/resume stays fenced — B4 contract 5) → resume_anchored,
never DeviceLog::resume. Local ops not covered by the checkpoint —
including a returning straggler’s never-pushed writes — are carried into
the rebased tail and pushed on the next pump: re-entry is lossless (see
the relay module docs for why this is safe under seq-based frontiers).
Structs§
- Pump
Report - What one
SyncSession::pumpround did. - Sync
Session - A device’s live sync endpoint: its append chain, its durable journal, and the pump. See the module docs for the contract sequencing.
Enums§
- Session
Error - A sync-session failure.