minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec::Vec;

use crate::metis::{
    Anchor, Cut, DotSet, Dotted, Metatheses, RefoundMap, RefoundMapDecodeBudget,
    RefoundMapDecodeError, Rhapsody,
};

use super::{Seq, clock, d, delete, fixture_map, insert};

/// The golden layout fixture for version 1 (the cross-repository fixture
/// obligation the frame family carries): every byte pinned, and the frame
/// round-trips to the map that produced it.
#[test]
fn test_refound_map_to_bytes_layout() {
    let map = fixture_map();
    let frame = map.to_bytes();
    assert_eq!(
        frame,
        [
            0x01, // version (v1)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // station_count = 2
            // row: station 1, three woven, two live (old 1 -> new 1, old 3 -> new 2)
            0x00, 0x00, 0x00, 0x01, // station = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // ceiling = 3
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // live = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // new 1 <- old 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // new 2 <- old 3
            // row: station 2, one woven, one live (old 1 -> new 1)
            0x00, 0x00, 0x00, 0x02, // station = 2
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // ceiling = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // live = 1
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // new 1 <- old 1
        ]
    );
    assert_eq!(RefoundMap::from_bytes(&frame), Ok(map));
}

/// The empty map (an empty document's re-foundation) is the empty frame:
/// header only, and it round-trips.
#[test]
fn test_refound_map_empty_frame() {
    let refounded = Rhapsody::new()
        .refound(&Metatheses::new())
        .expect("the empty stratum folds");
    let (_, map) = refounded.into_parts();
    let frame = map.to_bytes();
    assert_eq!(
        frame,
        [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    );
    assert_eq!(RefoundMap::from_bytes(&frame), Ok(map));
}

/// A fully swept station rides as a zero-count row: its ceiling still
/// bounds the affine window, so dropping the row would change
/// `translate`, and the decoded map answers exactly as the original (the
/// swept dot refused, the window dot shifted).
#[test]
fn test_refound_map_swept_station_row() {
    let mut replica: Seq = Dotted::new();
    let one = clock(1);
    let three = clock(3);
    let a = insert(&mut replica, &one, 1, Anchor::Origin);
    let b = insert(&mut replica, &three, 3, Anchor::After(a.into()));
    delete(&mut replica, b);

    let refounded = replica
        .store()
        .refound(&Metatheses::new())
        .expect("a sealed stratum folds");
    let (_, map) = refounded.into_parts();
    assert_eq!(map.translate(d(3, 1)), None, "the swept dot has no image");

    let frame = map.to_bytes();
    let decoded = RefoundMap::from_bytes(&frame).expect("a swept-station frame decodes");
    assert_eq!(decoded, map);
    assert_eq!(decoded.to_bytes(), frame);
    assert_eq!(decoded.translate(d(3, 1)), None);
    assert_eq!(
        decoded.translate(d(3, 2)),
        Some(d(3, 1)),
        "the affine window opens just above the swept station's ceiling"
    );
}

/// Entry order is semantic, not canonical-form redundancy: the position
/// of an old index *is* the new index it compacts onto, so two frames
/// with the same entries in different positions are different maps, and
/// both are accepted as canonical (each re-encodes to itself).
#[test]
fn test_refound_map_entry_order_is_semantic() {
    let map = fixture_map();
    let frame = map.to_bytes();
    let mut permuted = frame.clone();
    // Swap station 1's two entries (bytes 29..37 and 37..45).
    let head: [u8; 8] = frame[29..37].try_into().unwrap();
    let tail: [u8; 8] = frame[37..45].try_into().unwrap();
    permuted[29..37].copy_from_slice(&tail);
    permuted[37..45].copy_from_slice(&head);

    let decoded = RefoundMap::from_bytes(&permuted).expect("the permuted frame is canonical too");
    assert_ne!(decoded, map, "the permutation is the map's content");
    assert_eq!(map.translate(d(1, 1)), Some(d(1, 1)));
    assert_eq!(
        decoded.translate(d(1, 1)),
        Some(d(1, 2)),
        "old 1 now lands second"
    );
    assert_eq!(
        decoded.translate(d(1, 3)),
        Some(d(1, 1)),
        "old 3 now lands first"
    );
    assert_eq!(decoded.to_bytes(), permuted);
}

/// A cut-bound map (ceilings raised past the woven high water by the
/// witnessed epoch cut) rides the same rows and round-trips: the sparse
/// gap below the cut stays untranslatable, the affine window moves up.
#[test]
fn test_refound_map_cut_bound_map_round_trips() {
    let mut replica: Seq = Dotted::new();
    let one = clock(1);
    let a = insert(&mut replica, &one, 1, Anchor::Origin);
    let _b = insert(&mut replica, &one, 1, Anchor::After(a.into()));

    let refounded = replica
        .store()
        .refound(&Metatheses::new())
        .expect("a sealed stratum folds");
    let (_, mut map) = refounded.into_parts();

    // The witnessed cut sits two dots past station 1's woven high water.
    let mut have = DotSet::new();
    for dot in 1..=4 {
        assert!(have.insert(d(1, dot)));
    }
    map.bind_cut(&Cut::floor_of(&have));
    assert_eq!(
        map.translate(d(1, 3)),
        None,
        "below the cut, unwitnessed: no image"
    );
    assert_eq!(
        map.translate(d(1, 5)),
        Some(d(1, 3)),
        "above the cut: affine"
    );

    let frame = map.to_bytes();
    let decoded = RefoundMap::from_bytes(&frame).expect("a cut-bound frame decodes");
    assert_eq!(decoded, map);
    assert_eq!(decoded.to_bytes(), frame);
    assert_eq!(decoded.translate(d(1, 3)), None);
    assert_eq!(decoded.translate(d(1, 5)), Some(d(1, 3)));
}

/// An unknown version byte refuses before any structural read; there is
/// no fallback decode (v1 is the only version).
#[test]
fn test_refound_map_unknown_version_refused() {
    let mut frame = fixture_map().to_bytes();
    frame[0] = 0x02;
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::UnknownVersion(0x02))
    );
    assert_eq!(
        RefoundMap::from_bytes(&[]),
        Err(RefoundMapDecodeError::UnexpectedLength {
            expected: 9,
            found: 0,
        })
    );
}

/// A frame shorter than its declared counts requires refuses in `O(1)`
/// against the per-row and per-entry minimums, at the header, mid-row,
/// and mid-entry alike.
#[test]
fn test_refound_map_truncation_refused() {
    let frame = fixture_map().to_bytes();
    // Cut mid-way through the final entry.
    let cut = &frame[..frame.len() - 4];
    assert!(matches!(
        RefoundMap::from_bytes(cut),
        Err(RefoundMapDecodeError::UnexpectedLength { .. })
    ));
    // A station count the buffer cannot back refuses without allocation.
    let mut hostile = Vec::from(&[0x01u8][..]);
    hostile.extend_from_slice(&u64::MAX.to_be_bytes());
    assert!(matches!(
        RefoundMap::from_bytes(&hostile),
        Err(RefoundMapDecodeError::UnexpectedLength { .. })
    ));
}

/// The impossible-count refusal fires before any row is decoded: a frame
/// declaring one row more than its body backs refuses on the count
/// division alone, even when the first carried row is malformed (a zeroed
/// ceiling would surface as `ZeroCeiling` if rows were processed first).
#[test]
fn test_refound_map_impossible_count_refuses_before_rows() {
    let mut frame = fixture_map().to_bytes();
    // Declare a fourth row the 64-byte body (three row minimums) cannot
    // back.
    frame[1..9].copy_from_slice(&4u64.to_be_bytes());
    // Corrupt the first row's ceiling: reaching it would refuse as
    // `ZeroCeiling`, so `UnexpectedLength` proves the preflight ran first.
    for byte in &mut frame[13..21] {
        *byte = 0;
    }
    assert!(matches!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::UnexpectedLength { .. })
    ));
}

/// Trailing bytes past the declared frame refuse: the exact door decodes
/// exactly one frame.
#[test]
fn test_refound_map_trailing_bytes_refused() {
    let mut frame = fixture_map().to_bytes();
    let exact = frame.len();
    frame.push(0x00);
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::UnexpectedLength {
            expected: exact,
            found: exact + 1,
        })
    );
}

/// A zero ceiling is non-canonical: a station enters the map only by
/// owning identities at or below its ceiling, so no encoder produces one.
#[test]
fn test_refound_map_zero_ceiling_refused() {
    let mut frame = fixture_map().to_bytes();
    for byte in &mut frame[13..21] {
        *byte = 0;
    }
    // The row's live count (2) now exceeds its ceiling (0) too; the
    // ceiling refusal must come first, naming the sharper shape.
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::ZeroCeiling { station: 1 })
    );
}

/// Unsorted and duplicate station rows are non-canonical: the map's
/// ceilings enumerate strictly ascending.
#[test]
fn test_refound_map_non_ascending_stations_refused() {
    // Two zero-count rows, hand-spelled (both are 20 bytes, so the frame
    // shape is easiest to state directly): stations 2 then 1.
    let mut frame = Vec::from(&[0x01u8][..]);
    frame.extend_from_slice(&2u64.to_be_bytes());
    for station in [2u32, 1u32] {
        frame.extend_from_slice(&station.to_be_bytes());
        frame.extend_from_slice(&1u64.to_be_bytes()); // ceiling
        frame.extend_from_slice(&0u64.to_be_bytes()); // live
    }
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::NonAscendingStations {
            previous: 2,
            found: 1,
        })
    );

    let mut duplicated = Vec::from(&[0x01u8][..]);
    duplicated.extend_from_slice(&2u64.to_be_bytes());
    for _ in 0..2 {
        duplicated.extend_from_slice(&1u32.to_be_bytes());
        duplicated.extend_from_slice(&1u64.to_be_bytes());
        duplicated.extend_from_slice(&0u64.to_be_bytes());
    }
    assert_eq!(
        RefoundMap::from_bytes(&duplicated),
        Err(RefoundMapDecodeError::NonAscendingStations {
            previous: 1,
            found: 1,
        })
    );
}

/// A live count past the ceiling refuses before any entry is read: the
/// fold's tally bound is a wire law, not a convention.
#[test]
fn test_refound_map_live_exceeds_ceiling_refused() {
    // One row claiming two live entries over a ceiling of one, with both
    // entry slots backed by bytes: the refusal is the bound, not length.
    let mut frame = Vec::from(&[0x01u8][..]);
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u32.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes()); // ceiling = 1
    frame.extend_from_slice(&2u64.to_be_bytes()); // live = 2
    frame.extend_from_slice(&1u64.to_be_bytes());
    frame.extend_from_slice(&1u64.to_be_bytes());
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::LiveExceedsCeiling {
            station: 1,
            live: 2,
            ceiling: 1,
        })
    );
}

/// An old index outside the compacted domain `1..=ceiling` refuses: zero
/// is the non-dot, and an index above the ceiling belongs to the affine
/// window, whose translation is closed-form and never spelled.
#[test]
fn test_refound_map_index_out_of_range_refused() {
    let mut zeroed = fixture_map().to_bytes();
    // Station 1's second entry (old index 3, bytes 37..45) becomes 0.
    for byte in &mut zeroed[37..45] {
        *byte = 0;
    }
    assert_eq!(
        RefoundMap::from_bytes(&zeroed),
        Err(RefoundMapDecodeError::IndexOutOfRange {
            station: 1,
            index: 0,
            ceiling: 3,
        })
    );

    let mut above = fixture_map().to_bytes();
    above[44] = 0x04;
    assert_eq!(
        RefoundMap::from_bytes(&above),
        Err(RefoundMapDecodeError::IndexOutOfRange {
            station: 1,
            index: 4,
            ceiling: 3,
        })
    );
}

/// One old index named twice refuses: the compaction is a bijection, and
/// a duplicate would fold two old identities onto one new dot.
#[test]
fn test_refound_map_duplicate_index_refused() {
    let mut frame = fixture_map().to_bytes();
    // Station 1's entries become [1, 1].
    frame[44] = 0x01;
    assert_eq!(
        RefoundMap::from_bytes(&frame),
        Err(RefoundMapDecodeError::DuplicateIndex {
            station: 1,
            index: 1,
        })
    );
}

/// The budgeted door refuses declared counts past the caller's licence
/// before the corresponding work; exact budgets admit the frame.
#[test]
fn test_refound_map_budget_refusals() {
    let map = fixture_map();
    let frame = map.to_bytes();
    assert_eq!(
        RefoundMap::from_bytes_with_budget(&frame, RefoundMapDecodeBudget::new(1, 3)),
        Err(RefoundMapDecodeError::TooManyStations {
            count: 2,
            budget: 1,
        })
    );
    // The entry licence is cumulative: station 1's two entries pass a
    // budget of two, station 2's third crosses it.
    assert_eq!(
        RefoundMap::from_bytes_with_budget(&frame, RefoundMapDecodeBudget::new(2, 2)),
        Err(RefoundMapDecodeError::TooManyEntries {
            count: 3,
            budget: 2,
        })
    );
    assert_eq!(
        RefoundMap::from_bytes_with_budget(&frame, RefoundMapDecodeBudget::new(2, 3)),
        Ok(map)
    );
}