1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11
12use super::SpaceId;
13use crate::audit_trail::AuditTrail;
14use crate::memory::{MemoryEntry, MemoryManager};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CrossRefEntry {
19 pub from: SpaceId,
21 pub to: SpaceId,
23 pub entry_ids: Vec<String>,
25 pub flow: KnowledgeFlow,
27 pub timestamp: DateTime<Utc>,
29}
30
31impl CrossRefEntry {
32 pub fn new(from: SpaceId, to: SpaceId, entry_ids: Vec<String>, flow: KnowledgeFlow) -> Self {
34 Self {
35 from,
36 to,
37 entry_ids,
38 flow,
39 timestamp: Utc::now(),
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum KnowledgeFlow {
48 Reference,
50 Transfer,
52 Synthesis,
54}
55
56impl std::fmt::Display for KnowledgeFlow {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 KnowledgeFlow::Reference => write!(f, "reference"),
60 KnowledgeFlow::Transfer => write!(f, "transfer"),
61 KnowledgeFlow::Synthesis => write!(f, "synthesis"),
62 }
63 }
64}
65
66pub struct KnowledgeBridge {
73 space_manager: Arc<super::manager::SpaceManager>,
75 audit_trail: Option<Arc<AuditTrail>>,
77 recent_refs: parking_lot::RwLock<Vec<CrossRefEntry>>,
79}
80
81impl KnowledgeBridge {
82 pub fn new(
84 space_manager: Arc<super::manager::SpaceManager>,
85 audit_trail: Option<Arc<AuditTrail>>,
86 ) -> Self {
87 Self {
88 space_manager,
89 audit_trail,
90 recent_refs: parking_lot::RwLock::new(Vec::new()),
91 }
92 }
93
94 #[allow(unused_variables)]
104 pub async fn reference(
105 &self,
106 from_space_id: SpaceId,
107 to_space_id: SpaceId,
108 _memory_manager: &MemoryManager,
109 _query: &str,
110 ) -> anyhow::Result<Vec<MemoryEntry>> {
111 let from_space = self.space_manager.get_space(&from_space_id).await?;
113 if let Some(space) = from_space {
114 if !space.knowledge_visible {
115 anyhow::bail!("Space {} is private and cannot be accessed", from_space_id);
116 }
117 }
118
119 let entries = Vec::new();
127
128 let entry = CrossRefEntry::new(
130 from_space_id,
131 to_space_id,
132 entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
133 KnowledgeFlow::Reference,
134 );
135 self.record_entry(entry);
136
137 Ok(entries)
138 }
139
140 #[allow(unused_variables)]
151 pub async fn transfer(
152 &self,
153 from_space_id: SpaceId,
154 to_space_id: SpaceId,
155 _memory_manager: &MemoryManager,
156 entries: Vec<MemoryEntry>,
157 ) -> anyhow::Result<usize> {
158 let from_space = self.space_manager.get_space(&from_space_id).await?;
160 if let Some(space) = from_space {
161 if !space.knowledge_visible {
162 tracing::warn!(
163 from = %from_space_id,
164 to = %to_space_id,
165 "Skipping transfer: source Space is private"
166 );
167 return Ok(0);
168 }
169 }
170
171 let count = entries.len();
175
176 let entry = CrossRefEntry::new(
178 from_space_id,
179 to_space_id,
180 entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
181 KnowledgeFlow::Transfer,
182 );
183 self.record_entry(entry);
184
185 tracing::info!(
186 from = %from_space_id,
187 to = %to_space_id,
188 count,
189 "Knowledge transfer recorded"
190 );
191
192 Ok(count)
193 }
194
195 fn record_entry(&self, entry: CrossRefEntry) {
197 let mut refs = self.recent_refs.write();
199 refs.push(entry.clone());
200
201 while refs.len() > 100 {
203 refs.remove(0);
204 }
205
206 if let Some(ref audit) = self.audit_trail {
208 audit.append(
209 format!("space:{}", entry.to),
210 crate::audit_trail::AuditAction::Other {
211 detail: format!(
212 "knowledge_{}: {}->{} ({} entries)",
213 entry.flow,
214 entry.from,
215 entry.to,
216 entry.entry_ids.len()
217 ),
218 },
219 format!("{:?}", entry),
220 );
221 }
222 }
223
224 pub fn recent_references(&self) -> Vec<CrossRefEntry> {
226 self.recent_refs.read().clone()
227 }
228
229 pub fn references_for(&self, space_id: SpaceId) -> Vec<CrossRefEntry> {
231 self.recent_refs
232 .read()
233 .iter()
234 .filter(|e| e.from == space_id || e.to == space_id)
235 .cloned()
236 .collect()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_knowledge_flow_display() {
246 assert_eq!(KnowledgeFlow::Reference.to_string(), "reference");
247 assert_eq!(KnowledgeFlow::Transfer.to_string(), "transfer");
248 assert_eq!(KnowledgeFlow::Synthesis.to_string(), "synthesis");
249 }
250
251 #[test]
252 fn test_cross_ref_entry() {
253 let entry = CrossRefEntry::new(
254 SpaceId::new_v4(),
255 SpaceId::new_v4(),
256 vec!["mem1".to_string(), "mem2".to_string()],
257 KnowledgeFlow::Transfer,
258 );
259 assert_eq!(entry.entry_ids.len(), 2);
260 assert!(!entry.timestamp.format("%Y").to_string().is_empty());
261 }
262}