minerva 0.2.0

Causal ordering for distributed systems
//! The span-boundary read: [`Verge`], [`Extent`], and
//! [`Rhapsody::extent`] (PRD 0021, the mark store's sequence half).
//!
//! A [`Verge`] names one sided boundary in the sequence's *identity* space:
//! not a position (positions drift under concurrent inserts) but a slot edge
//! pinned to an element that never moves. Two verges delimit a span, and
//! [`Rhapsody::extent`] projects that span onto the current document order,
//! classifying honestly instead of choosing: the covered content, the empty
//! span, the crossed ends, or the boundary whose element has no place here.
//!
//! The sided vocabulary is deliberate, and it is the whole expansion story
//! (Peritext's per-mark expand flags, expressed structurally): whether text
//! typed at a span's edge joins the span is decided by *which element and
//! which side* the caller anchored, not by a stored behavior field.
//!
//! * start [`After(d)`](Verge::After): the expanding left edge. New elements
//!   anchored after `d` land inside.
//! * start [`Before(e)`](Verge::Before): the non-expanding left edge. New
//!   elements woven before `e` land outside; the span still begins at `e`.
//! * end [`Before(f)`](Verge::Before): the expanding right edge. New
//!   elements woven before `f` land inside.
//! * end [`After(e)`](Verge::After): the non-expanding right edge. New
//!   elements anchored after `e` land outside; the span still ends at `e`.
//!
//! [`Origin`](Verge::Origin) and [`Terminus`](Verge::Terminus) are the two
//! fixed document edges (a span to `Terminus` expands with every append),
//! so every combination of edge behaviors is expressible with no schema and
//! no policy in the store; meaning stays the caller's.

extern crate alloc;

use alloc::vec::Vec;

use super::Rhapsody;
use super::placement::{Dot, RawDot};

/// One sided boundary of a span in a sequence's identity space (PRD 0021).
///
/// A verge is where a span's edge sits, said in identities: the fixed
/// document edges ([`Origin`](Self::Origin) before all content,
/// [`Terminus`](Self::Terminus) after all of it), or a slot edge pinned to
/// a woven element ([`Before`](Self::Before) / [`After`](Self::After) a
/// dot), live or order tombstone. Identity anchoring is the hazard cure:
/// a position-anchored span drifts under concurrent inserts (the annotation
/// ends up bolding the wrong words); an identity-anchored span cannot,
/// because a dot never moves and a deleted element still holds its order
/// slot as a tombstone. The side is the expansion rule (the module note):
/// which element and which side a caller anchors decides whether edge
/// inserts join the span, so no expansion field and no schema exists.
///
/// The vocabulary extends the placement [`Anchor`](super::Anchor) (PRD
/// 0018) by the one boundary placement cannot need: `Terminus`, the
/// document end (an insert has a place already; a span may cover "to the
/// end of the document, whatever arrives").
// Deliberately no `Ord`: the derived enum order would not be the document
// boundary order, and a sortable verge invites exactly that misread (the
// created-order hazard one level up). Boundary order is a read against a
// sequence (`extent`), never a property of the vocabulary.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Verge {
    /// The fixed left edge of the document, before all content, present
    /// and future: a span starting here expands with every prepend.
    Origin,
    /// The boundary immediately *before* this element's slot (after any
    /// elements woven before it): as a start, the non-expanding left edge;
    /// as an end, the expanding right edge.
    Before(RawDot),
    /// The boundary immediately *after* this element's slot (before any
    /// elements anchored after it): as a start, the expanding left edge;
    /// as an end, the non-expanding right edge.
    After(RawDot),
    /// The fixed right edge of the document, after all content, present
    /// and future: a span ending here expands with every append.
    Terminus,
}

impl Verge {
    /// The coordinate this verge is pinned to, side-agnostically; the
    /// fixed document edges (`Origin`, `Terminus`) are pinned to none.
    ///
    /// A [`RawDot`], not an identity claim: a verge is payload, so it may
    /// dangle or spell the non-dot zero, and [`Rhapsody::extent`] reads it
    /// totally (a coordinate with no place projects as
    /// [`Extent::Dangling`], never a refusal).
    #[must_use]
    pub const fn dot(self) -> Option<RawDot> {
        match self {
            Self::Origin | Self::Terminus => None,
            Self::Before(dot) | Self::After(dot) => Some(dot),
        }
    }
}

/// The projection of a span onto one replica's current document order: what
/// [`Rhapsody::extent`] hands back, a verdict and never a choice.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Extent {
    /// The end boundary lies at or ahead of the start: the covered visible
    /// dots, in document order. Empty is a covered verdict (the span
    /// survives with nothing currently inside it), not an error.
    Covered(Vec<Dot>),
    /// The end boundary lies behind the start (distinct identities reading
    /// in reverse order): representable under concurrent mark edits, so it
    /// is surfaced for the caller's policy, never silently swapped.
    Inverted,
    /// A dot-anchored verge names an element with no place in this
    /// replica's current order: not yet woven here, parked unplaced before
    /// its repair merge, excised by `condense`, or spelling the non-dot
    /// counter zero (a coordinate that can never name an element). The
    /// flags say which end(s); at least one is true.
    Dangling {
        /// Whether the start verge dangles.
        start: bool,
        /// Whether the end verge dangles.
        end: bool,
    },
}

impl Extent {
    /// The covered dots when the verdict is [`Covered`](Self::Covered),
    /// in document order.
    #[must_use]
    pub fn covered(&self) -> Option<&[Dot]> {
        match self {
            Self::Covered(dots) => Some(dots),
            Self::Inverted | Self::Dangling { .. } => None,
        }
    }
}

impl Rhapsody {
    /// Projects the span between two verges onto the current document order:
    /// the mark store's sequence-side read (PRD 0021).
    ///
    /// The classification is total and the machine chooses nothing:
    ///
    /// * [`Extent::Covered`] carries the visible dots strictly between the
    ///   two boundaries, in document order (possibly none: a span whose
    ///   content was deleted, or whose boundaries currently coincide, is
    ///   *empty*, and empty is a covered verdict, not an error).
    /// * [`Extent::Inverted`] surfaces ends whose boundaries read in reverse
    ///   order (the end at or left of the start with distinct identity):
    ///   representable under concurrent mark edits, so it is a verdict,
    ///   never a panic and never a silent swap.
    /// * [`Extent::Dangling`] surfaces a dot-anchored verge whose element
    ///   has no place in this replica's current order: not yet woven (the
    ///   mark delta outran its sequence delta), parked unplaced (a dangling
    ///   weave before its repair), or excised (`condense` reclaimed the
    ///   tombstone the verge named). The flags say which end.
    ///
    /// Verdicts are identity-based. They read boundaries in the walk order,
    /// with [`Origin`](Verge::Origin) leftmost and
    /// [`Terminus`](Verge::Terminus) rightmost. Equal verges are the empty
    /// span. Distinct verges classify by scan, so a positionally coincident
    /// but backwards pair reads `Inverted`, deterministically at every
    /// converged replica. One such pair is `[Before(first), Origin]` in a
    /// document with nothing before `first`.
    ///
    /// # Shape and cost
    ///
    /// The scan resumes at the start boundary through the same lazy walk the
    /// windowed order read uses (S183). A covered span therefore costs
    /// `O(climb + extent)` in slots stepped, tombstones included, never the
    /// document. The inverted verdict costs the suffix past the start: the
    /// scan must exhaust it to know the end lies behind. The dangling verdict
    /// is two placement-set lookups. An eager `Vec` is the honest contract for
    /// reading covered content (the `order()` precedent); a lazy sibling waits
    /// on a consumer's number (PRD 0021 R6).
    #[must_use]
    pub fn extent(&self, start: Verge, end: Verge) -> Extent {
        // A dot-anchored verge with no place here dangles: surfaced first,
        // because a dangling boundary has no order verdict to give.
        // A verge names a coordinate, not an identity: a coordinate that is
        // not a dot names no woven element, so it dangles exactly as an
        // unwoven one does (the payload funnel, ruling R-91).
        let dangles = |verge: Verge| {
            verge
                .dot()
                .is_some_and(|raw| !Dot::try_from(raw).is_ok_and(|dot| self.is_reachable(dot)))
        };
        let (start_dangles, end_dangles) = (dangles(start), dangles(end));
        if start_dangles || end_dangles {
            return Extent::Dangling {
                start: start_dangles,
                end: end_dangles,
            };
        }
        // Identical verges are the empty span by identity, no scan owed.
        if start == end {
            return Extent::Covered(Vec::new());
        }
        let mut covered = Vec::new();
        // Position the slot scan at the start boundary.
        let mut walk = match start {
            Verge::Origin => self.order_walk(),
            // Nothing lies past the terminus, and the end is a distinct
            // verge, so it lies at or before the start: backwards.
            Verge::Terminus => return Extent::Inverted,
            // The boundary immediately after the slot: the resumed walk's
            // own contract (everything strictly after it).
            Verge::After(raw) => match Dot::try_from(raw)
                .ok()
                .and_then(|dot| self.order_walk_after(dot))
            {
                Some(walk) => walk,
                // Unreachable: placement was verified above, and the walk
                // refuses exactly the unplaced. Refuse over panic anyway.
                None => {
                    return Extent::Dangling {
                        start: true,
                        end: false,
                    };
                }
            },
            // The boundary immediately before the slot: the named element
            // itself is the first covered candidate, then its suffix.
            Verge::Before(raw) => {
                let ends_here = end == Verge::After(raw);
                // The dangle verdict above already proved this coordinate
                // names a placed element, so the crossing is total here.
                let start_dot = Dot::try_from(raw).ok();
                if let Some(dot) = start_dot
                    && self.is_visible(dot)
                {
                    covered.push(dot);
                }
                // The one-element span [Before(d), After(d)].
                if ends_here {
                    return Extent::Covered(covered);
                }
                match start_dot.and_then(|dot| self.order_walk_after(dot)) {
                    Some(walk) => walk,
                    None => {
                        return Extent::Dangling {
                            start: true,
                            end: false,
                        };
                    }
                }
            }
        };
        loop {
            let Some((slot, visible)) = walk.next_slot() else {
                // The document ran out. A Terminus end closes the span
                // there; any other end (a placed slot never stepped past,
                // or the Origin) lies at or before the start boundary.
                return match end {
                    Verge::Terminus => Extent::Covered(covered),
                    _ => Extent::Inverted,
                };
            };
            // The Before boundary sits immediately left of its slot, so it
            // closes the span before the slot is collected; the After
            // boundary sits immediately right, so it closes after.
            let raw_slot: RawDot = slot.into();
            if end == Verge::Before(raw_slot) {
                return Extent::Covered(covered);
            }
            if visible {
                covered.push(slot);
            }
            if end == Verge::After(raw_slot) {
                return Extent::Covered(covered);
            }
        }
    }
}