use hirn_core::id::MemoryId;
use hirn_core::provenance::Provenance;
use hirn_core::record::MemoryRecord;
use hirn_core::types::{AgentId, Namespace, Origin};
use hirn_core::{HirnError, HirnResult};
use crate::causal::TraceReport;
use crate::db::HirnDB;
use crate::ql::context::ConflictGroup;
use crate::ql::results::SemanticRevisionSummary;
use crate::retrieval::recall::ResourceEvidenceSummary;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TraceResult {
pub record: MemoryRecord,
pub provenance: Provenance,
pub source_episodes: Vec<MemoryId>,
pub derived_records: Vec<MemoryId>,
pub mutation_count: usize,
pub trust_score: f32,
pub lineage_tree: String,
pub semantic_revision: Option<SemanticRevisionSummary>,
pub conflict_groups: Vec<ConflictGroup>,
pub resource_evidence: Vec<ResourceEvidenceSummary>,
}
impl From<TraceReport> for TraceResult {
fn from(report: TraceReport) -> Self {
Self {
record: report.record,
provenance: report.provenance,
source_episodes: report.source_episodes,
derived_records: report.derived_records,
mutation_count: report.mutation_count,
trust_score: report.trust_score,
lineage_tree: report.lineage_tree,
semantic_revision: None,
conflict_groups: Vec::new(),
resource_evidence: Vec::new(),
}
}
}
pub struct TraceBuilder<'a> {
db: &'a HirnDB,
id: MemoryId,
allowed_namespaces: Option<Vec<Namespace>>,
agent_id: Option<String>,
exact_conflict_target: bool,
}
impl<'a> TraceBuilder<'a> {
pub(crate) fn new(db: &'a HirnDB, id: MemoryId) -> Self {
Self {
db,
id,
allowed_namespaces: None,
agent_id: None,
exact_conflict_target: false,
}
}
#[must_use]
pub fn allowed_namespaces(mut self, allowed_namespaces: Vec<Namespace>) -> Self {
self.allowed_namespaces = Some(allowed_namespaces);
self
}
#[must_use]
pub fn agent_id(mut self, agent_id: impl Into<String>) -> Self {
self.agent_id = Some(agent_id.into());
self
}
#[must_use]
pub fn exact_conflict_target(mut self, exact_conflict_target: bool) -> Self {
self.exact_conflict_target = exact_conflict_target;
self
}
pub async fn execute(self) -> HirnResult<TraceResult> {
let record = self.db.get_memory(self.id).await?;
if let Some(allowed_namespaces) = self.allowed_namespaces.as_deref() {
let namespace = record.effective_namespace();
if !allowed_namespaces.contains(&namespace) {
return Err(HirnError::AccessDenied(format!(
"TRACE cannot access namespace '{}'",
namespace.as_str()
)));
}
}
let conflict_groups = if self.exact_conflict_target {
crate::ql::context::detect_conflicts_for_exact_record(
self.db,
&record,
self.allowed_namespaces.as_deref(),
)
.await
.groups
} else {
crate::ql::context::detect_conflicts_for_record(
self.db,
&record,
self.allowed_namespaces.as_deref(),
)
.await
.groups
};
let semantic_revision = match &record {
MemoryRecord::Semantic(record) => {
Some(crate::ql::results::load_semantic_revision_summary(self.db, record).await?)
}
_ => None,
};
let (provenance, source_episodes) = match &record {
MemoryRecord::Episodic(e) => (e.provenance.clone(), vec![]),
MemoryRecord::Semantic(s) => (s.provenance.clone(), s.source_episodes.clone()),
MemoryRecord::Working(_) => (
Provenance::with_origin(Origin::DirectObservation, AgentId::well_known("system")),
vec![],
),
MemoryRecord::Procedural(p) => (p.provenance.clone(), p.source_episodes.clone()),
};
let report = crate::causal::build_trace_report(
self.db.graph_store(),
record,
provenance,
source_episodes,
)
.await?;
let mut result = TraceResult::from(report);
result.semantic_revision = semantic_revision;
result.conflict_groups = conflict_groups;
let agent_id = self.agent_id.as_deref().unwrap_or("anonymous");
result.resource_evidence = self
.db
.resource_evidence_summaries_for_record(&result.record, agent_id)
.await?;
Ok(result)
}
}