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"));
}