weavatrix-memory 0.2.0

Event-sourced, bitemporal context compiler for coding agents
Documentation
mod common;

use common::{event, fact, node, ts};
use weavatrix_memory::{
    EventStore, Evidence, ExpectedVersion, FactId, InMemoryStore, MemoryEvent, MemoryProjection,
    ProjectionClock, StreamId, replay,
};

#[test]
fn late_supersession_changes_known_future_not_known_past() {
    let stream = StreamId::new("task:temporal").unwrap();
    let old = fact("fact:old", "decision:1", "affects", "file:1", 10, 20);
    let replacement = fact("fact:new", "decision:1", "affects", "file:1", 30, 50)
        .supersedes(old.id.clone())
        .unwrap();
    let events = vec![
        event(
            "event:node:decision",
            5,
            MemoryEvent::NodeUpserted {
                node: node("decision:1", "decision", "Use DateHour"),
            },
        ),
        event(
            "event:node:file",
            5,
            MemoryEvent::NodeUpserted {
                node: node("file:1", "file", "query-builder.rs"),
            },
        ),
        event(
            "event:fact:old",
            20,
            MemoryEvent::FactRecorded { fact: old },
        ),
        event(
            "event:fact:new",
            50,
            MemoryEvent::FactRecorded { fact: replacement },
        ),
    ];
    let mut store = InMemoryStore::default();
    store
        .append(&stream, ExpectedVersion::NoStream, &events)
        .unwrap();
    let projection = replay::<_, MemoryProjection>(&store.load_all(None, usize::MAX)).unwrap();

    let before_learning = projection.view(ProjectionClock::new(ts(40), ts(40)));
    assert_eq!(before_learning.facts[0].id.as_str(), "fact:old");

    let after_learning = projection.view(ProjectionClock::new(ts(40), ts(60)));
    assert_eq!(after_learning.facts[0].id.as_str(), "fact:new");
}

#[test]
fn replay_rejects_tampered_global_position() {
    let stream = StreamId::new("task:tamper").unwrap();
    let mut store = InMemoryStore::default();
    store
        .append(
            &stream,
            ExpectedVersion::NoStream,
            &[event(
                "event:1",
                1,
                MemoryEvent::NodeUpserted {
                    node: node("task:1", "task", "Task"),
                },
            )],
        )
        .unwrap();
    let mut events = store.load_all(None, usize::MAX);
    events[0].metadata.global_position = 9;

    assert!(replay::<_, MemoryProjection>(&events).is_err());
}

#[test]
fn retraction_is_visible_only_after_it_is_recorded() {
    let original = fact("fact:old", "task:1", "affects", "file:1", 10, 20);
    let events = vec![
        event(
            "event:task",
            1,
            MemoryEvent::NodeUpserted {
                node: node("task:1", "task", "Task"),
            },
        ),
        event(
            "event:file",
            1,
            MemoryEvent::NodeUpserted {
                node: node("file:1", "file", "File"),
            },
        ),
        event(
            "event:fact",
            20,
            MemoryEvent::FactRecorded { fact: original },
        ),
        event(
            "event:retract",
            50,
            MemoryEvent::FactRetracted {
                fact_id: FactId::new("fact:old").unwrap(),
                valid_until: ts(30),
                evidence: vec![Evidence::new("test", "regression-suite").unwrap()],
            },
        ),
    ];
    let stream = StreamId::new("task:retract").unwrap();
    let mut store = InMemoryStore::default();
    store
        .append(&stream, ExpectedVersion::NoStream, &events)
        .unwrap();
    let projection: MemoryProjection = replay(&store.load_all(None, usize::MAX)).unwrap();

    assert_eq!(
        projection
            .view(ProjectionClock::new(ts(40), ts(40)))
            .facts
            .len(),
        1
    );
    assert!(
        projection
            .view(ProjectionClock::new(ts(40), ts(60)))
            .facts
            .is_empty()
    );
}