minerva 0.2.0

Causal ordering for distributed systems
//! The editor harness: a replica composed entirely from shipped metis parts.
//!
//! A [`Replica`] holds a document text as a [`Composer`] over a [`Rhapsody`] (each
//! character is one woven element, its rank minted from the replica's own
//! [`Clock`] the way `src/metis/tests/support.rs` builds clocks), a separate
//! cursor/selection layer as a [`Composer`] over
//! `DotMap<u32, DotFun<u64>>` (per-user register of positions), and a
//! [`Purview`] for delta targeting. Text gossip is
//! [`Composer::owed_to_witnessed`] / [`Composer::absorb`] (since S192 the
//! helper states the receiver's woven witness, the flow the field consumer
//! runs; the bare [`Composer::owed_to`] read stays available to scenarios
//! that want it); GC is [`Stability`] plus [`Rhapsody::condense`] under an
//! honestly gathered retirement claim.
//!
//! The harness owns mechanism, never policy: which anchor a keystroke picks,
//! which dots a delete supersedes, and which claim a condense trusts are the
//! scenarios' choices, made visible here so the recipe reads plainly.

extern crate alloc;

use alloc::collections::BTreeMap;

use crate::kairos::{Clock, TickCounter};
use crate::metis::{Composer, Dot, DotFun, DotMap, Purview, Rhapsody};

mod cursor;
mod gossip;
mod text;

pub use gossip::{gossip_cursors, gossip_text};

/// The text layer: a rhapsody of character elements, carried through a composer.
pub type TextComposer = Composer<Rhapsody>;

/// The cursor layer: per user (`u32` station), a register of `u64` positions.
/// A register write supersedes the user's prior cursor dots, so a lone user
/// reads one position and a two-device user reads siblings.
pub type CursorStore = DotMap<u32, DotFun<u64>>;

/// The cursor layer carried through a composer.
pub type CursorComposer = Composer<CursorStore>;

/// One replica of the collaborative document, composed from shipped parts.
pub struct Replica {
    /// This replica's station id (>= 1, the crate convention).
    station: u32,
    /// The character payloads, keyed by dot: the caller's text buffer, which
    /// the [`Rhapsody`] deliberately does not own.
    chars: BTreeMap<Dot, char>,
    /// The text order/liveness state, written through the composer facade.
    text: TextComposer,
    /// The cursor registers, written through their own composer facade.
    cursors: CursorComposer,
    /// This replica's clock, ticking one physical unit per rank mint (a fresh
    /// `TickCounter`, exactly the support.rs build).
    clock: Clock<TickCounter>,
    /// Per-peer targeting estimate, noted from received delta contexts.
    purview: Purview,
}

impl Replica {
    /// A fresh replica for `station`, tracking every peer in `roster` for
    /// gossip targeting (its own row included is harmless: it never gossips to
    /// itself).
    #[must_use]
    pub fn new(station: u32, roster: &[u32]) -> Self {
        let clock = Clock::with_default_config(TickCounter::new(), station).unwrap();
        Self {
            station,
            chars: BTreeMap::new(),
            text: Composer::new(station),
            cursors: Composer::new(station),
            clock,
            purview: Purview::new(roster.iter().copied()),
        }
    }

    /// This replica's station id.
    #[must_use]
    pub const fn station(&self) -> u32 {
        self.station
    }

    /// The char payload map, shared with peers so an absorbed delta's payloads
    /// can be learned (the caller's out-of-band payload channel; the rhapsody
    /// carries identity and order, never the char).
    #[must_use]
    pub const fn chars(&self) -> &BTreeMap<Dot, char> {
        &self.chars
    }
}