/// Swarm routes — distributed activation across the peer network. /// /// POST /swarm/activate — SwarmActivateRequest → SwarmActivateResponse /// GET /swarm/status — peer health check and last sync times use axum::{ extract::State, http::StatusCode, Json, }; use engram_sync::{ client::SyncClient, merge_activation_results, Peer, PeerActivationResult, PeerStatus, SerializableActivatedNode, SwarmActivateRequest, SwarmActivateResponse, }; use std::sync::Arc; use uuid::Uuid; use crate::state::AppState; /// POST /swarm/activate /// /// Runs spreading activation locally, then (if include_peers=true) fans out /// to all trusted peers in parallel and returns merged results. pub async fn swarm_activate( State(state): State>, Json(req): Json, ) -> Result, StatusCode> { // Step 1: Run local activation. Lock db, compute, drop immediately. let local_results: Vec = { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let activated = db .activate(&req.seeds, &req.query_embedding, req.max_depth, req.limit) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; activated.into_iter().map(Into::into).collect() // db lock released here }; // Step 2: Snapshot peer list (lock, read, drop). let (our_id, trusted_peers): (Uuid, Vec) = { let engine = state.sync_engine.lock().await; let id = engine.our_id(); let peers = engine.list_peers().iter().filter(|p| p.trusted).cloned().collect(); (id, peers) // engine lock released here }; // Step 3: Fan out to peers — no locks held across these awaits. let mut peer_results: Vec = Vec::new(); if req.include_peers { let mut handles = Vec::new(); for peer in trusted_peers { let seeds = req.seeds.clone(); let embedding = req.query_embedding.clone(); let max_depth = req.max_depth; let limit = req.limit; handles.push(tokio::spawn(async move { let client = SyncClient::new(peer.clone(), our_id); match client.remote_activate(&seeds, &embedding, max_depth, limit).await { Ok(results) => PeerActivationResult { peer_id: peer.id, peer_name: peer.name, results, error: None, }, Err(e) => PeerActivationResult { peer_id: peer.id, peer_name: peer.name, results: Vec::new(), error: Some(e.to_string()), }, } })); } for handle in handles { if let Ok(result) = handle.await { peer_results.push(result); } } } // Step 4: Merge and return. let merged = merge_activation_results(&local_results, &peer_results, req.limit); Ok(Json(SwarmActivateResponse { local_results, peer_results, merged, })) } /// GET /swarm/status /// /// Returns peer list with reachability status and last sync times. pub async fn swarm_status( State(state): State>, ) -> Result>, StatusCode> { // Snapshot peer list and our_id without holding the lock across awaits. let (our_id, peers): (Uuid, Vec) = { let engine = state.sync_engine.lock().await; let id = engine.our_id(); let peers = engine.list_peers().to_vec(); (id, peers) }; let mut statuses: Vec = Vec::new(); for peer in peers { use engram_core::types::now_ms; let client = SyncClient::new(peer.clone(), our_id); let reachable = client.pull_delta(now_ms()).await.is_ok(); statuses.push(PeerStatus { peer_id: peer.id, peer_name: peer.name, address: peer.address, last_sync_at: peer.last_sync_at, reachable, sync_tiers: peer.sync_tiers, trusted: peer.trusted, }); } Ok(Json(statuses)) }