//! GUN Decentralized Database Integration //! //! Provides decentralized P2P sync for swarm intelligence using GUN protocol. //! //! GUN Features: //! - Offline-first: Works without internet //! - Real-time sync: Changes propagate instantly //! - Decentralized: No central server needed //! - Graph database: Perfect for relationship data //! - Conflict resolution: HAM (Hypothetical Amnesia Machine) #[cfg(feature = "gun")] use gundb::Node; use crate::Result; use crate::intelligence::{LearningState, Pattern}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; /// GUN-backed swarm state that syncs across all peers #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GunSwarmState { /// Swarm identifier pub swarm_id: String, /// Connected peers pub peers: HashMap, /// Shared learning patterns (synced via GUN) pub patterns: HashMap, /// Shared memories (synced via GUN) pub memories: Vec, /// Global swarm config pub config: GunSwarmConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GunPeerInfo { pub agent_id: String, pub public_key: Option, pub last_seen: u64, pub patterns_count: usize, pub memories_count: usize, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GunMemory { pub id: String, pub content: String, pub embedding_hash: String, // Hash of embedding for dedup pub owner: String, pub timestamp: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GunSwarmConfig { /// GUN relay peers to connect to pub relays: Vec, /// Enable encryption (SEA) pub encrypted: bool, /// Sync interval in ms pub sync_interval_ms: u64, /// Max patterns to sync pub max_patterns: usize, } impl Default for GunSwarmConfig { fn default() -> Self { Self { relays: vec![ "https://gun-manhattan.herokuapp.com/gun".to_string(), "https://gun-us.herokuapp.com/gun".to_string(), ], encrypted: true, sync_interval_ms: 1000, max_patterns: 10000, } } } /// GUN-powered decentralized swarm sync pub struct GunSync { swarm_id: String, agent_id: String, state: Arc>, #[cfg(feature = "gun")] node: Option, config: GunSwarmConfig, } impl GunSync { /// Create new GUN sync manager pub fn new(swarm_id: &str, agent_id: &str, config: GunSwarmConfig) -> Self { let state = GunSwarmState { swarm_id: swarm_id.to_string(), peers: HashMap::new(), patterns: HashMap::new(), memories: Vec::new(), config: config.clone(), }; Self { swarm_id: swarm_id.to_string(), agent_id: agent_id.to_string(), state: Arc::new(RwLock::new(state)), #[cfg(feature = "gun")] node: None, config, } } /// Connect to GUN network #[cfg(feature = "gun")] pub async fn connect(&mut self) -> Result<()> { use gundb::Node; tracing::info!("Connecting to GUN network for swarm: {}", self.swarm_id); // Create GUN node let node = Node::new_with_config(gundb::Config { peers: self.config.relays.clone(), ..Default::default() }); self.node = Some(node); // Subscribe to swarm updates self.subscribe_to_swarm().await?; tracing::info!("Connected to GUN network"); Ok(()) } #[cfg(not(feature = "gun"))] pub async fn connect(&mut self) -> Result<()> { tracing::warn!("GUN feature not enabled, using local-only mode"); Ok(()) } /// Subscribe to swarm data changes #[cfg(feature = "gun")] async fn subscribe_to_swarm(&self) -> Result<()> { if let Some(ref node) = self.node { let path = format!("swarms/{}", self.swarm_id); // In real implementation, set up GUN subscriptions // node.get(&path).on(|data| { ... }); tracing::info!("Subscribed to GUN path: {}", path); } Ok(()) } /// Publish pattern to GUN network pub async fn publish_pattern(&self, pattern: &Pattern) -> Result<()> { let key = format!("{}|{}", pattern.state, pattern.action); // Update local state { let mut state = self.state.write().await; state.patterns.insert(key.clone(), pattern.clone()); } // Publish to GUN (if connected) #[cfg(feature = "gun")] if let Some(ref node) = self.node { let path = format!("swarms/{}/patterns/{}", self.swarm_id, key); let data = serde_json::to_string(pattern) .map_err(|e| SwarmError::Serialization(e.to_string()))?; // node.get(&path).put(&data); tracing::debug!("Published pattern to GUN: {}", path); } Ok(()) } /// Publish memory to GUN network pub async fn publish_memory(&self, memory: GunMemory) -> Result<()> { // Update local state { let mut state = self.state.write().await; state.memories.push(memory.clone()); } // Publish to GUN #[cfg(feature = "gun")] if let Some(ref node) = self.node { let path = format!("swarms/{}/memories/{}", self.swarm_id, memory.id); let data = serde_json::to_string(&memory) .map_err(|e| SwarmError::Serialization(e.to_string()))?; // node.get(&path).put(&data); tracing::debug!("Published memory to GUN: {}", path); } Ok(()) } /// Announce peer presence pub async fn announce_peer(&self) -> Result<()> { let peer_info = GunPeerInfo { agent_id: self.agent_id.clone(), public_key: None, // Would be set with SEA encryption last_seen: chrono::Utc::now().timestamp_millis() as u64, patterns_count: self.state.read().await.patterns.len(), memories_count: self.state.read().await.memories.len(), }; // Update local state { let mut state = self.state.write().await; state.peers.insert(self.agent_id.clone(), peer_info.clone()); } // Publish to GUN #[cfg(feature = "gun")] if let Some(ref node) = self.node { let path = format!("swarms/{}/peers/{}", self.swarm_id, self.agent_id); let data = serde_json::to_string(&peer_info) .map_err(|e| SwarmError::Serialization(e.to_string()))?; // node.get(&path).put(&data); tracing::debug!("Announced peer to GUN: {}", path); } Ok(()) } /// Get all patterns from swarm pub async fn get_patterns(&self) -> HashMap { self.state.read().await.patterns.clone() } /// Get all peers in swarm pub async fn get_peers(&self) -> Vec { self.state.read().await.peers.values().cloned().collect() } /// Get swarm statistics pub async fn get_stats(&self) -> GunSwarmStats { let state = self.state.read().await; GunSwarmStats { swarm_id: state.swarm_id.clone(), total_peers: state.peers.len(), total_patterns: state.patterns.len(), total_memories: state.memories.len(), relays: self.config.relays.len(), encrypted: self.config.encrypted, } } /// Sync learning state to GUN pub async fn sync_learning_state(&self, learning_state: &LearningState) -> Result { let mut synced = 0; for (_key, pattern) in &learning_state.patterns { self.publish_pattern(pattern).await?; synced += 1; } Ok(synced) } /// Import patterns from GUN to local learning state pub async fn import_to_learning_state(&self, learning_state: &mut LearningState) -> usize { let state = self.state.read().await; let mut imported = 0; for (key, pattern) in &state.patterns { if !learning_state.patterns.contains_key(key) { learning_state.patterns.insert(key.clone(), pattern.clone()); imported += 1; } else { // Merge patterns (take higher Q-value or more visits) if let Some(existing) = learning_state.patterns.get_mut(key) { if pattern.visits > existing.visits { *existing = pattern.clone(); imported += 1; } } } } imported } } /// GUN swarm statistics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GunSwarmStats { pub swarm_id: String, pub total_peers: usize, pub total_patterns: usize, pub total_memories: usize, pub relays: usize, pub encrypted: bool, } /// Builder for GUN-enabled swarm pub struct GunSwarmBuilder { swarm_id: String, relays: Vec, encrypted: bool, sync_interval_ms: u64, } impl GunSwarmBuilder { pub fn new(swarm_id: &str) -> Self { Self { swarm_id: swarm_id.to_string(), relays: vec![], encrypted: true, sync_interval_ms: 1000, } } /// Add a GUN relay peer pub fn with_relay(mut self, url: &str) -> Self { self.relays.push(url.to_string()); self } /// Add default public relays pub fn with_public_relays(mut self) -> Self { self.relays.extend(vec![ "https://gun-manhattan.herokuapp.com/gun".to_string(), "https://gun-us.herokuapp.com/gun".to_string(), "https://gun-eu.herokuapp.com/gun".to_string(), ]); self } /// Enable/disable encryption pub fn encrypted(mut self, enabled: bool) -> Self { self.encrypted = enabled; self } /// Set sync interval pub fn sync_interval(mut self, ms: u64) -> Self { self.sync_interval_ms = ms; self } /// Build GunSync instance pub fn build(self, agent_id: &str) -> GunSync { let config = GunSwarmConfig { relays: if self.relays.is_empty() { GunSwarmConfig::default().relays } else { self.relays }, encrypted: self.encrypted, sync_interval_ms: self.sync_interval_ms, max_patterns: 10000, }; GunSync::new(&self.swarm_id, agent_id, config) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_gun_sync_local() { let sync = GunSwarmBuilder::new("test-swarm") .encrypted(false) .build("agent-001"); // Test pattern publishing (local only, no GUN feature) let pattern = Pattern::new("edit_ts", "typescript-developer"); sync.publish_pattern(&pattern).await.unwrap(); let patterns = sync.get_patterns().await; assert_eq!(patterns.len(), 1); // Test stats let stats = sync.get_stats().await; assert_eq!(stats.swarm_id, "test-swarm"); assert_eq!(stats.total_patterns, 1); } #[tokio::test] async fn test_gun_peer_announce() { let sync = GunSwarmBuilder::new("test-swarm") .build("agent-001"); sync.announce_peer().await.unwrap(); let peers = sync.get_peers().await; assert_eq!(peers.len(), 1); assert_eq!(peers[0].agent_id, "agent-001"); } }