Archived
125 lines
4.2 KiB
Rust
125 lines
4.2 KiB
Rust
/// 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<Arc<AppState>>,
|
|
Json(req): Json<SwarmActivateRequest>,
|
|
) -> Result<Json<SwarmActivateResponse>, StatusCode> {
|
|
// Step 1: Run local activation. Lock db, compute, drop immediately.
|
|
let local_results: Vec<SerializableActivatedNode> = {
|
|
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<Peer>) = {
|
|
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<PeerActivationResult> = 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<Arc<AppState>>,
|
|
) -> Result<Json<Vec<PeerStatus>>, StatusCode> {
|
|
// Snapshot peer list and our_id without holding the lock across awaits.
|
|
let (our_id, peers): (Uuid, Vec<Peer>) = {
|
|
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<PeerStatus> = 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))
|
|
}
|