use cortex_core::{
EventId, FailingEdge, MemoryId, ProofClosureReport, ProofEdge, ProofEdgeFailure, ProofEdgeKind,
TemporalAuthorityReport,
};
use rusqlite::{params, OptionalExtension};
use serde_json::Value;
use crate::repo::{EpisodeRepo, EventRepo, MemoryRepo};
use crate::{Pool, StoreResult};
pub fn verify_memory_proof_closure(
pool: &Pool,
memory_id: &MemoryId,
) -> StoreResult<ProofClosureReport> {
let memories = MemoryRepo::new(pool);
let Some(memory) = memories.get_by_id(memory_id)? else {
return Ok(ProofClosureReport::from_edges(
Vec::new(),
vec![FailingEdge::missing(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
"memory row not found",
)],
));
};
if memory.memory_type == "operator_note" {
return Ok(ProofClosureReport::from_edges(
vec![cortex_core::ProofEdge::new(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
"operator_note",
)],
Vec::new(),
));
}
let mut verified_edges = Vec::new();
let mut failing_edges = Vec::new();
let mut source_events =
string_array_refs(&memory.source_events_json, memory_id, "source_events").unwrap_or_else(
|edge| {
failing_edges.push(edge);
Vec::new()
},
);
for episode_ref in string_array_refs(&memory.source_episodes_json, memory_id, "source_episodes")
.unwrap_or_else(|edge| {
failing_edges.push(edge);
Vec::new()
})
{
verify_episode_lineage(
pool,
memory_id,
&episode_ref,
&mut source_events,
&mut verified_edges,
&mut failing_edges,
)?;
}
if source_events.is_empty() {
failing_edges.push(FailingEdge::missing(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
"memory has no source event lineage after episode expansion",
));
}
for event_ref in source_events {
verify_event_lineage(
pool,
memory_id,
&event_ref,
&mut verified_edges,
&mut failing_edges,
)?;
}
Ok(ProofClosureReport::from_edges(
verified_edges,
failing_edges,
))
}
#[must_use]
pub fn temporal_authority_proof_report(
target_ref: impl Into<String>,
report: &TemporalAuthorityReport,
) -> ProofClosureReport {
let target_ref = target_ref.into();
if let Some(edge) = report.current_use_failing_edge(target_ref.clone()) {
return ProofClosureReport::from_edges(Vec::new(), vec![edge]);
}
ProofClosureReport::full_chain_verified(vec![ProofEdge::new(
ProofEdgeKind::AuthorityFold,
target_ref,
report.key_id.clone(),
)
.with_evidence_ref("temporal_authority")])
}
fn verify_episode_lineage(
pool: &Pool,
memory_id: &MemoryId,
episode_ref: &str,
source_events: &mut Vec<String>,
verified_edges: &mut Vec<ProofEdge>,
failing_edges: &mut Vec<FailingEdge>,
) -> StoreResult<()> {
let episodes = EpisodeRepo::new(pool);
let Ok(episode_id) = episode_ref.parse() else {
failing_edges.push(FailingEdge::broken(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
episode_ref,
ProofEdgeFailure::Mismatch,
"memory source_episodes entry is not a valid episode id",
));
return Ok(());
};
let Some(episode) = episodes.get_by_id(&episode_id)? else {
failing_edges.push(FailingEdge::missing(
ProofEdgeKind::LineageClosure,
episode_ref,
"source episode not found",
));
return Ok(());
};
verified_edges.push(
ProofEdge::new(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
episode.id.to_string(),
)
.with_evidence_ref("memories.source_episodes_json"),
);
match string_array_refs(
&episode.source_events_json,
memory_id,
"episode.source_events",
) {
Ok(events) => source_events.extend(events),
Err(edge) => failing_edges.push(edge),
}
Ok(())
}
fn verify_event_lineage(
pool: &Pool,
memory_id: &MemoryId,
event_ref: &str,
verified_edges: &mut Vec<ProofEdge>,
failing_edges: &mut Vec<FailingEdge>,
) -> StoreResult<()> {
let events = EventRepo::new(pool);
let Ok(event_id) = event_ref.parse::<EventId>() else {
failing_edges.push(FailingEdge::broken(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
event_ref,
ProofEdgeFailure::Mismatch,
"source event reference is not a valid event id",
));
return Ok(());
};
let Some(event) = events.get_by_id(&event_id)? else {
failing_edges.push(FailingEdge::missing(
ProofEdgeKind::LineageClosure,
event_ref,
"source event not found",
));
return Ok(());
};
verified_edges.push(
ProofEdge::new(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
event.id.to_string(),
)
.with_evidence_ref(event.event_hash.clone()),
);
if let Some(prev_hash) = &event.prev_event_hash {
match event_id_by_hash(pool, prev_hash)? {
Some(parent_id) => verified_edges.push(
ProofEdge::new(ProofEdgeKind::HashChain, parent_id, event.id.to_string())
.with_evidence_ref(prev_hash.clone()),
),
None => failing_edges.push(FailingEdge::missing(
ProofEdgeKind::HashChain,
event.id.to_string(),
"source event prev_event_hash has no parent event in store",
)),
}
}
Ok(())
}
fn event_id_by_hash(pool: &Pool, event_hash: &str) -> StoreResult<Option<String>> {
Ok(pool
.query_row(
"SELECT id FROM events WHERE event_hash = ?1;",
params![event_hash],
|row| row.get::<_, String>(0),
)
.optional()?)
}
fn string_array_refs(
value: &Value,
memory_id: &MemoryId,
field: &'static str,
) -> Result<Vec<String>, FailingEdge> {
let Some(items) = value.as_array() else {
return Err(FailingEdge::broken(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
field,
ProofEdgeFailure::Mismatch,
format!("{field} must be a JSON array"),
));
};
let mut refs = Vec::with_capacity(items.len());
for item in items {
let Some(text) = item.as_str() else {
return Err(FailingEdge::broken(
ProofEdgeKind::LineageClosure,
memory_id.to_string(),
field,
ProofEdgeFailure::Mismatch,
format!("{field} must contain string refs"),
));
};
refs.push(text.to_string());
}
Ok(refs)
}