weavatrix-memory 0.3.2

Event-sourced bitemporal evidence memory and budgeted context retrieval for coding agents
Documentation
mod common;

use common::{event, node};
use weavatrix_memory::{
    AppendReceipt, EventId, EventStore, ExpectedVersion, InMemoryStore, MemoryError, MemoryEvent,
    StreamId,
};

#[test]
fn append_is_atomic_and_optimistically_concurrent() {
    let stream = StreamId::new("task:GPRO-7301").unwrap();
    let first = event(
        "event-1",
        10,
        MemoryEvent::NodeUpserted {
            node: node("task:1", "task", "Fix shaping"),
        },
    );
    let second = event(
        "event-2",
        11,
        MemoryEvent::NodeUpserted {
            node: node("file:1", "file", "query-builder.rs"),
        },
    );
    let mut store = InMemoryStore::default();
    let committed = store
        .append(&stream, ExpectedVersion::NoStream, &[first.clone(), second])
        .unwrap();

    assert_eq!(committed[0].metadata.stream_version, 0);
    assert_eq!(committed[1].metadata.stream_version, 1);
    assert_eq!(store.stream_version(&stream), Some(1));

    let error = store
        .append(&stream, ExpectedVersion::Exact(0), &[first])
        .unwrap_err();
    assert!(matches!(error, MemoryError::VersionConflict { .. }));
    assert_eq!(store.len(), 2);
}

#[test]
fn duplicate_in_batch_does_not_partially_append() {
    let stream = StreamId::new("task:1").unwrap();
    let duplicate = event(
        "same-id",
        10,
        MemoryEvent::NodeUpserted {
            node: node("node:1", "task", "One"),
        },
    );
    let mut store = InMemoryStore::default();
    let error = store
        .append(
            &stream,
            ExpectedVersion::NoStream,
            &[duplicate.clone(), duplicate],
        )
        .unwrap_err();

    assert!(matches!(error, MemoryError::DuplicateEvent { .. }));
    assert!(store.is_empty());
}

#[test]
fn cursors_are_exclusive_and_deterministic() {
    let stream = StreamId::new("task:1").unwrap();
    let events = (0..4)
        .map(|index| {
            event(
                &format!("event-{index}"),
                index,
                MemoryEvent::NodeUpserted {
                    node: node(&format!("node:{index}"), "observation", "Observed"),
                },
            )
        })
        .collect::<Vec<_>>();
    let mut store = InMemoryStore::default();
    store
        .append(&stream, ExpectedVersion::NoStream, &events)
        .unwrap();

    assert_eq!(store.load_stream(&stream, Some(1)).len(), 2);
    assert_eq!(store.load_all(Some(1), 1)[0].metadata.global_position, 2);
}

#[test]
fn identifier_deserialization_preserves_validation() {
    let result = blazingly_json::from_str::<EventId>("\" invalid \"");
    assert!(result.is_err());
}

#[test]
fn owned_append_preserves_the_event_contract() {
    let stream = StreamId::new("task:owned").unwrap();
    let pending = event(
        "event-owned",
        10,
        MemoryEvent::NodeUpserted {
            node: node("node:owned", "task", "Owned append"),
        },
    );
    let mut store = InMemoryStore::default();

    let committed = store
        .append_owned(&stream, ExpectedVersion::NoStream, vec![pending])
        .unwrap();

    assert_eq!(committed, store.load_stream(&stream, None));
}

#[test]
fn receipt_append_moves_payloads_and_reports_exact_positions() {
    let stream = StreamId::new("task:receipt").unwrap();
    let pending = (0..3)
        .map(|index| {
            event(
                &format!("event-receipt-{index}"),
                index,
                MemoryEvent::NodeUpserted {
                    node: node(&format!("node:receipt:{index}"), "task", "Receipt append"),
                },
            )
        })
        .collect();
    let mut store = InMemoryStore::default();

    let receipt = store
        .append_owned_receipt(&stream, ExpectedVersion::NoStream, pending)
        .unwrap();

    assert_eq!(receipt.event_count, 3);
    assert_eq!(receipt.first_stream_version, Some(0));
    assert_eq!(receipt.last_stream_version, Some(2));
    assert_eq!(receipt.first_global_position, Some(0));
    assert_eq!(receipt.last_global_position, Some(2));
    assert_eq!(store.len(), 3);
}

#[test]
fn empty_receipt_append_is_observable_without_creating_a_stream() {
    let stream = StreamId::new("task:empty-receipt").unwrap();
    let mut store = InMemoryStore::<MemoryEvent>::default();

    let receipt = store
        .append_owned_receipt(&stream, ExpectedVersion::NoStream, Vec::new())
        .unwrap();

    assert_eq!(receipt, AppendReceipt::default());
    assert_eq!(store.stream_version(&stream), None);
}