minerva 0.2.0

Causal ordering for distributed systems
//! The windowed order read (S183, crdt-vision arc 9): [`Rhapsody::order_walk`]
//! and [`Rhapsody::order_walk_after`], the lazy walk behind
//! [`Rhapsody::order`] and its mid-document resume. The law under test is the
//! suffix law: resuming after a placed element yields exactly `order()`'s
//! elements strictly past that element's slot, tombstone resume points
//! included, and a dot with no place in the current order (never woven, or
//! dangling before its repair) is refused rather than read as "at the end".

extern crate alloc;

use alloc::vec::Vec;

use super::super::d;
use super::{Seq, clock, insert, woven};
use crate::metis::dot::RawDot;
use crate::metis::{Anchor, Dot, DotSet, Dotted, Locus, Rhapsody};

/// A pure removal delta: the victim dot as bare context, the documented
/// delete flow (the survivor law supersedes exactly that dot).
fn delete(replica: &mut Seq, victim: Dot) {
    let mut ctx = DotSet::new();
    let _ = ctx.insert(victim);
    *replica = replica.merge(&Dotted::from_context(ctx));
}

/// Collects a resumed walk, panicking if the resume was refused.
fn suffix_of(seq: &Seq, at: Dot) -> Vec<Dot> {
    seq.store()
        .order_walk_after(at)
        .expect("a placed element resumes")
        .collect()
}

#[test]
fn test_walk_after_yields_the_order_suffix_at_every_slot() {
    let clk = clock(1);
    let mut seq = Seq::new();
    let a = insert(&mut seq, &clk, 1, None);
    let b = insert(&mut seq, &clk, 1, Some(a));
    let c = insert(&mut seq, &clk, 1, Some(b));

    let order = seq.store().order();
    assert_eq!(order, alloc::vec![a, b, c]);
    // Every slot's suffix, the last element's being empty.
    assert_eq!(suffix_of(&seq, a), alloc::vec![b, c]);
    assert_eq!(suffix_of(&seq, b), alloc::vec![c]);
    assert_eq!(suffix_of(&seq, c), Vec::new());
}

#[test]
fn test_walk_takes_a_window_without_reading_the_document() {
    let clk = clock(1);
    let mut seq = Seq::new();
    let mut prev = None;
    let mut dots = Vec::new();
    for _ in 0..10 {
        let dot = insert(&mut seq, &clk, 1, prev);
        dots.push(dot);
        prev = Some(dot);
    }
    let order = seq.store().order();
    // A front window off the lazy whole-document walk.
    let front: Vec<Dot> = seq.store().order_walk().take(3).collect();
    assert_eq!(front, order[..3]);
    // A viewport resumed mid-document: the next 3 after the 4th element.
    let window: Vec<Dot> = seq
        .store()
        .order_walk_after(dots[3])
        .expect("a placed element resumes")
        .take(3)
        .collect();
    assert_eq!(window, order[4..7]);
}

#[test]
fn test_walk_resumes_after_an_order_tombstone() {
    let clk = clock(1);
    let mut seq = Seq::new();
    let a = insert(&mut seq, &clk, 1, None);
    let b = insert(&mut seq, &clk, 1, Some(a));
    let c = insert(&mut seq, &clk, 1, Some(b));
    delete(&mut seq, b);

    // The tombstone keeps its slot in the skeleton walk: its own emit is
    // skipped, its suffix is well defined, so a viewport anchored on a
    // concurrently deleted element still resumes correctly.
    assert_eq!(seq.store().order(), alloc::vec![a, c]);
    assert_eq!(suffix_of(&seq, b), alloc::vec![c]);
    assert_eq!(suffix_of(&seq, a), alloc::vec![c]);
}

#[test]
fn test_walk_resumes_through_before_anchors() {
    // A backward run woven explicitly on the Fugue side: z Before y Before x,
    // with one After child under x, so the resume crosses every continuation
    // shape (before-siblings, the anchor's own emit, its After bucket).
    let clk = clock(1);
    let mut seq = Seq::new();
    let x = seq.next_dot(1);
    seq = seq.merge(&woven(
        x,
        Locus {
            anchor: Anchor::Origin,
            rank: clk.now(0u16),
        },
    ));
    let y = seq.next_dot(1);
    seq = seq.merge(&woven(
        y,
        Locus {
            anchor: Anchor::Before(x.into()),
            rank: clk.now(0u16),
        },
    ));
    let z = seq.next_dot(1);
    seq = seq.merge(&woven(
        z,
        Locus {
            anchor: Anchor::Before(y.into()),
            rank: clk.now(0u16),
        },
    ));
    let w = seq.next_dot(1);
    seq = seq.merge(&woven(
        w,
        Locus {
            anchor: Anchor::After(x.into()),
            rank: clk.now(0u16),
        },
    ));

    let order = seq.store().order();
    assert_eq!(order, alloc::vec![z, y, x, w]);
    // Resuming after a Before-child climbs through its anchor's emit and
    // After bucket: the suffix after y is x then w.
    assert_eq!(suffix_of(&seq, z), alloc::vec![y, x, w]);
    assert_eq!(suffix_of(&seq, y), alloc::vec![x, w]);
    assert_eq!(suffix_of(&seq, x), alloc::vec![w]);
    assert_eq!(suffix_of(&seq, w), Vec::new());
}

#[test]
fn test_walk_refuses_a_dot_with_no_place() {
    let clk = clock(1);
    let mut seq = Seq::new();
    let a = insert(&mut seq, &clk, 1, None);

    // Never woven here: no place, no suffix. The non-dot 0 no longer reaches
    // this door at all --- the identity type carries the law (ruling R-91).
    assert!(seq.store().order_walk_after(d(1, 99)).is_none());
    assert!(Dot::from_parts(1, 0).is_err());
    assert!(seq.store().order_walk_after(d(7, 1)).is_none());

    // A dangling delta: woven, but its anchor chain does not reach the origin,
    // so it has no slot in the current order. Refused, exactly the
    // `is_reachable` boundary; fabricating an empty suffix would read the
    // out-of-order element as "at the document end".
    let mut bare = Rhapsody::new();
    assert!(bare.weave(
        d(2, 1),
        Locus {
            anchor: Anchor::After(RawDot {
                station: 9,
                counter: 9
            }),
            rank: clock(2).now(0u16),
        },
    ));
    let mut ctx = DotSet::new();
    let _ = ctx.insert(d(2, 1));
    seq = seq.merge(&Dotted::try_new(bare, ctx).unwrap());
    assert!(!seq.store().is_reachable(d(2, 1)));
    assert!(seq.store().order_walk_after(d(2, 1)).is_none());

    // The same verdicts through the in-place fold (S182): the placement set
    // is maintained incrementally by the merge_from path, not only rebuilt by
    // the pure-merge path, so a replica reached either way refuses alike.
    let mut incremental = Seq::new();
    incremental.merge_from(&seq);
    assert!(!incremental.store().is_reachable(d(2, 1)));
    assert!(incremental.store().order_walk_after(d(2, 1)).is_none());

    // The repair merge that weaves the missing anchor makes the same element
    // resume: the refusal is about the current order, not the dot.
    let mut repair = Rhapsody::new();
    assert!(repair.weave(
        d(9, 9),
        Locus {
            anchor: Anchor::After(a.into()),
            rank: clock(9).now(0u16),
        },
    ));
    let mut repair_ctx = DotSet::new();
    let _ = repair_ctx.insert(d(9, 9));
    seq = seq.merge(&Dotted::try_new(repair, repair_ctx).unwrap());
    assert!(seq.store().is_reachable(d(2, 1)));
    assert_eq!(suffix_of(&seq, a), alloc::vec![d(9, 9), d(2, 1)]);
    assert_eq!(suffix_of(&seq, d(9, 9)), alloc::vec![d(2, 1)]);
    assert_eq!(suffix_of(&seq, d(2, 1)), Vec::new());

    // The repaired verdicts through the in-place fold: absorbing the whole
    // state records `(2, 1)` (dot order) before its anchor `(9, 9)`, so the
    // element parks dangling mid-fold and the repair worklist re-places it
    // when the anchor's locus lands, the incremental half of the placement
    // maintenance.
    let mut incremental = Seq::new();
    incremental.merge_from(&seq);
    assert!(incremental.store().is_reachable(d(2, 1)));
    assert_eq!(suffix_of(&incremental, d(9, 9)), alloc::vec![d(2, 1)]);
}