use std::sync::{Arc, Mutex};
use serde_json::{json, Value};
use crate::{GateId, ToolError, ToolHandler};
#[derive(Debug)]
pub struct CortexMemoryHealthTool {
pool: Arc<Mutex<cortex_store::Pool>>,
}
impl CortexMemoryHealthTool {
#[must_use]
pub fn new(pool: Arc<Mutex<cortex_store::Pool>>) -> Self {
Self { pool }
}
}
impl ToolHandler for CortexMemoryHealthTool {
fn name(&self) -> &'static str {
"cortex_memory_health"
}
fn gate_set(&self) -> &'static [GateId] {
&[GateId::HealthRead]
}
fn call(&self, _params: Value) -> Result<Value, ToolError> {
let pool = self
.pool
.lock()
.map_err(|err| ToolError::Internal(format!("failed to acquire store lock: {err}")))?;
let cutoff_dt = chrono::Utc::now() - chrono::Duration::days(30);
let cutoff_rfc = cutoff_dt.to_rfc3339();
let mut stmt = pool
.prepare(
"SELECT created_at, validation_epoch \
FROM memories WHERE status = 'active';",
)
.map_err(|err| ToolError::Internal(format!("failed to prepare health query: {err}")))?;
let rows = stmt
.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, Option<i64>>(1)?))
})
.map_err(|err| {
ToolError::Internal(format!("failed to query active memories: {err}"))
})?;
let mut total: i64 = 0;
let mut stale: i64 = 0;
let mut unvalidated: i64 = 0;
for row_result in rows {
let (created_at, validation_epoch) = row_result
.map_err(|err| ToolError::Internal(format!("failed to read health row: {err}")))?;
total += 1;
if created_at.as_str() < cutoff_rfc.as_str() {
stale += 1;
}
if validation_epoch.unwrap_or(0) == 0 {
unvalidated += 1;
}
}
let quarantined: i64 = pool
.query_row(
"SELECT COUNT(*) FROM memories WHERE status = 'quarantined';",
[],
|row| row.get(0),
)
.map_err(|err| {
ToolError::Internal(format!("failed to query quarantined count: {err}"))
})?;
Ok(json!({
"total": total,
"stale": stale,
"unvalidated": unvalidated,
"quarantined": quarantined,
}))
}
}