minerva 0.2.0

Causal ordering for distributed systems
use super::VersionVector;

impl VersionVector {
    /// Returns the restriction of this vector to `roster`: the same counts on
    /// the stations the roster names, absent (zero) everywhere else.
    ///
    /// The *base-change read* (PRD 0013). A vector is a family over the station
    /// base, one counter per station, and `restrict` pulls the family back
    /// along a sub-roster inclusion: the same causal record, read *as far as
    /// these stations are concerned*. The base is a discrete set, so
    /// restriction selects whole per-station fibers and never looks inside
    /// one. It is therefore exact everywhere:
    ///
    /// * it commutes with [`merge`](Self::merge) and [`meet`](Self::meet);
    /// * it preserves `<=`;
    /// * it sends bottom to bottom;
    /// * it is the identity on any roster covering the vector's support;
    /// * it composes by roster intersection
    ///   (`v.restrict(a).restrict(b) == v.restrict(a intersect b)`).
    ///
    /// # The witness travels one way
    ///
    /// Restriction *preserves* order and *reflects* concurrency, and neither
    /// converse holds. If `a <= b` then `a.restrict(r) <= b.restrict(r)`; and
    /// if the restrictions are concurrent the originals already were (the two
    /// leading stations survive in `r`). But two concurrent vectors can
    /// restrict to ordered or equal ones, because restriction can drop exactly
    /// the stations that witnessed the disagreement. A concurrency verdict on
    /// restrictions is therefore global evidence. An order verdict on
    /// restrictions certifies nothing beyond the sub-roster: restricted order
    /// is order *over `r`* alone. Comparing restrictions and acting as if the
    /// verdict were global is the *created-order hazard*, the one PRD 0013
    /// exists to name.
    ///
    /// Roster stations the vector never observed contribute nothing (absent
    /// equals zero), duplicate roster entries are harmless, and canonical form
    /// is preserved: no zero counter is ever stored.
    #[must_use]
    pub fn restrict(&self, roster: impl IntoIterator<Item = u32>) -> Self {
        let mut restricted = Self::new();
        for station in roster {
            restricted.observe(station, self.get(station));
        }
        restricted
    }

    /// Glues two local sections into one, exactly: checks that both sides
    /// agree on every station in `overlap`, then returns their
    /// [`merge`](Self::merge); refuses with the first disagreement otherwise.
    ///
    /// The *descent read* (PRD 0013), the exact companion of the lax join.
    /// `merge` is total because it resolves every disagreement by pointwise
    /// maximum, which is the right fold for *knowledge*: the larger count is
    /// simply the newer fact. It is the wrong fold for *authority*. Two
    /// partial views can each claim to speak for one station with different
    /// counts. The merge then fabricates a section nobody vouched for.
    /// Downstream of a cut claim, that fabrication over-claims in the
    /// unrecoverable direction: a meet over over-claimed cuts steers
    /// reclamation past state a peer still needs (PRD 0011 R8). `try_glue` is
    /// the fold that refuses instead: agreement is checked exactly where
    /// authority overlaps, and the refusal carries the witness.
    ///
    /// `overlap` is the set of stations *both* sections claim authority over.
    /// The caller declares it, exactly as it declares the
    /// [`Stability`](crate::metis::Stability) roster. Under absent-equals-zero
    /// a vector does not carry its own domain. Which stations a section speaks
    /// for is therefore a deployment fact the crate cannot infer. Stations are
    /// compared by [`get`](Self::get) in the order the caller's iterator
    /// yields them, so the error names the first disagreement in that order.
    ///
    /// Sections cut from one vector always re-glue to it: for any `v`,
    /// `v.restrict(a).try_glue(&v.restrict(b), o)` succeeds for every overlap
    /// `o` inside `a intersect b` and returns `v.restrict(a union b)`. That is
    /// the sheaf condition, in code; an empty overlap makes the check vacuous
    /// and the glue degenerates to the lax merge, deliberately.
    ///
    /// # Errors
    ///
    /// [`Disagreement`] if the sections differ on a station in `overlap`,
    /// carrying the station and both counts: the material a caller needs to
    /// decide who over-claims, or to fall back to the lax `merge` on purpose.
    pub fn try_glue(
        &self,
        other: &Self,
        overlap: impl IntoIterator<Item = u32>,
    ) -> Result<Self, Disagreement> {
        for station in overlap {
            let (left, right) = (self.get(station), other.get(station));
            if left != right {
                return Err(Disagreement {
                    station,
                    left,
                    right,
                });
            }
        }
        Ok(self.merge(other))
    }
}

/// The rejected outcome of [`VersionVector::try_glue`]: the two sections
/// disagree on `station`, a station both claimed authority over, so no glued
/// section exists and nothing was folded.
///
/// This is the descent read's typed refusal (PRD 0013). The lax
/// [`merge`](VersionVector::merge) would have absorbed the disagreement by
/// pointwise maximum, fabricating a section nobody vouched for; the glue
/// refuses and hands back the seam instead. Which side over-claims, and what a
/// disagreement costs, is the caller's to decide: the witness is evidence, not
/// a verdict.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("sections disagree on station {station}: {left} against {right}")]
pub struct Disagreement {
    /// The first overlap station (in the caller's overlap order) on which the
    /// sections disagree.
    pub station: u32,
    /// `self`'s count for the station.
    pub left: u64,
    /// `other`'s count for the station.
    pub right: u64,
}