weavatrix-graph 0.6.1

Deterministic, evidence-carrying graph primitives for repository intelligence
Documentation
use weavatrix_graph::{
    AcyclicPayloadGraph, GraphError, GraphView, StablePayloadGraph, bfs, topological_sort,
};

#[test]
fn stable_payload_keys_survive_unrelated_mutations_and_detect_reuse() {
    let mut graph = StablePayloadGraph::new();
    let alpha = graph.add_node("alpha").unwrap();
    let beta = graph.add_node("beta").unwrap();
    let edge = graph.add_edge(alpha, beta, 7_u64).unwrap();

    assert_eq!(graph.remove_node(alpha), Some("alpha"));
    assert!(graph.edge(edge).is_none());
    let replacement = graph.add_node("replacement").unwrap();
    assert_eq!(replacement.slot(), alpha.slot());
    assert_ne!(replacement.generation(), alpha.generation());
    assert!(graph.node(alpha).is_none());
    assert_eq!(graph.node(beta), Some(&"beta"));
}

#[test]
fn stable_payload_graph_supports_algorithms_retargeting_and_compaction() {
    let mut graph = StablePayloadGraph::new();
    let alpha = graph.add_node("alpha").unwrap();
    let beta = graph.add_node("beta").unwrap();
    let gamma = graph.add_node("gamma").unwrap();
    let edge = graph.add_edge(alpha, beta, "calls").unwrap();
    assert_eq!(bfs(&graph, alpha), [alpha, beta]);

    graph.set_edge_endpoints(edge, alpha, gamma).unwrap();
    assert_eq!(bfs(&graph, alpha), [alpha, gamma]);
    let frozen = graph.freeze().unwrap();
    assert_eq!(frozen.graph().node_count(), 3);
    assert_eq!(frozen.graph().edge_count(), 1);
    assert_eq!(frozen.indices().node(alpha).unwrap().index(), 0);
    assert_eq!(frozen.indices().edge(edge).unwrap().index(), 0);
}

#[test]
fn payload_mapping_preserves_keys_and_holes() {
    let mut graph = StablePayloadGraph::new();
    let removed = graph.add_node(1_u32).unwrap();
    let live = graph.add_node(2).unwrap();
    graph.remove_node(removed);
    let mapped = graph.map_payloads(|node| node.to_string(), |edge: u64| edge + 1);
    assert_eq!(mapped.node(live).map(String::as_str), Some("2"));
    assert!(mapped.node(removed).is_none());
}

#[test]
fn acyclic_graph_rejects_cycles_and_ignores_the_retargeted_edge() {
    let mut graph = AcyclicPayloadGraph::new();
    let alpha = graph.add_node("alpha").unwrap();
    let beta = graph.add_node("beta").unwrap();
    let gamma = graph.add_node("gamma").unwrap();
    let edge = graph.add_edge(alpha, beta, ()).unwrap();
    graph.add_edge(beta, gamma, ()).unwrap();

    assert_eq!(
        graph.add_edge(gamma, alpha, ()),
        Err(GraphError::CycleWouldBeCreated)
    );
    assert!(graph.set_edge_endpoints(edge, alpha, gamma).unwrap());
    assert_eq!(topological_sort(&graph).unwrap().len(), 3);
}

#[test]
fn stale_endpoints_are_rejected_without_mutation() {
    let mut graph = StablePayloadGraph::<_, ()>::new();
    let removed = graph.add_node("removed").unwrap();
    let live = graph.add_node("live").unwrap();
    graph.remove_node(removed);
    assert_eq!(
        graph.add_edge(removed, live, ()),
        Err(GraphError::InvalidStableKey {
            category: "node",
            slot: removed.slot(),
            generation: removed.generation(),
        })
    );
    assert_eq!(graph.edge_count(), 0);
}