minerva 0.2.0

Causal ordering for distributed systems
//! The chain-ambiguity invariant's end-to-end law (S212): chains are
//! LAYOUT, never identity, so no interior edit, split, undo, spelling
//! history, or codec pass may lose or alter a single dot's identity,
//! locus, or relative order. This is the metis no-data-loss statement the
//! shell-discipline note's chain-identity section documents; the arithmetic
//! floor is the chain-law Kani harnesses (`wire/snapshot/proofs.rs`), the
//! canonicity floor is the proven decode core's split-chain refusal, and
//! this property is the sampled roof over the whole store.

extern crate alloc;

use alloc::vec::Vec;
use core::num::NonZeroU32;

use crate::kairos::{Clock, TickCounter};
use crate::metis::{Anchor, Dot, DotSet, Locus, Rhapsody};
use proptest::prelude::*;

use super::{Seq, arb_ops_with_paste, run};

/// One interior-ambiguity action against a run-bearing document: an insert
/// INSIDE an existing chain (the split shape), a span delete starting
/// inside it (the survivor-law shape), or the undo shape (retract of the
/// storm's own most recent insert).
#[derive(Clone, Debug)]
enum Storm {
    InsertInside { pick: usize },
    DeleteSpan { pick: usize, span: usize },
    UndoLastInsert,
}

fn arb_storm() -> impl Strategy<Value = Vec<Storm>> {
    let op = prop_oneof![
        3 => (0usize..64).prop_map(|pick| Storm::InsertInside { pick }),
        2 => (0usize..64, 1usize..9).prop_map(|(pick, span)| Storm::DeleteSpan { pick, span }),
        1 => Just(Storm::UndoLastInsert),
    ];
    prop::collection::vec(op, 0..14)
}

proptest! {
    /// The invariant, sampled whole: after any run-bearing history, any
    /// interior-edit storm (chain splits, span deletes, undo retracts),
    /// and a concurrent fork converged back through BOTH merge forms,
    ///
    /// * every dot ever woven still holds its EXACT original locus (a dot
    ///   names one write, and no split or re-spelling may lose or alter
    ///   it: the no-data-loss core);
    /// * the surviving original elements keep their exact relative order
    ///   (interior inserts interleave, they never permute);
    /// * the value is spelling-independent end to end: the churned store,
    ///   its v2 decode, and its snapshot decode are one value with one
    ///   hash, and both wire forms re-encode byte-identically from the
    ///   freshest spelling (canonical bytes are a function of the value,
    ///   never of the split history).
    #[test]
    fn prop_chain_identity_survives_interior_ambiguity(
        ops in arb_ops_with_paste(),
        storm in arb_storm(),
        fork_inserts in 0usize..4,
    ) {
        let (live, _) = run(&ops);
        let mut state = live.iter().fold(Seq::new(), |acc, r| acc.merge(r));
        let fork = state.clone();

        // The before-record: every woven dot's locus (identity) and the
        // visible order, captured before any ambiguity is introduced.
        let before: Vec<(Dot, Locus)> = state
            .store()
            .woven()
            .dots()
            .map(|dot| {
                let locus = state.store().locus(dot).expect("woven dots have loci");
                (dot, locus)
            })
            .collect();
        let before_order: Vec<Dot> = state.store().order();

        // The storm: interior edits driven through the public write flow,
        // a fresh station-9 clock minting above the incumbent bucket head
        // exactly as an editor would.
        let clock = Clock::with_default_config(TickCounter::new(), 9).unwrap();
        let mut inserted: Vec<Dot> = Vec::new();
        for action in &storm {
            match *action {
                Storm::InsertInside { pick } => {
                    let woven: Vec<Dot> = state.store().woven().dots().collect();
                    if woven.is_empty() {
                        continue;
                    }
                    let anchor = Anchor::After(woven[pick % woven.len()].into());
                    if let Some(top) = state.store().children_of(anchor).next()
                        && let Some(locus) = state.store().locus(top)
                    {
                        clock.observe(locus.rank);
                    }
                    let dot = state.next_dot(9);
                    let rank = clock.now(0u16);
                    let mut delta = Rhapsody::new();
                    let woven_fresh = delta.weave(dot, Locus { anchor, rank });
                    prop_assert!(woven_fresh);
                    state.merge_from(&Seq::from_store(delta));
                    inserted.push(dot);
                }
                Storm::DeleteSpan { pick, span } => {
                    let visible = state.store().order();
                    if visible.is_empty() {
                        continue;
                    }
                    let start = pick % visible.len();
                    let mut superseded = DotSet::new();
                    for &dot in visible.iter().skip(start).take(span) {
                        let _ = superseded.insert(dot);
                    }
                    state.merge_from(&Seq::from_context(superseded));
                }
                Storm::UndoLastInsert => {
                    if let Some(dot) = inserted.pop() {
                        let mut superseded = DotSet::new();
                        let _ = superseded.insert(dot);
                        state.merge_from(&Seq::from_context(superseded));
                    }
                }
            }
        }

        // A concurrent fork pastes its own run inside the document, then
        // converges back through BOTH merge forms (the run arm of the
        // in-place fold under interior ambiguity, against the pure form).
        let mut fork = fork;
        if fork_inserts > 0 {
            let fork_clock =
                Clock::with_default_config(TickCounter::new(), 8).unwrap();
            let len = u32::try_from(fork_inserts).expect("small");
            let reservation = fork_clock.now_run(NonZeroU32::new(len).unwrap(), 0u16);
            let first = fork.next_dot(8);
            let anchor = fork
                .store()
                .order()
                .first()
                .map_or(Anchor::Origin, |&head| Anchor::After(head.into()));
            let mut delta = Rhapsody::new();
            prop_assert!(delta.weave_run(first, anchor, reservation));
            fork.merge_from(&Seq::from_store(delta));
        }
        let pure = state.merge(&fork);
        state.merge_from(&fork);
        prop_assert_eq!(&state, &pure);
        state.store().check_order_thread();

        // 1. No data loss: every original dot keeps its exact locus.
        for &(dot, locus) in &before {
            prop_assert_eq!(
                state.store().locus(dot),
                Some(locus),
                "dot ({}, {}) lost or changed its locus under interior ambiguity",
                dot.station(),
                dot.counter()
            );
        }

        // 2. Surviving originals keep their exact relative order.
        let after_order = state.store().order();
        let originals_after: Vec<Dot> = after_order
            .iter()
            .copied()
            .filter(|dot| before.iter().any(|&(seen, _)| seen == *dot))
            .collect();
        let originals_expected: Vec<Dot> = before_order
            .iter()
            .copied()
            .filter(|&dot| state.store().is_visible(dot))
            .collect();
        prop_assert_eq!(originals_after, originals_expected);

        // 3. Spelling independence, end to end: both codecs round-trip to
        // the same value, and re-encode byte-identically from the fresh
        // spelling the decode built.
        let store = state.store();
        let v2 = store.to_bytes();
        let decoded = Rhapsody::from_bytes(&v2).expect("own encoding decodes");
        prop_assert_eq!(&decoded, store);
        prop_assert_eq!(&decoded.to_bytes(), &v2);
        let snapshot = store.to_snapshot_bytes();
        let resnapped = Rhapsody::from_snapshot_bytes(&snapshot).expect("own snapshot decodes");
        prop_assert_eq!(&resnapped, store);
        prop_assert_eq!(resnapped.to_snapshot_bytes(), snapshot);
        prop_assert_eq!(resnapped.to_bytes(), v2);
        prop_assert_eq!(decoded.order(), store.order());
    }
}