deepstrike_core/memory/
curator.rs1use std::collections::HashSet;
8
9use crate::mm::memory::MemoryRecord;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConflictResolution {
14 PreferNewer,
15 PreferHigherConfidence,
16}
17
18pub fn same_scoped_key(left: &MemoryRecord, right: &MemoryRecord) -> bool {
20 left.scope == right.scope && left.kind == right.kind && left.name == right.name
21}
22
23pub fn jaccard_similarity(left: &str, right: &str) -> f64 {
25 let left: HashSet<&str> = left.split_whitespace().collect();
26 let right: HashSet<&str> = right.split_whitespace().collect();
27 let intersection = left.intersection(&right).count();
28 let union = left.union(&right).count();
29 if union == 0 {
30 0.0
31 } else {
32 intersection as f64 / union as f64
33 }
34}
35
36pub fn resolve_conflict<'a>(
38 existing: &'a MemoryRecord,
39 incoming: &'a MemoryRecord,
40 policy: ConflictResolution,
41) -> &'a MemoryRecord {
42 match policy {
43 ConflictResolution::PreferNewer => incoming,
44 ConflictResolution::PreferHigherConfidence => {
45 if incoming.confidence >= existing.confidence {
46 incoming
47 } else {
48 existing
49 }
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::mm::memory::{
58 MemoryAuthor, MemoryKind, MemoryProvenance, MemoryScope, MemoryTrustLevel,
59 };
60
61 fn record(name: &str, content: &str, confidence: f64) -> MemoryRecord {
62 MemoryRecord {
63 record_id: format!("record-{name}"),
64 scope: MemoryScope::new("tenant", "project"),
65 name: name.into(),
66 kind: MemoryKind::Project,
67 content: content.into(),
68 description: String::new(),
69 provenance: MemoryProvenance {
70 session_id: Some("session".into()),
71 author: MemoryAuthor::Extraction,
72 trust: MemoryTrustLevel::Untrusted,
73 evidence_refs: Vec::new(),
74 },
75 created_at: 1,
76 updated_at: 1,
77 last_recalled_at: None,
78 recall_count: 0,
79 confidence,
80 links: Vec::new(),
81 pinned: false,
82 ttl_days: None,
83 }
84 }
85
86 #[test]
87 fn scoped_key_precedes_content_similarity() {
88 let existing = record("compiler", "prefer cargo nextest", 0.8);
89 let same_key = record("compiler", "use cargo test", 0.7);
90 let other_key = record("editor", "prefer cargo nextest", 0.9);
91 assert!(same_scoped_key(&existing, &same_key));
92 assert!(!same_scoped_key(&existing, &other_key));
93 assert_eq!(
94 jaccard_similarity(&existing.content, &other_key.content),
95 1.0
96 );
97 }
98
99 #[test]
100 fn conflict_resolution_is_pure_and_deterministic() {
101 let old = record("compiler", "old", 0.9);
102 let new = record("compiler", "new", 0.7);
103 assert_eq!(
104 resolve_conflict(&old, &new, ConflictResolution::PreferNewer).content,
105 "new"
106 );
107 assert_eq!(
108 resolve_conflict(&old, &new, ConflictResolution::PreferHigherConfidence).content,
109 "old"
110 );
111 }
112}