crtx-store 0.1.1

SQLite persistence: migrations, repositories, transactions.
Documentation
use chrono::{TimeZone, Utc};
use cortex_core::{Event, EventSource, EventType, MemoryId, ProofEdgeFailure, ProofState};
use cortex_store::migrate::apply_pending;
use cortex_store::repo::{EventRepo, MemoryCandidate, MemoryRepo};
use cortex_store::{verify_memory_proof_closure, Pool};
use rusqlite::Connection;
use serde_json::json;

fn test_pool() -> Pool {
    let pool = Connection::open_in_memory().expect("open in-memory sqlite");
    apply_pending(&pool).expect("apply migrations");
    pool
}

fn event(id: &str, event_hash: &str, prev_event_hash: Option<&str>, second: u32) -> Event {
    Event {
        id: id.parse().unwrap(),
        schema_version: cortex_core::SCHEMA_VERSION,
        observed_at: Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, second).unwrap(),
        recorded_at: Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, second).unwrap(),
        source: EventSource::Runtime,
        event_type: EventType::SystemNote,
        trace_id: None,
        session_id: Some("proof-closure-test".into()),
        domain_tags: vec!["proof".into()],
        payload: json!({"id": id}),
        payload_hash: format!("payload-{event_hash}"),
        prev_event_hash: prev_event_hash.map(str::to_string),
        event_hash: event_hash.into(),
    }
}

fn memory(source_events_json: serde_json::Value) -> MemoryCandidate {
    MemoryCandidate {
        id: "mem_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap(),
        memory_type: "semantic".into(),
        claim: "Proof closure verifies source event lineage.".into(),
        source_episodes_json: json!([]),
        source_events_json,
        domains_json: json!(["proof"]),
        salience_json: json!({"score": 0.7}),
        confidence: 0.8,
        authority: "candidate".into(),
        applies_when_json: json!(["verifying memory lineage"]),
        does_not_apply_when_json: json!([]),
        created_at: Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, 3).unwrap(),
        updated_at: Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, 3).unwrap(),
    }
}

#[test]
fn proof_closure_full_chain_verified_for_fixture() {
    let pool = test_pool();
    let events = EventRepo::new(&pool);
    let first = event("evt_01ARZ3NDEKTSV4RRFFQ69G5FAV", "hash-1", None, 1);
    let second = event(
        "evt_01ARZ3NDEKTSV4RRFFQ69G5FAW",
        "hash-2",
        Some("hash-1"),
        2,
    );
    events.append(&first).expect("append first event");
    events.append(&second).expect("append second event");

    let memories = MemoryRepo::new(&pool);
    let memory = memory(json!([second.id.to_string()]));
    memories
        .insert_candidate(&memory)
        .expect("insert memory candidate");

    let report = verify_memory_proof_closure(&pool, &memory.id).expect("verify proof closure");

    assert_eq!(report.state(), ProofState::FullChainVerified);
    assert!(report.is_full_chain_verified());
    assert!(report.failing_edges().is_empty());
    assert_eq!(report.verified_edges().len(), 2);
}

#[test]
fn proof_closure_names_missing_source_event_edge() {
    let pool = test_pool();
    let memories = MemoryRepo::new(&pool);
    let memory = memory(json!(["evt_01ARZ3NDEKTSV4RRFFQ69G5FAW"]));
    memories
        .insert_candidate(&memory)
        .expect("insert memory candidate");

    let report = verify_memory_proof_closure(&pool, &memory.id).expect("verify proof closure");

    assert_eq!(report.state(), ProofState::Partial);
    assert_eq!(report.failing_edges().len(), 1);
    assert_eq!(report.failing_edges()[0].failure, ProofEdgeFailure::Missing);
    assert!(report.failing_edges()[0]
        .reason
        .contains("source event not found"));
}

#[test]
fn proof_closure_invalid_source_ref_is_broken() {
    let pool = test_pool();
    let memories = MemoryRepo::new(&pool);
    let memory = memory(json!(["not-an-event-id"]));
    memories
        .insert_candidate(&memory)
        .expect("insert memory candidate");

    let report = verify_memory_proof_closure(&pool, &memory.id).expect("verify proof closure");

    assert_eq!(report.state(), ProofState::Broken);
    assert!(report.is_broken());
    assert_eq!(
        report.failing_edges()[0].failure,
        ProofEdgeFailure::Mismatch
    );
}

#[test]
fn proof_closure_missing_memory_is_partial_with_named_edge() {
    let pool = test_pool();
    let missing: MemoryId = "mem_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap();

    let report = verify_memory_proof_closure(&pool, &missing).expect("verify proof closure");

    assert_eq!(report.state(), ProofState::Partial);
    assert_eq!(report.failing_edges().len(), 1);
    assert!(report.failing_edges()[0].reason.contains("memory row"));
}