Skip to main content

SyncSession

Struct SyncSession 

Source
pub struct SyncSession { /* private fields */ }
Expand description

A device’s live sync endpoint: its append chain, its durable journal, and the pump. See the module docs for the contract sequencing.

Implementations§

Source§

impl SyncSession

Source

pub fn open( device_id: impl Into<String>, journal_path: &Path, checkpoint_dir: &Path, wall: WallClock, ) -> Result<Self, SessionError>

Open a session over an existing (possibly empty, possibly truncated) journal. A truncated journal resumes only through its covering checkpoint (resume_anchored — B4 contract): the checkpoint file must be present in checkpoint_dir under its content address.

Source

pub fn with_key_provider(self, provider: Arc<dyn SyncKeyProvider>) -> Self

Turn on E2E: op payloads are encrypted at SyncSession::append and decrypted at SyncSession::state, keyed per scope audience by provider (a login-derived crate::crypto::DerivedKeyProvider in the daemon). Builder so SyncSession::open’s signature stays stable for the local (cleartext FsRelay) path. NOTE: with a remote relay, checkpoint state is still cleartext-folded — do not push checkpoints to an untrusted relay until per-scope checkpoint encryption lands (crypto.rs follow-up).

Source

pub fn bootstrap( device_id: impl Into<String>, journal_path: &Path, checkpoint_dir: &Path, relay: &mut dyn Relay, wall: WallClock, ) -> Result<Self, SessionError>

Open + immediately SyncSession::rebase onto the relay’s latest checkpoint — the cold-device / returning-straggler entry point (proposal §“Cold / new device bootstrap”). With no relay checkpoint (young account) this degrades to a plain open; the first pump replays from genesis.

Source

pub fn append( &mut self, scope: Scope, surface: Surface, payload: Value, ) -> Result<OpRecord, SessionError>

Record a local mutation: stamp (hybrid clock), journal (flushed), then hold for push — the op is journal-durable before it can ever be transmitted (B1 MUST). If the journal write fails the device chain is rolled back (rebuilt from the durable ops), so the failed op can never leave a hole for the next append to chain onto.

Source

pub fn committed_run( &self, agent_id: &str, run_id: &str, ) -> Option<FoldedRecord>

The fence-independent committed-run idempotency oracle (B5) over this session’s folded state (checkpoint base + journal tail): “has run_id already committed for agent_id?” Keep-all and immune to epoch bumps AND compaction, so it is the CORRECT idempotency lookup — unlike crate::fold::SyncState::intent, which is the fenced “who holds now” view and can read None/pending for a run that actually committed. A B6 dispatch fence performs exactly this read before an external side effect.

Source

pub fn record_intent( &mut self, scope: Scope, intent: &Intent, ) -> Result<Option<OpRecord>, SessionError>

Record a leased execution Intent (B5) — journal-durable exactly like any SyncSession::append, so it is durable before it can be transmitted. The partitioned / ungated path: a zombie that cannot reach the coordinator still records here, and the fold converges the ledger deterministically.

Terminal guard (C3): a committed run is terminal. A pending/failed write for a run already committed (per the fence-independent SyncSession::committed_run oracle) is a no-op (Ok(None)), so a failed-over holder writing pending before checking cannot revert the committed ledger.

This converges the ledger; it is NOT the exactly-once execution gate. The B6 dispatch fence — a linearizable “am I still epoch N?” plus this committed-oracle read before the external effect — is what makes execution single-shot.

Source

pub fn record_intent_if_current( &mut self, scope: Scope, intent: &Intent, coordinator: &mut dyn LeaseCoordinator, ) -> Result<Option<OpRecord>, SessionError>

Record a leased Intent only if the local epoch is still current — the best-effort local gate: a linearizable coordinator read confirms this device still holds the lease at intent.epoch before the op is journaled. Returns Ok(None) when the coordinator says this device is no longer the holder at that epoch (skipping a write the fold would fence) or when the run is already committed (the SyncSession::record_intent terminal guard).

This is a liveness optimization, NOT the safety gate: a partitioned zombie that cannot reach the coordinator falls back to SyncSession::record_intent. Exactly-once execution is the B6 dispatch fence (this check races a pause-after-check — the Kleppmann residual).

Source

pub fn pump( &mut self, relay: &mut dyn Relay, ) -> Result<PumpReport, SessionError>

One reconciliation round: push journal-durable own ops → pull → verify → journal the folds → ack. See the module docs for the contract sequencing; retry-safe at every crash point.

Returns RelayError::FrontierTruncated (wrapped) when the relay has GC’d past this session’s frontier — call SyncSession::rebase and pump again.

Source

pub fn rebase(&mut self, relay: &mut dyn Relay) -> Result<bool, SessionError>

Re-anchor this session on the relay’s latest checkpoint — the cold bootstrap / post-eviction re-entry move. Returns true when a rebase happened.

Sequencing (each step durable before the next depends on it): checkpoint_getpull(since = checkpoint frontier) → merge in every locally-held op the checkpoint does NOT cover (a returning straggler’s unpushed writes survive) → verify_anchored → save the checkpoint into the session checkpoint dir → truncate_to (journal rewritten as the anchored tail, truncation marker stamped) → resume_anchored. The push cursor resets so the next pump re-offers every own op in the tail (relay dedups the already-pushed).

Source

pub fn publish_checkpoint( &mut self, relay: &mut dyn Relay, ) -> Result<Option<Checkpoint>, SessionError>

Compute a checkpoint at the relay’s stable frontier from this session’s held ops and upload it — the device-computed snapshot the proposal requires under E2E (“the relay holds ciphertext and cannot fold”). Call after a pump (so held == relay-known and own ops are pushed). Returns the uploaded checkpoint, or None when there is no stable frontier, nothing below it, or this session is itself anchored on a checkpoint (recompaction over a base is the same later slice B4 deferred).

Source

pub fn state(&self) -> SyncState

The materialized state: fold_onto(base checkpoint, journal tail).

Under E2E (SyncSession::with_key_provider) the tail is ciphertext, so each op’s payload is decrypted here — after the chain has verified, before the fold groups on payload["id"]/fold_key. Op identity stays the cleartext-metadata op_id; only the payload is swapped.

Source

pub fn state_hash(&self) -> String

state_hash of SyncSession::state — the divergence invariant two synced devices must agree on.

Source

pub fn device_id(&self) -> &str

Source

pub fn ops(&self) -> &[OpRecord]

The journal-held ops (the anchored tail, when a base is set).

Source

pub fn base(&self) -> Option<&Checkpoint>

The checkpoint this session’s journal is anchored on, if truncated.

Trait Implementations§

Source§

impl Debug for SyncSession

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more