minerva 0.2.0

Causal ordering for distributed systems
//! The move record: [`Metathesis`] and [`Metatheses`] (PRD 0022, stage C
//! of the structured document horizon, the reorder half).
//!
//! A *metathesis* is one movement testimony. It places an existing `target`
//! at a new [`Locus`] without changing the target dot. The name is the
//! grammarians' own for transposition, and it
//! carries the design's one load-bearing idea: *a move is a second placement
//! testimony for an existing identity*, exactly the `(dot, locus)` shape a
//! weave already records, never a delete-plus-reinsert that forks it.
//! `Metatheses` is the store of them, under the survivor law.
//!
//! The hazard this closes is the field-pinned identity fork: compose a move
//! as delete-plus-reinsert and a concurrent edit lands on the deleted
//! identity, converging stranded at the old slot. With movement as testimony
//! the identity survives, so an edit anchored to the moved element follows
//! it. The read that resolves placement is
//! [`Rhapsody::recension`](crate::metis::Rhapsody::recension).
//!
//! # The record never chooses
//!
//! This store is *record*, not decision. It remembers every testimony that
//! survives the survivor law, and the winner among conflicting moves is
//! decided only in the recension read, by the fixed public rank order the
//! delivery buffer already uses.
//!
//! Because that read is a pure function of converged state rather than an
//! operation log, retracting a testimony is lawful and useful: an
//! observed-remove of a move's dot *undoes the move* everywhere,
//! convergently.
//!
//! # The recipes
//!
//! A `Metatheses` behaves exactly like the payload store it carries
//! (`DotFun<Metathesis>`), so the register recipes apply verbatim, in a
//! causal pair parallel to the text's (the Scholia precedent):
//!
//! * *Move an element*: mint the testimony through the facade, rank from
//!   the mover's clock, outranking the target's current effective bucket
//!   the same way a visual insert does:
//!   `composer.compose(|dot| (Metatheses::singleton(dot, metathesis),
//!   DotSet::new()))`.
//! * *Move it again*: just another testimony; the highest-rank surviving,
//!   non-refused testimony for a target wins in the read. To keep the
//!   record tidy, supersede your own earlier moves of that target
//!   (`moves_of(target)` is the supersede set) through `compose_super`.
//! * *Undo a move*: `composer.retract` of the testimony's dot; earlier
//!   surviving testimonies (or the birth placement) take over in the read.

extern crate alloc;

use super::rhapsody::Locus;
use super::{Dot, DotFun, DotFunIter, DotSet};
use crate::metis::dot::RawDot;

mod store;
mod wire;

pub use wire::{MetathesesDecodeBudget, MetathesesDecodeError};

/// One movement testimony: an existing element re-placed by identity.
///
/// `target` is the moved element's dot (its identity preserved) and `to`
/// is where it now hangs, a fresh [`Locus`]: the new anchor, and the rank
/// that both orders it among its new siblings and timestamps the move in
/// the recension's replay.
///
/// A metathesis is immutable under its own dot (content follows the dot,
/// axis law 3): re-moving is a fresh testimony, undoing is a retract of
/// this one.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Metathesis {
    /// The element being moved, as a raw coordinate ([`RawDot`]).
    ///
    /// A target is payload, not an identity claim: the record stores it
    /// verbatim, wire codecs carry it verbatim (a zero counter included),
    /// and a target that names no element supersedes nothing and places
    /// nothing (ruling R-91; the S117 anchor law's movement half).
    pub target: RawDot,
    /// Where it now hangs: the new anchor and the rank that timestamps
    /// this testimony in the replay's total order.
    pub to: Locus,
}

/// The move record: dot-tagged [`Metathesis`] testimonies under the
/// survivor law, the movement instance of the open
/// [`DotStore`](crate::metis::DotStore) axis (PRD 0022).
///
/// Structurally the payload store applied to movement
/// (`DotFun<Metathesis>`), with pure delegation for its `DotStore`
/// instance: no new merge semantics, so concurrent moves of one element
/// are surfaced sibling testimonies here and resolved only in the
/// [`recension`](crate::metis::Rhapsody::recension) read, by rank. What
/// the named type adds is the movement vocabulary and the two reads the
/// recipes need: [`moves_of`](Self::moves_of) (the supersede set for
/// re-moving tidily) and [`targets`](Self::targets) (which identities
/// have movement testimony at all).
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Metatheses {
    /// The testimonies, one metathesis per dot.
    moves: DotFun<Metathesis>,
}

impl Metatheses {
    /// Creates the empty record: no testimonies, the lattice bottom.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            moves: DotFun::new(),
        }
    }

    /// A one-testimony record, the delta building block: `metathesis`
    /// under `dot`, nothing else.
    #[must_use]
    pub fn singleton(dot: Dot, metathesis: Metathesis) -> Self {
        Self {
            moves: DotFun::singleton(dot, metathesis),
        }
    }

    /// Records a testimony: `metathesis` under `dot`, returning whether
    /// the record changed. The one refusal is the payload store's
    /// ([`DotFun::insert`]): an already-carried dot is a no-op. The non-dot
    /// `0` no longer needs a refusal --- [`Dot`] carries that law (ruling
    /// R-91).
    #[must_use = "the write is refused (returns false) for an already-carried dot"]
    pub fn insert(&mut self, dot: Dot, metathesis: Metathesis) -> bool {
        self.moves.insert(dot, metathesis)
    }

    /// The testimony under `dot`, if the record carries it.
    #[must_use]
    pub fn get(&self, dot: Dot) -> Option<&Metathesis> {
        self.moves.get(dot)
    }

    /// Iterates the `(dot, &metathesis)` entries in ascending dot order.
    #[must_use]
    pub fn iter(&self) -> DotFunIter<'_, Metathesis> {
        self.moves.iter()
    }

    /// The surviving testimonies, ascending by their own dot.
    pub fn testimonies(&self) -> impl Iterator<Item = &Metathesis> + '_ {
        self.moves.values()
    }

    /// The testimony dots currently naming `target`: the supersede set
    /// for a tidy re-move (mint the fresh testimony superseding exactly
    /// these, the register discipline applied per target), and the
    /// retract set for a whole-target undo.
    #[must_use]
    pub fn moves_of(&self, target: RawDot) -> DotSet {
        let mut named = DotSet::new();
        for (dot, metathesis) in &self.moves {
            if metathesis.target == target {
                let _ = named.insert(dot);
            }
        }
        named
    }

    /// The elements with any surviving movement testimony, ascending,
    /// each once.
    ///
    /// A target is payload, so it may spell the non-dot `0`; such a target
    /// names no element and is dropped here, exactly as before (the
    /// payload-to-identity funnel, ruling R-91).
    #[must_use]
    pub fn targets(&self) -> DotSet {
        let mut targets = DotSet::new();
        for metathesis in self.moves.values() {
            if let Ok(target) = Dot::try_from(metathesis.target) {
                let _ = targets.insert(target);
            }
        }
        targets
    }

    /// The number of surviving testimonies.
    #[must_use]
    pub fn len(&self) -> usize {
        self.moves.len()
    }

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

impl<'a> IntoIterator for &'a Metatheses {
    type Item = (Dot, &'a Metathesis);
    type IntoIter = DotFunIter<'a, Metathesis>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}