use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use cortex_ledger::{audit::verify_schema_migration_v1_to_v2_boundary, JsonlLog};
use serde_json::{json, Value};
use crate::{GateId, ToolError, ToolHandler};
#[derive(Debug)]
pub struct CortexDoctorTool {
pool: Arc<Mutex<cortex_store::Pool>>,
event_log: PathBuf,
}
impl CortexDoctorTool {
#[must_use]
pub fn new(pool: Arc<Mutex<cortex_store::Pool>>, event_log: PathBuf) -> Self {
Self { pool, event_log }
}
}
impl ToolHandler for CortexDoctorTool {
fn name(&self) -> &'static str {
"cortex_doctor"
}
fn gate_set(&self) -> &'static [GateId] {
&[GateId::HealthRead]
}
fn call(&self, _params: Value) -> Result<Value, ToolError> {
tracing::info!("cortex_doctor called via MCP");
let pool = self
.pool
.lock()
.map_err(|err| ToolError::Internal(format!("failed to acquire store lock: {err}")))?;
let mut issues: Vec<String> = Vec::new();
let schema_report =
cortex_store::verify::verify_schema_version(&pool, cortex_core::SCHEMA_VERSION)
.map_err(|err| {
ToolError::Internal(format!("failed to verify schema version: {err}"))
})?;
if !schema_report.is_ok() {
for failure in &schema_report.failures {
issues.push(format!("{}: {}", failure.invariant(), failure.detail()));
}
} else {
let needs_boundary = if cortex_core::SCHEMA_VERSION >= 2 {
contains_pre_cutover_v1_rows(&self.event_log).map_err(|err| {
ToolError::Internal(format!(
"failed to inspect event log for pre-cutover v1 rows: {err}"
))
})?
} else {
false
};
match verify_schema_migration_v1_to_v2_boundary(&self.event_log, needs_boundary) {
Ok(boundary_report) if boundary_report.ok() => {}
Ok(boundary_report) => {
for failure in &boundary_report.failures {
issues.push(format!("{}: {:?}", failure.invariant, failure.detail));
}
}
Err(err) => {
issues.push(format!("failed to verify schema boundary events: {err}"));
}
}
}
let ok = issues.is_empty();
Ok(json!({ "ok": ok, "issues": issues }))
}
}
fn contains_pre_cutover_v1_rows(
event_log_path: &std::path::Path,
) -> Result<bool, cortex_ledger::JsonlError> {
let log = JsonlLog::open(event_log_path)?;
for item in log.iter()? {
let event = item?;
if event.schema_version < cortex_core::SCHEMA_VERSION {
return Ok(true);
}
}
Ok(false)
}