weavatrix-memory 0.3.1

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

use common::{event, fact, node, ts};
use weavatrix_memory::{
    ContextCompiler, ContextRequest, EntityId, EventStore, ExpectedVersion, InMemoryStore,
    MemoryError, MemoryEvent, MemoryProjection, StreamId, replay,
};

fn projection() -> MemoryProjection {
    let main_task = node("task:1", "task", "Fix shaping")
        .in_repository("analytics")
        .on_branch("main");
    let main_file = node("file:main", "file", "query-builder.rs")
        .in_repository("analytics")
        .on_branch("main");
    let other_file = node("file:other", "file", "legacy.rs")
        .in_repository("analytics")
        .on_branch("release");
    let events = vec![
        event(
            "node:task",
            1,
            MemoryEvent::NodeUpserted { node: main_task },
        ),
        event(
            "node:main",
            1,
            MemoryEvent::NodeUpserted { node: main_file },
        ),
        event(
            "node:other",
            1,
            MemoryEvent::NodeUpserted { node: other_file },
        ),
        event(
            "fact:main:event",
            2,
            MemoryEvent::FactRecorded {
                fact: fact("fact:main", "task:1", "affects", "file:main", 2, 2),
            },
        ),
        event(
            "fact:other:event",
            3,
            MemoryEvent::FactRecorded {
                fact: fact("fact:other", "task:1", "affects", "file:other", 3, 3),
            },
        ),
    ];
    let stream = StreamId::new("task:1").unwrap();
    let mut store = InMemoryStore::default();
    store
        .append(&stream, ExpectedVersion::NoStream, &events)
        .unwrap();
    replay(&store.load_all(None, usize::MAX)).unwrap()
}

#[test]
fn compiler_honors_scope_budget_and_produces_graph() {
    let projection = projection();
    let request = ContextRequest::new(vec![EntityId::new("task:1").unwrap()], ts(10), ts(10), 200)
        .unwrap()
        .in_repository("analytics")
        .on_branch("main")
        .include_relation("affects");
    let compiler = ContextCompiler::default();

    let first = compiler.compile(&projection, &request).unwrap();
    let second = compiler.compile(&projection, &request).unwrap();

    assert_eq!(first.view.nodes.len(), 2);
    assert_eq!(first.view.facts.len(), 1);
    assert_eq!(first.graph.edge_count(), 1);
    assert_eq!(first.receipt.excluded_by_scope, 1);
    assert_eq!(first.receipt, second.receipt);
    assert!(first.receipt.estimated_tokens <= request.token_budget);
}

#[test]
fn compiler_never_silently_exceeds_seed_budget() {
    let projection = projection();
    let request =
        ContextRequest::new(vec![EntityId::new("task:1").unwrap()], ts(10), ts(10), 1).unwrap();

    let error = ContextCompiler::default()
        .compile(&projection, &request)
        .unwrap_err();
    assert!(matches!(error, MemoryError::BudgetTooSmall { .. }));
}

#[test]
fn indexed_compiler_keeps_edges_between_depth_boundary_nodes() {
    let nodes = ["node:a", "node:b", "node:c"].map(|id| node(id, "observation", id));
    let events = vec![
        event(
            "event:a",
            1,
            MemoryEvent::NodeUpserted {
                node: nodes[0].clone(),
            },
        ),
        event(
            "event:b",
            1,
            MemoryEvent::NodeUpserted {
                node: nodes[1].clone(),
            },
        ),
        event(
            "event:c",
            1,
            MemoryEvent::NodeUpserted {
                node: nodes[2].clone(),
            },
        ),
        event(
            "event:ab",
            2,
            MemoryEvent::FactRecorded {
                fact: fact("fact:ab", "node:a", "supports", "node:b", 2, 2),
            },
        ),
        event(
            "event:ac",
            2,
            MemoryEvent::FactRecorded {
                fact: fact("fact:ac", "node:a", "supports", "node:c", 2, 2),
            },
        ),
        event(
            "event:bc",
            2,
            MemoryEvent::FactRecorded {
                fact: fact("fact:bc", "node:b", "supports", "node:c", 2, 2),
            },
        ),
    ];
    let mut store = InMemoryStore::default();
    store
        .append(
            &StreamId::new("boundary").unwrap(),
            ExpectedVersion::NoStream,
            &events,
        )
        .unwrap();
    let projection = replay(&store.load_all(None, usize::MAX)).unwrap();
    let request = ContextRequest::new(
        vec![EntityId::new("node:a").unwrap()],
        ts(10),
        ts(10),
        10_000,
    )
    .unwrap()
    .with_max_depth(1);

    let bundle = ContextCompiler::default()
        .compile(&projection, &request)
        .unwrap();

    assert_eq!(bundle.graph.node_count(), 3);
    assert_eq!(bundle.graph.edge_count(), 3);
}