Docs

coord

Source: crates/lvcs-net/src/coord/mod.rs.

/// `lvcs.toml` `[coord]` (design §5).
///
/// `wait_ms` is the wait window, `WAIT_WINDOW_MS` by default; the daemon's
/// loader caps it at `WAIT_WINDOW_MAX_MS`, so 0 to that bound is accepted,
/// and the shell sends it to the coord actor as [`CoordinationEvents::Configure`].
/// Owner: `M3.coord`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct CoordConfig {
    /// The wait window, in milliseconds.
    pub wait_ms: u64,
}

/// A notification's id.
///
/// It is the `HASH` of its [`Notification`] payload's canonical CBOR
/// encoding ([`NotificationId::of`]), written in `ID_ENCODING` on the API;
/// the last key of the yield order. Owner: `M3.coord`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NotificationId(#[serde(with = "crate::wire::cbor::fixed")] pub [u8; 32]);

/// A notification's range resolved by this daemon's document actor
/// (`DocEvent::Resolve`) to code points of the document's text at its
/// current heads.
///
/// Overlap between notifications is decided on these spans (chosen at the
/// M3 freeze; the cursor pair itself is opaque to `lvcs-net`). Two spans
/// overlap when each starts before the other ends. A whole-document
/// notification (an empty `Notification::range`) has the span
/// [`Span::WHOLE`], `0..usize::MAX`, which overlaps every span, so it needs
/// no resolution. A span meets another when they overlap or touch (one
/// ends where the other starts): a save is announced again only when no
/// current notification of its author meets its span. Owner: `M3.coord`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
    /// Where the span starts, in code points (inclusive).
    pub start: usize,
    /// Where the span ends, in code points (exclusive).
    pub end: usize,
}

/// Whether a response is a `note` or a `hold` (design §5).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ResponseKind {
    /// Information only: the notifier proceeds.
    Note,
    /// An objection: the notifier's edit pauses, and a thread opens on the
    /// range (design §9).
    Hold,
}

/// One response as the coord actor keeps it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Responded {
    /// Whether it is a `note` or a `hold`.
    pub kind: ResponseKind,
    /// The response payload.
    pub response: Response,
}

/// One head of a document (an Automerge change hash's bytes), opaque to
/// `lvcs-net`. Owner: `M3.coord`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Head(#[serde(with = "crate::wire::cbor::fixed")] pub [u8; 32]);

/// A committed save of a document through a main mirror, by the mirror's
/// author, which holds the message capability on it (decision 165).
///
/// `range` is the canonical CBOR encoding of the anchor the save's touched
/// lines were anchored to at `heads` (empty for a whole document, a
/// removal, or a binary save, whose span is [`Span::WHOLE`]), opaque here
/// like [`Notification::range`]; `span` is that range resolved at `heads`.
/// `op` and `what` are what the notification says. Owner: `M3.coord`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Saved {
    /// The document saved.
    pub doc: String,
    /// The saving participant's key bytes: the mirror's author.
    #[serde(with = "crate::wire::cbor::fixed")]
    pub by: [u8; 32],
    /// The canonical CBOR encoding of the touched lines' anchor; empty for
    /// the whole document.
    #[serde(with = "crate::wire::cbor::bytes")]
    pub range: Vec<u8>,
    /// The touched lines resolved at `heads`.
    pub span: Span,
    /// The document's heads after the save.
    pub heads: Vec<Head>,
    /// What the save did to the range.
    pub op: NotifyOp,
    /// What the save is, in generated plain text.
    pub what: String,
}

/// A local participant's save, seen at `now_ms` (decision 165): the event
/// of [`CoordinationEvents::LocalSave`]. Owner: `M3.coord`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SaveSeen {
    /// The save.
    pub saved: Saved,
    /// The shell's clock.
    pub now_ms: i64,
}

/// The coord actor's events, generated by its sml table: its mailbox and
/// journal type (decision 286). `now_ms` is the shell's clock (Unix
/// milliseconds). Owner: `M3.coord`.
///
/// Its events, each the payload type its variant is named for: [`Configure`],
/// [`PresenceSeen`], [`WindowElapsed`], [`BulletinRequest`],
/// [`PresenceRequest`], [`LocalNotification`], [`PeerNotification`],
/// [`ResponseSeen`], [`LocalSave`].
pub use machine::{
    BulletinRequest, Configure, CoordinationEvents, LocalNotification, LocalSave, PeerNotification,
    PresenceRequest, PresenceSeen, ResponseSeen, WindowElapsed,
};

/// How a local notification's wait ended (design §5, decision 51).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Verdict {
    /// No one else was present, or the window ended with no hold and no
    /// earlier overlapping notification: the edit proceeds. `notes` are
    /// the notes received meanwhile.
    Proceed {
        /// The notes received meanwhile.
        notes: Vec<Response>,
    },
    /// At least one `hold` arrived: the edit pauses, and each hold names
    /// the thread its holder opened.
    Hold {
        /// The holds received.
        holds: Vec<Response>,
        /// The notes received meanwhile.
        notes: Vec<Response>,
    },
    /// An overlapping notification earlier in the yield order was seen
    /// during the window: the participant yields and turns its edit into
    /// an `ask` to that notification's sender (advisory).
    Yield {
        /// The earlier overlapping notification.
        to: Notification,
        /// The notes received meanwhile.
        notes: Vec<Response>,
    },
}

/// Outputs of the coord actor, which the daemon shell carries out.
/// Owner: `M3.coord`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CoordOutput {
    /// Seal `notification` with its participant's key and publish it on
    /// the document's topic and every ancestor directory's topic.
    Publish(Notification),
    /// Seal and publish a local response on the notifier's inbox topic and
    /// the document's topic.
    Respond(Responded),
    /// Publish a local participant's presence on its document's topic and
    /// every ancestor directory's topic, with a token from the
    /// participant's gossip bucket: its first local presence on the
    /// document in this incarnation, then one at least a third of
    /// `PRESENCE_TTL_SECS` after the last one published, so other daemons
    /// count it present without a gap.
    PublishPresence(Presence),
    /// Send [`CoordinationEvents::WindowElapsed`] for `notification` at `at_ms`.
    Wake {
        /// The local notification whose window ends.
        notification: NotificationId,
        /// When, by the shell's clock.
        at_ms: i64,
    },
    /// Answer the `announce` request `req`.
    Decided {
        /// The request answered.
        req: u64,
        /// The notification's id.
        notification: NotificationId,
        /// How its wait ended.
        verdict: Verdict,
    },
    /// Push a received notification to this daemon's clients.
    Announce {
        /// The notification's id.
        id: NotificationId,
        /// The notification.
        notification: Notification,
    },
    /// Push a response to the clients of its notification's sender.
    Forward(Responded),
    /// Seal `notification`, an automatic announcement of a local save
    /// (`auto` true), with its participant's key and publish it like
    /// [`CoordOutput::Publish`], with a token from the participant's gossip
    /// bucket, since no request took one. `span` is its range resolved at
    /// `heads`, the save's heads.
    AutoPublish {
        /// The automatic notification.
        notification: Notification,
        /// Its range resolved at `heads`.
        span: Span,
        /// The document's heads after the save.
        heads: Vec<Head>,
    },
    /// Answer the bulletin request `req`: the document's notifications from
    /// the last `BULLETIN_SECS`, oldest first.
    Bulletin {
        /// The request answered.
        req: u64,
        /// The document's recent notifications, oldest first.
        notifications: Vec<Notification>,
    },
    /// Answer the presence request `req` (decision 303): every
    /// participant present on a document, by document, then participant.
    Presences {
        /// The request answered.
        req: u64,
        /// Each presence: the document, the participant, its latest `ts`.
        presences: Vec<(String, Signer, i64)>,
    },
}