Files
el/engram/engrams/engram-server/src/routes/sync.rs
T
Will Anderson 909c1577f1 rename crates/ to engrams/, bindings/ to receptors/
- crates/ → engrams/ (Rust engrams live here)
- bindings/ → receptors/ (cross-language access points into the graph)
- Cargo.toml workspace paths updated
2026-04-29 03:27:33 -05:00

134 lines
4.3 KiB
Rust

/// Sync routes — peer delta exchange and peer registry.
///
/// All routes under /sync require Authorization: Bearer {api_key}.
///
/// GET /sync/delta?since={ms}&peer_id={uuid} — generate delta for caller
/// POST /sync/push — receive delta from peer
/// POST /sync/peers — register a new peer
/// GET /sync/peers — list peers
/// DELETE /sync/peers/{id} — remove peer
use axum::{
extract::{Path, Query, State},
http::StatusCode,
Json,
};
use engram_core::types::MemoryTier;
use engram_sync::{Peer, SyncDelta};
use serde::Deserialize;
use std::sync::Arc;
use uuid::Uuid;
use crate::state::AppState;
// ── GET /sync/delta ───────────────────────────────────────────────────────────
#[derive(Deserialize)]
pub struct DeltaParams {
pub since: Option<i64>,
pub peer_id: Option<Uuid>,
}
pub async fn get_delta(
State(state): State<Arc<AppState>>,
Query(params): Query<DeltaParams>,
) -> Result<Json<SyncDelta>, StatusCode> {
let since = params.since.unwrap_or(0);
// Determine which tiers to expose based on caller's peer_id
let tiers: Vec<MemoryTier> = {
let engine = state.sync_engine.lock().await;
if let Some(peer_id) = params.peer_id {
if let Some(peer) = engine.get_peer(peer_id) {
if peer.trusted {
peer.sync_tiers.clone()
} else {
vec![MemoryTier::Semantic]
}
} else {
vec![MemoryTier::Semantic]
}
} else {
vec![MemoryTier::Semantic]
}
// engine lock released here
};
let engine = state.sync_engine.lock().await;
let delta = engine
.generate_delta(since, &tiers)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(delta))
}
// ── POST /sync/push ───────────────────────────────────────────────────────────
pub async fn push_delta(
State(state): State<Arc<AppState>>,
Json(delta): Json<SyncDelta>,
) -> Result<StatusCode, StatusCode> {
// Accepted tiers for incoming pushes (Semantic and Procedural by default)
let accepted_tiers = vec![MemoryTier::Semantic, MemoryTier::Procedural];
// Apply the delta using the DB handle directly (no async needed)
let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Tombstones
for id in &delta.tombstones {
let _ = db.delete_node(*id);
}
// Nodes
for node in delta.nodes {
if !accepted_tiers.contains(&node.tier) {
continue;
}
if db.get_node(node.id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?.is_some() {
continue;
}
db.put_node(node).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}
// Edges
for edge in delta.edges {
let from_ok = db.get_node(edge.from_id)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.is_some();
let to_ok = db.get_node(edge.to_id)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.is_some();
if from_ok && to_ok {
let _ = db.put_edge(edge);
}
}
Ok(StatusCode::OK)
}
// ── Peer registry ─────────────────────────────────────────────────────────────
pub async fn list_peers(
State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<Peer>>, StatusCode> {
let engine = state.sync_engine.lock().await;
Ok(Json(engine.list_peers().to_vec()))
}
pub async fn register_peer(
State(state): State<Arc<AppState>>,
Json(peer): Json<Peer>,
) -> Result<StatusCode, StatusCode> {
let mut engine = state.sync_engine.lock().await;
engine.add_peer(peer);
Ok(StatusCode::CREATED)
}
pub async fn delete_peer(
State(state): State<Arc<AppState>>,
Path(id): Path<Uuid>,
) -> Result<StatusCode, StatusCode> {
let mut engine = state.sync_engine.lock().await;
engine.remove_peer(id);
Ok(StatusCode::NO_CONTENT)
}