relay-knowledge 1.1.14

Graph-database-based knowledge graph project.
Documentation
use super::*;
use crate::{
    domain::{
        ClaimRecord, EventRecord, EvidenceRecord, FactStatus, GraphMutationBatch,
        GraphRelationRecord, GraphVersionRange, SourceScope,
    },
    storage::{GraphCanvasSelection, GraphCanvasStorageRequest, GraphStore},
};

#[test]
fn fact_evidence_ids_preserve_order_and_reject_invalid_json() {
    assert_eq!(
        evidence_ids(r#"["ev-2","ev-1"]"#).expect("evidence JSON should decode"),
        ["ev-2", "ev-1"]
    );
    assert!(matches!(
        evidence_ids("not-json"),
        Err(StorageError::InvalidInput(_))
    ));
}

#[tokio::test]
async fn canvas_projects_structured_fact_nodes_and_edges() {
    let store = crate::storage::SqliteGraphStore::open_in_memory().expect("store should open");
    let scope = SourceScope::parse("docs").expect("scope should parse");
    let evidence = EvidenceRecord::new(
        "ev-structured",
        scope.clone(),
        "Relay Knowledge documents relation, claim, and event canvas rendering",
        vec!["Relay Knowledge".to_owned()],
    )
    .expect("evidence should validate");
    let relation = GraphRelationRecord::new(
        "rel-structured",
        scope.clone(),
        "Relay Knowledge",
        "renders",
        "graph canvas",
        vec!["ev-structured".to_owned()],
    )
    .expect("relation should validate");
    let claim = ClaimRecord::new(
        "claim-structured",
        scope.clone(),
        "Relay Knowledge",
        "canvas_mode",
        "keeps structured facts selectable",
        vec!["ev-structured".to_owned()],
    )
    .expect("claim should validate")
    .with_metadata(
        crate::domain::ConfidenceScore {
            basis_points: 8_750,
        },
        FactStatus::Proposed,
        crate::domain::GraphVersionRange::open_from(GraphVersion::ZERO),
    )
    .expect("claim metadata should validate");
    let event = EventRecord::new(
        "event-structured",
        scope,
        "canvas_refreshed",
        vec!["Relay Knowledge".to_owned()],
        Some("2026-05-15T10:00:00Z".to_owned()),
        vec!["ev-structured".to_owned()],
    )
    .expect("event should validate");
    store
        .commit_mutation_batch(
            GraphMutationBatch::with_facts(
                vec![evidence],
                vec![relation],
                vec![claim],
                vec![event],
            )
            .expect("batch should validate"),
        )
        .await
        .expect("commit should succeed");

    let snapshot = store
        .graph_canvas(GraphCanvasStorageRequest {
            selection: GraphCanvasSelection::Knowledge,
            source_scope: Some("docs".to_owned()),
            query: None,
            graph_version: GraphVersion::new(1),
            limit: 50,
        })
        .await
        .expect("canvas should load");

    let relation = snapshot
        .edges
        .iter()
        .find(|edge| edge.id == "relation:rel-structured")
        .expect("relation edge should be projected");
    assert_eq!(relation.kind, "relation");
    assert_eq!(relation.confidence_basis_points, Some(10_000));
    assert_eq!(relation.evidence_count, Some(1));
    assert_eq!(
        relation.details.get("relation_type").map(String::as_str),
        Some("renders")
    );
    let claim = snapshot
        .nodes
        .iter()
        .find(|node| node.id == "claim:claim-structured")
        .expect("claim node should be projected");
    assert_eq!(claim.status.as_deref(), Some("proposed"));
    assert_eq!(
        claim.details.get("confidence").map(String::as_str),
        Some("8750")
    );
    let event = snapshot
        .nodes
        .iter()
        .find(|node| node.id == "event:event-structured")
        .expect("event node should be projected");
    assert_eq!(event.kind, "event");
    assert!(
        event
            .label
            .contains("canvas_refreshed @ 2026-05-15T10:00:00Z")
    );
    assert!(
        snapshot
            .edges
            .iter()
            .any(|edge| edge.kind == "claim_subject" && edge.target == "claim:claim-structured")
    );
    assert!(
        snapshot
            .edges
            .iter()
            .any(|edge| edge.kind == "event_entity" && edge.source == "event:event-structured")
    );
    assert!(
        snapshot
            .available_kinds
            .iter()
            .any(|kind| kind == "evidence_link")
    );
}

#[tokio::test]
async fn canvas_filters_structured_facts_by_validity_window() {
    let store = crate::storage::SqliteGraphStore::open_in_memory().expect("store should open");
    let scope = SourceScope::parse("docs").expect("scope should parse");
    let evidence = EvidenceRecord::new(
        "ev-window",
        scope.clone(),
        "Canvas validity windows should match retrieval visibility",
        vec!["Windowed Graph".to_owned()],
    )
    .expect("evidence should validate");
    let future_relation = GraphRelationRecord::new(
        "rel-future",
        scope.clone(),
        "Windowed Graph",
        "appears_at",
        "future snapshot",
        vec!["ev-window".to_owned()],
    )
    .expect("relation should validate")
    .with_metadata(
        crate::domain::ConfidenceScore::CERTAIN,
        FactStatus::Accepted,
        GraphVersionRange::open_from(GraphVersion::new(3)),
    )
    .expect("relation metadata should validate");
    let expired_claim = ClaimRecord::new(
        "claim-expired",
        scope.clone(),
        "Windowed Graph",
        "visibility",
        "expired before snapshot",
        vec!["ev-window".to_owned()],
    )
    .expect("claim should validate")
    .with_metadata(
        crate::domain::ConfidenceScore::CERTAIN,
        FactStatus::Accepted,
        GraphVersionRange::new(GraphVersion::new(1), Some(GraphVersion::new(1)))
            .expect("range should validate"),
    )
    .expect("claim metadata should validate");
    let expired_event = EventRecord::new(
        "event-expired",
        scope,
        "window_closed",
        vec!["Windowed Graph".to_owned()],
        None,
        vec!["ev-window".to_owned()],
    )
    .expect("event should validate")
    .with_metadata(
        crate::domain::ConfidenceScore::CERTAIN,
        FactStatus::Accepted,
        GraphVersionRange::new(GraphVersion::new(1), Some(GraphVersion::new(1)))
            .expect("range should validate"),
    )
    .expect("event metadata should validate");
    store
        .commit_mutation_batch(
            GraphMutationBatch::with_facts(
                vec![evidence],
                vec![future_relation],
                vec![expired_claim],
                vec![expired_event],
            )
            .expect("batch should validate"),
        )
        .await
        .expect("commit should succeed");

    let before_future = store
        .graph_canvas(GraphCanvasStorageRequest {
            selection: GraphCanvasSelection::Knowledge,
            source_scope: Some("docs".to_owned()),
            query: None,
            graph_version: GraphVersion::new(2),
            limit: 50,
        })
        .await
        .expect("canvas should load");
    assert!(
        before_future
            .edges
            .iter()
            .all(|edge| edge.id != "relation:rel-future")
    );
    assert!(
        before_future
            .nodes
            .iter()
            .all(|node| node.id != "claim:claim-expired")
    );
    assert!(
        before_future
            .nodes
            .iter()
            .all(|node| node.id != "event:event-expired")
    );

    let at_future = store
        .graph_canvas(GraphCanvasStorageRequest {
            selection: GraphCanvasSelection::Knowledge,
            source_scope: Some("docs".to_owned()),
            query: None,
            graph_version: GraphVersion::new(3),
            limit: 50,
        })
        .await
        .expect("canvas should load");
    assert!(
        at_future
            .edges
            .iter()
            .any(|edge| edge.id == "relation:rel-future")
    );
    assert!(
        at_future
            .nodes
            .iter()
            .all(|node| node.id != "claim:claim-expired" && node.id != "event:event-expired")
    );
}

#[tokio::test]
async fn canvas_fact_scope_filters_ignore_future_evidence_scope_changes() {
    let store = crate::storage::SqliteGraphStore::open_in_memory().expect("store should open");
    let docs_scope = SourceScope::parse("docs").expect("scope should parse");
    let repo_scope = SourceScope::parse("repo").expect("scope should parse");
    let evidence = EvidenceRecord::new(
        "ev-scope-drift",
        docs_scope.clone(),
        "Scope Drift belongs to docs at the first snapshot",
        vec!["Scope Drift".to_owned(), "Relay".to_owned()],
    )
    .expect("evidence should validate");
    let relation = GraphRelationRecord::new(
        "rel-scope-drift",
        docs_scope.clone(),
        "Scope Drift",
        "documents",
        "Relay",
        vec!["ev-scope-drift".to_owned()],
    )
    .expect("relation should validate");
    let claim = ClaimRecord::new(
        "claim-scope-drift",
        docs_scope.clone(),
        "Scope Drift",
        "scope",
        "docs",
        vec!["ev-scope-drift".to_owned()],
    )
    .expect("claim should validate");
    let event = EventRecord::new(
        "event-scope-drift",
        docs_scope,
        "scope_recorded",
        vec!["Scope Drift".to_owned()],
        None,
        vec!["ev-scope-drift".to_owned()],
    )
    .expect("event should validate");
    store
        .commit_mutation_batch(
            GraphMutationBatch::with_facts(
                vec![evidence],
                vec![relation],
                vec![claim],
                vec![event],
            )
            .expect("batch should validate"),
        )
        .await
        .expect("initial facts should commit");

    let moved_evidence = EvidenceRecord::new(
        "ev-scope-drift",
        repo_scope,
        "Scope Drift is reingested under repo later",
        vec!["Scope Drift".to_owned(), "Relay".to_owned()],
    )
    .expect("evidence should validate");
    store
        .commit_mutation_batch(GraphMutationBatch::new(vec![moved_evidence]).expect("batch"))
        .await
        .expect("moved evidence should commit");

    let old_repo_scope = store
        .graph_canvas(GraphCanvasStorageRequest {
            selection: GraphCanvasSelection::Knowledge,
            source_scope: Some("repo".to_owned()),
            query: None,
            graph_version: GraphVersion::new(1),
            limit: 80,
        })
        .await
        .expect("canvas should load");

    assert!(
        old_repo_scope
            .edges
            .iter()
            .all(|edge| edge.id != "relation:rel-scope-drift")
    );
    assert!(
        old_repo_scope
            .nodes
            .iter()
            .all(|node| node.id != "claim:claim-scope-drift")
    );
    assert!(
        old_repo_scope
            .nodes
            .iter()
            .all(|node| node.id != "event:event-scope-drift")
    );
}