Skip to main content

oxios_kernel/space/
space_bridge.rs

1//! SpaceBridge: manages memory flow between Spaces.
2//!
3//! Cross-Space memory flow is managed through three types:
4//! - Reference: read-only access to another Space's memory
5//! - Transfer: copy memory entries from one Space to another
6//! - Synthesis: combine knowledge from multiple Spaces (Phase 6)
7
8use 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/// Cross-reference log entry.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CrossRefEntry {
19    /// Source Space ID.
20    pub from: SpaceId,
21    /// Target Space ID.
22    pub to: SpaceId,
23    /// Memory entry IDs that were accessed.
24    pub entry_ids: Vec<String>,
25    /// Type of memory flow.
26    pub flow: MemoryFlow,
27    /// When this happened.
28    pub timestamp: DateTime<Utc>,
29}
30
31impl CrossRefEntry {
32    /// Create a new cross-reference entry.
33    pub fn new(from: SpaceId, to: SpaceId, entry_ids: Vec<String>, flow: MemoryFlow) -> Self {
34        Self {
35            from,
36            to,
37            entry_ids,
38            flow,
39            timestamp: Utc::now(),
40        }
41    }
42}
43
44/// Type of memory flow between Spaces.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum MemoryFlow {
48    /// Read-only access to another Space's memory.
49    Reference,
50    /// Copy entries from one Space to another.
51    Transfer,
52    /// Synthesize insights from multiple Spaces. (Phase 6)
53    Synthesis,
54}
55
56impl std::fmt::Display for MemoryFlow {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            MemoryFlow::Reference => write!(f, "reference"),
60            MemoryFlow::Transfer => write!(f, "transfer"),
61            MemoryFlow::Synthesis => write!(f, "synthesis"),
62        }
63    }
64}
65
66/// Manages memory flow between Spaces.
67///
68/// Provides controlled access to cross-Space memory:
69/// - Checks `memory_visible` flags
70/// - Records all access in audit trail
71/// - Respects privacy settings
72pub struct SpaceBridge {
73    /// Reference to SpaceManager for space lookups.
74    space_manager: Arc<super::manager::SpaceManager>,
75    /// Audit trail for memory flow logging.
76    audit_trail: Option<Arc<AuditTrail>>,
77    /// In-memory log of recent cross-references.
78    recent_refs: parking_lot::RwLock<Vec<CrossRefEntry>>,
79}
80
81impl SpaceBridge {
82    /// Create a new SpaceBridge.
83    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    /// Reference: read memory from another Space.
95    ///
96    /// Returns memory entries from `from_space` that match the query.
97    /// Records the access in audit trail.
98    ///
99    /// # Panics
100    ///
101    /// Panics if `memory_manager` is not properly initialized for cross-Space search.
102    /// This is a Phase 3 stub — actual cross-Space search requires Space-scoped MemoryManagers.
103    #[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        // Check visibility
112        let from_space = self.space_manager.get_space(&from_space_id).await?;
113        if let Some(space) = from_space {
114            if !space.memory_visible {
115                anyhow::bail!("Space {} is private and cannot be accessed", from_space_id);
116            }
117        }
118
119        // Search the from_space's memory
120        // Note: This requires a separate MemoryManager per Space, which
121        // is set up in SpaceManager. Here we use the provided memory_manager
122        // (which is the *current* Space's manager) — for true cross-Space
123        // search, we'd need Space-scoped MemoryManagers, which is Phase 3.
124        //
125        // For Phase 1, this is a stub that returns empty.
126        let entries = Vec::new();
127
128        // Record the reference
129        let entry = CrossRefEntry::new(
130            from_space_id,
131            to_space_id,
132            entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
133            MemoryFlow::Reference,
134        );
135        self.record_entry(entry);
136
137        Ok(entries)
138    }
139
140    /// Transfer: copy memory entries from one Space to another.
141    ///
142    /// Called when a new Space is created, to inject relevant knowledge
143    /// from existing Spaces.
144    ///
145    /// Returns the number of entries transferred.
146    ///
147    /// # Panics
148    ///
149    /// Panics if transfer is attempted with a private source Space.
150    #[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        // Check visibility
159        let from_space = self.space_manager.get_space(&from_space_id).await?;
160        if let Some(space) = from_space {
161            if !space.memory_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        // Transfer entries to the target space
172        // Note: Similar stub limitation as reference() above.
173        // Full implementation in Phase 3 with Space-scoped MemoryManagers.
174        let count = entries.len();
175
176        // Record the transfer
177        let entry = CrossRefEntry::new(
178            from_space_id,
179            to_space_id,
180            entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
181            MemoryFlow::Transfer,
182        );
183        self.record_entry(entry);
184
185        tracing::info!(
186            from = %from_space_id,
187            to = %to_space_id,
188            count,
189            "Memory transfer recorded"
190        );
191
192        Ok(count)
193    }
194
195    /// Record a cross-reference entry.
196    fn record_entry(&self, entry: CrossRefEntry) {
197        // Add to recent refs (bounded)
198        let mut refs = self.recent_refs.write();
199        refs.push(entry.clone());
200
201        // Keep only last 100
202        while refs.len() > 100 {
203            refs.remove(0);
204        }
205
206        // Also log to audit trail if available
207        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                        "memory_{}: {}->{} ({} 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    /// Get recent cross-reference entries.
225    pub fn recent_references(&self) -> Vec<CrossRefEntry> {
226        self.recent_refs.read().clone()
227    }
228
229    /// Get cross-references for a specific Space.
230    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_memory_flow_display() {
246        assert_eq!(MemoryFlow::Reference.to_string(), "reference");
247        assert_eq!(MemoryFlow::Transfer.to_string(), "transfer");
248        assert_eq!(MemoryFlow::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            MemoryFlow::Transfer,
258        );
259        assert_eq!(entry.entry_ids.len(), 2);
260        assert!(!entry.timestamp.format("%Y").to_string().is_empty());
261    }
262}