use crate::PurgeFilter;
use super::PurgeParams;
pub fn handle_maintain(server: &super::AlayaMcp) -> String {
let transform = server.with_store(|s| s.lifecycle().transform());
let forget = server.with_store(|s| s.lifecycle().forget());
match (transform, forget) {
(Ok(tr), Ok(fr)) => format!(
"Maintenance complete:\n Duplicates merged: {}\n Links pruned: {}\n Preferences decayed: {}\n Nodes decayed: {}\n Nodes archived: {}",
tr.duplicates_merged,
tr.links_pruned,
tr.preferences_decayed,
fr.nodes_decayed,
fr.nodes_archived,
),
(Err(e), _) | (_, Err(e)) => format!("Error: {e}"),
}
}
pub fn handle_purge(server: &super::AlayaMcp, params: PurgeParams) -> String {
let filter = match params.scope.as_str() {
"session" => match params.session_id {
Some(sid) => PurgeFilter::Session(sid),
None => return "Error: session_id required for scope 'session'".to_string(),
},
"older_than" => match params.before_timestamp {
Some(ts) => PurgeFilter::OlderThan(ts),
None => return "Error: before_timestamp required for scope 'older_than'".to_string(),
},
"all" => PurgeFilter::All,
_ => {
return format!(
"Error: invalid scope '{}'. Use: session, older_than, all",
params.scope
)
}
};
match server.with_store(|s| s.admin().purge(filter)) {
Ok(report) => format!(
"Purge complete: {} episodes deleted",
report.episodes_deleted
),
Err(e) => format!("Error: {e}"),
}
}
pub fn handle_reconcile(server: &super::AlayaMcp) -> String {
match server.with_store(|s| s.lifecycle().reconcile()) {
Ok(report) => format!(
"Reconciliation complete: detected: {}, resolved: {}, pending: {}, superseded: {}",
report.conflicts_detected,
report.conflicts_resolved,
report.conflicts_pending,
report.nodes_superseded,
),
Err(e) => format!("Error: {e}"),
}
}
pub fn handle_conflicts(server: &super::AlayaMcp) -> String {
match server.with_store(|s| s.lifecycle().conflicts()) {
Ok(conflicts) if conflicts.is_empty() => "No unresolved conflicts.".to_string(),
Ok(conflicts) => {
let mut out = format!("Found {} unresolved conflicts:\n\n", conflicts.len());
for c in &conflicts {
out.push_str(&format!(
"- Conflict #{}: node {} vs node {} (similarity: {:.2}, status: {})\n",
c.id.0,
c.node_a.0,
c.node_b.0,
c.similarity,
c.status.as_str(),
));
}
out
}
Err(e) => format!("Error: {e}"),
}
}
#[cfg(all(test, feature = "mcp"))]
mod tests {
use crate::Alaya;
use super::super::{AlayaMcp, PurgeParams, RememberParams};
fn make_server() -> AlayaMcp {
let store = Alaya::open_in_memory().unwrap();
AlayaMcp::new(store)
}
fn server_with_episodes(n: u32) -> AlayaMcp {
let srv = make_server();
for i in 0..n {
srv.remember(RememberParams {
content: format!("Message number {i}"),
role: "user".into(),
session_id: "sess-1".into(),
});
}
srv
}
#[test]
fn maintain_empty_store() {
let srv = make_server();
let result = srv.maintain();
assert!(result.contains("Maintenance complete"));
assert!(result.contains("Duplicates merged: 0"));
assert!(result.contains("Links pruned: 0"));
}
#[test]
fn maintain_after_data() {
let srv = server_with_episodes(5);
let result = srv.maintain();
assert!(result.contains("Maintenance complete"));
}
#[test]
fn purge_session() {
let srv = make_server();
for i in 0..3 {
srv.remember(RememberParams {
content: format!("Sess A msg {i}"),
role: "user".into(),
session_id: "sess-a".into(),
});
}
for i in 0..2 {
srv.remember(RememberParams {
content: format!("Sess B msg {i}"),
role: "user".into(),
session_id: "sess-b".into(),
});
}
let result = srv.purge(PurgeParams {
scope: "session".into(),
session_id: Some("sess-a".into()),
before_timestamp: None,
});
assert!(result.contains("Purge complete"));
assert!(result.contains("3 episodes deleted"));
}
#[test]
fn purge_older_than() {
let srv = make_server();
srv.remember(RememberParams {
content: "Old message".into(),
role: "user".into(),
session_id: "s1".into(),
});
let result = srv.purge(PurgeParams {
scope: "older_than".into(),
session_id: None,
before_timestamp: Some(i64::MAX),
});
assert!(result.contains("Purge complete"));
assert!(result.contains("episodes deleted"));
}
#[test]
fn purge_all() {
let srv = server_with_episodes(5);
let result = srv.purge(PurgeParams {
scope: "all".into(),
session_id: None,
before_timestamp: None,
});
assert!(result.contains("Purge complete"));
let status = srv.status();
assert!(
status.contains("Episodes: 0"),
"All episodes should be gone after purge all: {status}"
);
}
#[test]
fn purge_invalid_scope() {
let srv = make_server();
let result = srv.purge(PurgeParams {
scope: "invalid".into(),
session_id: None,
before_timestamp: None,
});
assert!(result.starts_with("Error: invalid scope"));
assert!(result.contains("invalid"));
}
#[test]
fn purge_session_without_session_id() {
let srv = make_server();
let result = srv.purge(PurgeParams {
scope: "session".into(),
session_id: None,
before_timestamp: None,
});
assert_eq!(result, "Error: session_id required for scope 'session'");
}
#[test]
fn purge_older_than_without_timestamp() {
let srv = make_server();
let result = srv.purge(PurgeParams {
scope: "older_than".into(),
session_id: None,
before_timestamp: None,
});
assert_eq!(
result,
"Error: before_timestamp required for scope 'older_than'"
);
}
#[test]
fn maintain_db_error() {
let store = Alaya::open_in_memory().unwrap();
store
.raw_conn()
.execute_batch("DROP TABLE semantic_nodes")
.unwrap();
let srv = AlayaMcp::new(store);
let result = srv.maintain();
assert!(
result.starts_with("Error:"),
"Should return error when DB is corrupted: {result}"
);
}
#[test]
fn purge_db_error() {
let store = Alaya::open_in_memory().unwrap();
store
.raw_conn()
.execute_batch("DROP TABLE episodes")
.unwrap();
let srv = AlayaMcp::new(store);
let result = srv.purge(PurgeParams {
scope: "all".into(),
session_id: None,
before_timestamp: None,
});
assert!(
result.starts_with("Error:"),
"Should return error when DB is corrupted: {result}"
);
}
#[test]
fn purge_session_deletes_only_that_session() {
let srv = make_server();
srv.remember(RememberParams {
content: "Keep me".into(),
role: "user".into(),
session_id: "keep".into(),
});
srv.remember(RememberParams {
content: "Delete me".into(),
role: "user".into(),
session_id: "delete".into(),
});
srv.purge(PurgeParams {
scope: "session".into(),
session_id: Some("delete".into()),
before_timestamp: None,
});
let result = srv.recall(super::super::RecallParams {
query: "Keep me".into(),
max_results: None,
boost_category: None,
});
assert!(result.contains("Found"));
assert!(result.contains("Keep me"));
}
#[test]
fn reconcile_empty_store() {
let srv = make_server();
let result = srv.reconcile_memories();
assert!(result.contains("Reconciliation complete"));
assert!(result.contains("detected: 0"));
}
#[test]
fn conflicts_empty_store() {
let srv = make_server();
let result = srv.list_conflicts();
assert_eq!(result, "No unresolved conflicts.");
}
#[test]
fn reconcile_db_error() {
let store = Alaya::open_in_memory().unwrap();
store
.raw_conn()
.execute_batch("DROP TABLE conflicts")
.unwrap();
let srv = AlayaMcp::new(store);
let result = srv.reconcile_memories();
assert!(
result.starts_with("Error:"),
"Should return error when DB is corrupted: {result}"
);
}
#[test]
fn conflicts_with_data() {
let store = Alaya::open_in_memory().unwrap();
store
.raw_conn()
.execute(
"INSERT INTO semantic_nodes (content, node_type, confidence, created_at, last_corroborated)
VALUES ('fact A', 'fact', 0.9, 1000, 1000)",
[],
)
.unwrap();
store
.raw_conn()
.execute(
"INSERT INTO semantic_nodes (content, node_type, confidence, created_at, last_corroborated)
VALUES ('fact B', 'fact', 0.8, 2000, 2000)",
[],
)
.unwrap();
store
.raw_conn()
.execute(
"INSERT INTO conflicts (node_a_id, node_b_id, similarity, status, detected_at)
VALUES (1, 2, 0.92, 'detected', 1000)",
[],
)
.unwrap();
let srv = AlayaMcp::new(store);
let result = srv.list_conflicts();
assert!(
result.contains("Found 1 unresolved conflict"),
"Should show conflicts: {result}"
);
assert!(
result.contains("node 1 vs node 2"),
"Should show node IDs: {result}"
);
assert!(
result.contains("similarity: 0.92"),
"Should show similarity: {result}"
);
}
#[test]
fn conflicts_db_error() {
let store = Alaya::open_in_memory().unwrap();
store
.raw_conn()
.execute_batch("DROP TABLE conflicts")
.unwrap();
let srv = AlayaMcp::new(store);
let result = srv.list_conflicts();
assert!(
result.starts_with("Error:"),
"Should return error when DB is corrupted: {result}"
);
}
}