minerva 0.2.0

Causal ordering for distributed systems
//! Shared-mark flows: the register recipes applied to spans, and the
//! integration with the sequence store that the mark store exists for.

extern crate alloc;

use alloc::vec::Vec;

use crate::kairos::{Clock, TickCounter};
use crate::metis::{Composer, DotSet, Extent, Locus, Rhapsody, Scholia, Scholion, Verge};

use super::super::support::dot;
use super::{Marks, mint};
use crate::metis::dot::RawDot;

fn clock(station: u32) -> Clock<TickCounter> {
    Clock::with_default_config(TickCounter::new(), station).unwrap()
}

/// The shared-mark lifecycle: mint at one replica, converge at another,
/// edit as a register supersede, retract; concurrent edits surface as
/// siblings, the machine choosing nothing.
#[test]
fn test_a_shared_mark_converges_edits_and_retracts() {
    let mut alice: Marks = Composer::new(1);
    let mut bob: Marks = Composer::new(2);
    let span = |tag| Scholion {
        start: Verge::Origin,
        end: Verge::Terminus,
        tag,
    };

    // Mint at alice; bob learns it through the owed exchange.
    let (mark, _delta) = mint(&mut alice, span("comment:open"));
    let _ = bob.absorb(1, &alice.owed_to(bob.state().context()));
    assert_eq!(bob.state().store().get(mark), Some(&span("comment:open")));

    // Edit at bob: a register supersede of exactly that mark's dot, one
    // covered delta, whole-scholion replacement (ends and tag move
    // together or not at all).
    let mut superseded = DotSet::new();
    assert!(superseded.insert(mark));
    let (edited, delta) = bob.compose_super(
        |dot| Scholia::singleton(dot, span("comment:resolved")),
        |_| superseded.clone(),
    );
    let _ = alice.absorb(2, &delta);
    for replica in [&alice, &bob] {
        assert_eq!(replica.state().store().len(), 1);
        assert_eq!(
            replica.state().store().get(edited),
            Some(&span("comment:resolved")),
        );
    }

    // Concurrent edits of one mark: both replicas supersede `edited`
    // before hearing each other; the survivors are two sibling scholia,
    // both readable, neither chosen.
    let mut displaced = DotSet::new();
    assert!(displaced.insert(edited));
    let (_, from_alice) = alice.compose_super(
        |dot| Scholia::singleton(dot, span("alice:wording")),
        |_| displaced.clone(),
    );
    let (_, from_bob) = bob.compose_super(
        |dot| Scholia::singleton(dot, span("bob:wording")),
        |_| displaced.clone(),
    );
    let _ = alice.absorb(2, &from_bob);
    let _ = bob.absorb(1, &from_alice);
    assert_eq!(alice.state(), bob.state());
    assert_eq!(
        alice.state().store().marks().count(),
        2,
        "concurrent edits surface as siblings"
    );

    // Retract everything observed: the marks are gone everywhere, and a
    // stale copy cannot resurrect them (the survivor law).
    let retract = alice.retract(alice.state().store().observed());
    let _ = bob.absorb(1, &retract);
    assert!(alice.state().store().is_empty());
    assert!(bob.state().store().is_empty());
}

/// The integration the store exists for: marks ride element identity
/// through concurrent edits of the text they annotate, and the projection
/// verdicts degrade honestly.
#[test]
fn test_marks_ride_identity_through_concurrent_text_edits() {
    // One text pair and one mark pair per replica (separate documents,
    // separate contexts: the parallel-pair recipe).
    let mut text_a: Composer<Rhapsody> = Composer::new(1);
    let mut text_b: Composer<Rhapsody> = Composer::new(2);
    let mut marks_a: Marks = Composer::new(1);
    let mut marks_b: Marks = Composer::new(2);
    let clk_a = clock(1);

    // Alice types "abc".
    let mut caret: Option<RawDot> = None;
    let mut typed = Vec::new();
    for _ in 0..3 {
        let anchor = text_a.state().store().anchor_for_visual_insert(caret);
        if let Some(top) = text_a.state().store().children_of(anchor).next()
            && let Some(locus) = text_a.state().store().locus(top)
        {
            clk_a.observe(locus.rank);
        }
        let rank = clk_a.now(0u16);
        let (woven, _) = text_a.compose(|assigned| {
            let mut delta = Rhapsody::new();
            assert!(delta.weave(assigned, Locus { anchor, rank }));
            (delta, DotSet::new())
        });
        typed.push(woven);
        caret = Some(woven.into());
    }
    let (first, marked, last) = (typed[0], typed[1], typed[2]);

    // Alice marks the middle element with an expanding span around it:
    // [After(first), Before(last)], both edges open toward the gap inserts.
    let scholion = Scholion {
        start: Verge::After(first.into()),
        end: Verge::Before(last.into()),
        tag: "bold",
    };
    let (_, mark_delta) = mint(&mut marks_a, scholion);

    // Bob learns both documents.
    let _ = text_b.absorb(1, &text_a.owed_to(text_b.state().context()));
    let _ = marks_b.absorb(1, &mark_delta);

    // Concurrently: bob types into the marked gap (after the marked
    // element), and alice types at the document start (outside the span).
    // Both converge.
    let clk_b = clock(2);
    let anchor = text_b
        .state()
        .store()
        .anchor_for_visual_insert(Some(marked.into()));
    if let Some(top) = text_b.state().store().children_of(anchor).next()
        && let Some(locus) = text_b.state().store().locus(top)
    {
        clk_b.observe(locus.rank);
    }
    let rank_x = clk_b.now(0u16);
    let (inside_insert, bob_delta) = text_b.compose(|dot| {
        let mut delta = Rhapsody::new();
        assert!(delta.weave(
            dot,
            Locus {
                anchor,
                rank: rank_x
            }
        ));
        (delta, DotSet::new())
    });
    let anchor = text_a.state().store().anchor_for_visual_insert(None);
    if let Some(top) = text_a.state().store().children_of(anchor).next()
        && let Some(locus) = text_a.state().store().locus(top)
    {
        clk_a.observe(locus.rank);
    }
    let rank_y = clk_a.now(0u16);
    let (outside_insert, alice_delta) = text_a.compose(|dot| {
        let mut delta = Rhapsody::new();
        assert!(delta.weave(
            dot,
            Locus {
                anchor,
                rank: rank_y
            }
        ));
        (delta, DotSet::new())
    });
    let _ = text_a.absorb(2, &bob_delta);
    let _ = text_b.absorb(1, &alice_delta);
    assert_eq!(text_a.state(), text_b.state());
    assert_eq!(
        text_a.state().store().order(),
        [outside_insert, first, marked, inside_insert, last],
    );

    // The mark did not drift: at both replicas it covers exactly the
    // marked region, the concurrent inside-insert joined it (expanding
    // edges), and the outside-insert did not.
    let held = marks_b.state().store().marks().next().unwrap();
    for text in [&text_a, &text_b] {
        assert_eq!(
            text.state().store().extent(held.start, held.end),
            Extent::Covered([marked, inside_insert].into()),
        );
    }

    // Delete the span's interior everywhere: the span survives, empty,
    // its boundaries riding the order tombstones.
    let mut gone = DotSet::new();
    assert!(gone.insert(marked));
    assert!(gone.insert(inside_insert));
    let delta = text_a.retract(gone);
    let _ = text_b.absorb(1, &delta);
    assert_eq!(
        text_b.state().store().extent(held.start, held.end),
        Extent::Covered(Vec::new()),
    );
}

/// The retention pin set: `anchor_dots` names exactly the sequence dots
/// the live marks are pinned to, and stops naming a mark's dots once it
/// is retracted.
#[test]
fn test_anchor_dots_reads_the_pin_set() {
    let mut marks: Marks = Composer::new(1);
    let (first, _) = mint(
        &mut marks,
        Scholion {
            start: Verge::After(RawDot {
                station: 7,
                counter: 1,
            }),
            end: Verge::Before(RawDot {
                station: 7,
                counter: 4,
            }),
            tag: "comment",
        },
    );
    let (_, _) = mint(
        &mut marks,
        Scholion {
            start: Verge::Origin,
            end: Verge::After(RawDot {
                station: 8,
                counter: 2,
            }),
            tag: "selection",
        },
    );

    let pins = marks.state().store().anchor_dots();
    let pinned: Vec<_> = pins.dots().collect();
    assert_eq!(pinned, [dot(7, 1), dot(7, 4), dot(8, 2)]);

    // Retracting the first mark releases its pins.
    let mut superseded = DotSet::new();
    assert!(superseded.insert(first));
    let _ = marks.retract(superseded);
    let pinned: Vec<_> = marks.state().store().anchor_dots().dots().collect();
    assert_eq!(pinned, [dot(8, 2)]);

    // Anchor pins are load-bearing for retention policy, caller-side: the
    // set is what a condense cadence subtracts before minting claims (PRD
    // 0021 R5).
}