use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use super::SpaceId;
use crate::audit_trail::AuditTrail;
use crate::memory::{MemoryEntry, MemoryManager};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossRefEntry {
pub from: SpaceId,
pub to: SpaceId,
pub entry_ids: Vec<String>,
pub flow: MemoryFlow,
pub timestamp: DateTime<Utc>,
}
impl CrossRefEntry {
pub fn new(from: SpaceId, to: SpaceId, entry_ids: Vec<String>, flow: MemoryFlow) -> Self {
Self {
from,
to,
entry_ids,
flow,
timestamp: Utc::now(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MemoryFlow {
Reference,
Transfer,
Synthesis,
}
impl std::fmt::Display for MemoryFlow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MemoryFlow::Reference => write!(f, "reference"),
MemoryFlow::Transfer => write!(f, "transfer"),
MemoryFlow::Synthesis => write!(f, "synthesis"),
}
}
}
pub struct SpaceBridge {
space_manager: Arc<super::manager::SpaceManager>,
audit_trail: Option<Arc<AuditTrail>>,
recent_refs: parking_lot::RwLock<Vec<CrossRefEntry>>,
}
impl SpaceBridge {
pub fn new(
space_manager: Arc<super::manager::SpaceManager>,
audit_trail: Option<Arc<AuditTrail>>,
) -> Self {
Self {
space_manager,
audit_trail,
recent_refs: parking_lot::RwLock::new(Vec::new()),
}
}
#[allow(unused_variables)]
pub async fn reference(
&self,
from_space_id: SpaceId,
to_space_id: SpaceId,
_memory_manager: &MemoryManager,
_query: &str,
) -> anyhow::Result<Vec<MemoryEntry>> {
let from_space = self.space_manager.get_space(&from_space_id).await?;
if let Some(space) = from_space {
if !space.memory_visible {
anyhow::bail!("Space {} is private and cannot be accessed", from_space_id);
}
}
let entries = Vec::new();
let entry = CrossRefEntry::new(
from_space_id,
to_space_id,
entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
MemoryFlow::Reference,
);
self.record_entry(entry);
Ok(entries)
}
#[allow(unused_variables)]
pub async fn transfer(
&self,
from_space_id: SpaceId,
to_space_id: SpaceId,
_memory_manager: &MemoryManager,
entries: Vec<MemoryEntry>,
) -> anyhow::Result<usize> {
let from_space = self.space_manager.get_space(&from_space_id).await?;
if let Some(space) = from_space {
if !space.memory_visible {
tracing::warn!(
from = %from_space_id,
to = %to_space_id,
"Skipping transfer: source Space is private"
);
return Ok(0);
}
}
let count = entries.len();
let entry = CrossRefEntry::new(
from_space_id,
to_space_id,
entries.iter().map(|e: &MemoryEntry| e.id.clone()).collect(),
MemoryFlow::Transfer,
);
self.record_entry(entry);
tracing::info!(
from = %from_space_id,
to = %to_space_id,
count,
"Memory transfer recorded"
);
Ok(count)
}
fn record_entry(&self, entry: CrossRefEntry) {
let mut refs = self.recent_refs.write();
refs.push(entry.clone());
while refs.len() > 100 {
refs.remove(0);
}
if let Some(ref audit) = self.audit_trail {
audit.append(
format!("space:{}", entry.to),
crate::audit_trail::AuditAction::Other {
detail: format!(
"memory_{}: {}->{} ({} entries)",
entry.flow,
entry.from,
entry.to,
entry.entry_ids.len()
),
},
format!("{:?}", entry),
);
}
}
pub fn recent_references(&self) -> Vec<CrossRefEntry> {
self.recent_refs.read().clone()
}
pub fn references_for(&self, space_id: SpaceId) -> Vec<CrossRefEntry> {
self.recent_refs
.read()
.iter()
.filter(|e| e.from == space_id || e.to == space_id)
.cloned()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_flow_display() {
assert_eq!(MemoryFlow::Reference.to_string(), "reference");
assert_eq!(MemoryFlow::Transfer.to_string(), "transfer");
assert_eq!(MemoryFlow::Synthesis.to_string(), "synthesis");
}
#[test]
fn test_cross_ref_entry() {
let entry = CrossRefEntry::new(
SpaceId::new_v4(),
SpaceId::new_v4(),
vec!["mem1".to_string(), "mem2".to_string()],
MemoryFlow::Transfer,
);
assert_eq!(entry.entry_ids.len(), 2);
assert!(!entry.timestamp.format("%Y").to_string().is_empty());
}
}