minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::collections::BTreeMap;

use super::{Dot, DotSet, DotStore};

mod iter;
mod store;

pub use iter::DotFunIter;

/// The payload store: per dot, the value that write carried.
///
/// The one value-carrying [`DotStore`] minerva ships in-tree, the multi-value
/// register shape from the delta-CRDT literature (dots to values). Where
/// [`DotSet`] carries bare presence and [`DotMap`](crate::metis::DotMap)
/// carries caller keys, `DotFun<V>` carries the caller's payload under each
/// dot: a dot names one write, and the value is what that write inscribed. A
/// register is a `Dotted<DotFun<V>>` (concurrent writes are sibling dots, both
/// readable via [`values`](Self::values)); a keyed register map is a
/// `Dotted<DotMap<K, DotFun<V>>>`.
///
/// # The value discipline: keep-self, equal by basis
///
/// The trait's content-follows-the-dot law (law 3) says a surviving dot keeps
/// the content the store attached to it, and the merge never invents, splits,
/// or reassigns content. On the *honest basis* a dot names exactly one write,
/// so a dot carried by two stores was minted by one write and carries one
/// value: the two copies are equal, and [`causal_merge`](DotStore::causal_merge)
/// keeps self's. That is the whole discipline, and it needs no `V: Eq` bound.
///
/// A same-dot disagreement (two stores carrying one dot with *different*
/// values) is a basis violation of exactly the class the clock already trusts
/// away: a reused station id. A station that mints two different writes under
/// one `(station, dot)` has broken the uniqueness the dot rests on, the same
/// way a station that reuses its id breaks the version vector's per-station
/// count. This store does not police that break, and deliberately so. An
/// enforcement path would need either a `V: Eq` bound and a total fallback
/// (silently pick one, which hides the corruption) or a panic path (which
/// turns a peer's Byzantine act into a local crash, a worse failure mode than
/// the honest keep-self). Neither is better than stating the basis plainly:
/// two honest stores carrying one dot carry equal values, so keep-self is
/// exact; a disagreement is out of scope, upstream of this type.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DotFun<V> {
    /// Per dot, the value that write carried. Bottom is the empty map; a dot
    /// appears at most once (support exactness).
    entries: BTreeMap<Dot, V>,
}

impl<V> Default for DotFun<V> {
    fn default() -> Self {
        Self {
            entries: BTreeMap::new(),
        }
    }
}

// The construction, write, and read surface carries no bound: only the
// causal fold and the `DotStore` obligations need `V: Clone`, so a payload
// that cannot clone still composes, reads, and counts.
impl<V> DotFun<V> {
    /// Creates the empty store: no dots, no values, the lattice bottom.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            entries: BTreeMap::new(),
        }
    }

    /// A one-write store, the delta building block: `value` under `dot`,
    /// nothing else.
    #[must_use]
    pub fn singleton(dot: Dot, value: V) -> Self {
        let mut store = Self::new();
        let _ = store.insert(dot, value);
        store
    }

    /// Records a write: `value` under `dot`, returning whether the store
    /// changed.
    ///
    /// The refusal is duplicate-only: a dot the store already carries is
    /// refused, because content is never reassigned (trait law 3). A dot
    /// names one write, and a second write under the same dot would be the
    /// basis violation the keep-self discipline rules out of scope. The
    /// non-dot `0` needs no arm here --- [`Dot`] carries the one-based law on
    /// the type (ruling R-91).
    #[must_use = "insertion is refused (returns false) for a duplicate dot, which the caller must handle to keep content-follows-the-dot"]
    pub fn insert(&mut self, dot: Dot, value: V) -> bool {
        if self.entries.contains_key(&dot) {
            return false;
        }
        let _ = self.entries.insert(dot, value);
        true
    }

    /// The value under `dot`, if the store carries it: the register read a
    /// consumer needs to see what a surviving dot inscribed.
    #[must_use]
    pub fn get(&self, dot: Dot) -> Option<&V> {
        self.entries.get(&dot)
    }

    /// Iterates the `(dot, &value)` entries in ascending dot order (the read
    /// side of the payload store).
    #[must_use]
    pub fn iter(&self) -> DotFunIter<'_, V> {
        DotFunIter::new(self.entries.iter())
    }

    /// The current values, ascending by dot: the multi-value read, every
    /// surviving sibling's payload. A register with no concurrency reads one
    /// value here; concurrent writes read as several.
    pub fn values(&self) -> impl Iterator<Item = &V> + '_ {
        self.entries.values()
    }

    /// Whether the store carries no dots at all (the lattice bottom).
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// The number of dots the store carries (surviving writes).
    #[must_use]
    pub fn len(&self) -> usize {
        self.entries.len()
    }
}

impl<V: Clone> DotFun<V> {
    /// The supersede-set of a register write: exactly the dots this store
    /// currently carries, as a [`DotSet`].
    ///
    /// A register update mints a fresh dot and displaces the values already
    /// held, so the write must supersede exactly the store's own live dots.
    /// Every consumer that drives a register-style [`compose`](crate::metis::Composer::compose)
    /// otherwise hand-rolls the same loop over [`dots`](DotStore::dots); this is
    /// that loop, named once. It pairs with
    /// [`Composer::compose_super`](crate::metis::Composer::compose_super), which
    /// reads the supersede-set from the held store inside the facade.
    #[must_use]
    pub fn observed(&self) -> DotSet {
        let mut set = DotSet::new();
        for dot in self.dots() {
            let _ = set.insert(dot);
        }
        set
    }
}