deepstrike_core/memory/
curator.rs1use crate::mm::memory::MemoryRecord;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ConflictResolution {
12 PreferNewer,
13 PreferHigherConfidence,
14}
15
16pub fn same_scoped_key(left: &MemoryRecord, right: &MemoryRecord) -> bool {
18 left.scope == right.scope && left.kind == right.kind && left.name == right.name
19}
20
21pub fn jaccard_similarity(left: &str, right: &str) -> f64 {
27 crate::lexical::jaccard(left, right)
28}
29
30pub fn resolve_conflict<'a>(
32 existing: &'a MemoryRecord,
33 incoming: &'a MemoryRecord,
34 policy: ConflictResolution,
35) -> &'a MemoryRecord {
36 match policy {
37 ConflictResolution::PreferNewer => incoming,
38 ConflictResolution::PreferHigherConfidence => {
39 if incoming.confidence >= existing.confidence {
40 incoming
41 } else {
42 existing
43 }
44 }
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::mm::memory::{
52 MemoryAuthor, MemoryKind, MemoryProvenance, MemoryScope, MemoryTrustLevel,
53 };
54
55 fn record(name: &str, content: &str, confidence: f64) -> MemoryRecord {
56 MemoryRecord {
57 record_id: format!("record-{name}"),
58 scope: MemoryScope::new("tenant", "project"),
59 name: name.into(),
60 kind: MemoryKind::Project,
61 content: content.into(),
62 description: String::new(),
63 provenance: MemoryProvenance {
64 session_id: Some("session".into()),
65 author: MemoryAuthor::Extraction,
66 trust: MemoryTrustLevel::Untrusted,
67 evidence_refs: Vec::new(),
68 },
69 created_at: 1,
70 updated_at: 1,
71 last_recalled_at: None,
72 recall_count: 0,
73 confidence,
74 links: Vec::new(),
75 pinned: false,
76 ttl_days: None,
77 }
78 }
79
80 #[test]
81 fn scoped_key_precedes_content_similarity() {
82 let existing = record("compiler", "prefer cargo nextest", 0.8);
83 let same_key = record("compiler", "use cargo test", 0.7);
84 let other_key = record("editor", "prefer cargo nextest", 0.9);
85 assert!(same_scoped_key(&existing, &same_key));
86 assert!(!same_scoped_key(&existing, &other_key));
87 assert_eq!(
88 jaccard_similarity(&existing.content, &other_key.content),
89 1.0
90 );
91 }
92
93 #[test]
94 fn cjk_near_duplicates_are_detectable_after_key_miss() {
95 let near = jaccard_similarity("用户偏好深色模式界面", "用户偏好浅色模式界面");
98 let unrelated = jaccard_similarity("用户偏好深色模式界面", "周五之前完成部署上线");
99 assert!(near > 0.5, "near-duplicate CJK must be detectable: {near}");
100 assert!(unrelated < 0.2, "unrelated CJK must stay low: {unrelated}");
101 }
102
103 #[test]
104 fn conflict_resolution_is_pure_and_deterministic() {
105 let old = record("compiler", "old", 0.9);
106 let new = record("compiler", "new", 0.7);
107 assert_eq!(
108 resolve_conflict(&old, &new, ConflictResolution::PreferNewer).content,
109 "new"
110 );
111 assert_eq!(
112 resolve_conflict(&old, &new, ConflictResolution::PreferHigherConfidence).content,
113 "old"
114 );
115 }
116}