minerva 0.2.0

Causal ordering for distributed systems
use core::marker::PhantomData;

mod cut;
mod dot_set;
mod vector;

/// Evidence restricted under a scope's roster, branded by it.
///
/// A `Scoped` value is minted only by
/// [`Scope::restrict`](super::Scope::restrict),
/// [`Scope::restrict_dots`](super::Scope::restrict_dots), and
/// [`Scope::restrict_all`](super::Scope::restrict_all), and every scoped fold
/// keeps the same brand, so a value can only ever be combined with siblings from
/// the same scope. The brand carries no data; it is pure [`PhantomData`], and
/// the crate is `forbid(unsafe_code)` throughout.
///
/// `PartialOrd` is deliberately *not* implemented. Ordering is offered only in
/// method form ([`partial_cmp_scoped`](Scoped::partial_cmp_scoped),
/// [`happens_before`](Scoped::happens_before)) so the `<`, `<=`, `>=`, `>`
/// operators cannot be reached, and so generic code bounded on `PartialOrd`
/// cannot compare scoped evidence as if the verdict were global. The scoped
/// order is sound *within* the scope by PRD 0013's preservation law and
/// inexpressible across scopes by the brand.
///
/// # The brand certifies the invocation, not the roster value
///
/// Two scopes opened over *identical* rosters still do not unify: the brand is a
/// fresh invariant lifetime per [`with_scope`](super::with_scope) call, not a
/// function of the roster. This is deliberate and load-bearing. The soundness the
/// brand carries is "these two values were read under the *same act of
/// restriction*", and two separate `with_scope([1u32], ..)` calls are two acts,
/// even over the same station set: a caller who restricted under one and
/// compared under the other would be reasoning across a boundary the type cannot
/// see is safe. So a same-roster cross-scope comparison is a compile error too:
///
/// ```compile_fail
/// use minerva::metis::{with_scope, VersionVector};
///
/// let a = VersionVector::new();
/// let b = VersionVector::new();
/// with_scope([1u32], |first| {
///     let ra = first.restrict(&a);
///     // Same roster `[1u32]`, a *different* invocation: the brands are still
///     // distinct invariant lifetimes, so this does not typecheck. The brand
///     // tracks the call, never the roster value.
///     with_scope([1u32], |second| {
///         let rb = second.restrict(&b);
///         let _ = ra.happens_before(&rb);
///     });
/// });
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Scoped<'brand, T> {
    /// The restricted evidence.
    pub(in crate::metis::scope) value: T,
    /// The scope's brand, carried so this value unifies only with siblings
    /// from the same [`with_scope`](super::with_scope) invocation.
    pub(in crate::metis::scope) _brand: PhantomData<fn(&'brand ()) -> &'brand ()>,
}

impl<'brand, T> Scoped<'brand, T> {
    /// Maps the branded value, keeping the brand, so a value derived from scoped
    /// evidence stays scoped.
    ///
    /// The functor that lets a caller compute a derived quantity (a count, a
    /// projection, a wrapped read) *inside* the scope without minting a fresh
    /// brand or laundering through a `forget` escape and re-restricting. The
    /// closure sees only the value, never the brand, and the result re-enters
    /// the same scope, so a derived value cannot be compared against evidence
    /// from another scope any more than its source could. `map` is the general
    /// door the typed folds (`merge` on version vectors, `floor` on dot sets)
    /// are special cases of; reach for those where they exist, this where they
    /// do not.
    ///
    /// The brand certifies the *invocation*, never the value: `map` cannot make
    /// a value from one scope agree with a value from another, because the two
    /// `'brand`s still do not unify whatever `f` computes.
    #[must_use]
    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Scoped<'brand, U> {
        Scoped {
            value: f(self.value),
            _brand: PhantomData,
        }
    }
}