use std::fmt;
use serde_json::{json, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GateId {
FtsRead,
EmbeddingRead,
ContextRead,
HealthRead,
SessionWrite,
CommitWrite,
}
#[derive(Debug)]
pub enum ToolError {
InvalidParams(String),
PolicyRejected(String),
SizeLimitExceeded(String),
Internal(String),
}
impl ToolError {
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::InvalidParams(_) => "invalid_params",
Self::PolicyRejected(_) => "policy_rejected",
Self::SizeLimitExceeded(_) => "size_limit_exceeded",
Self::Internal(_) => "internal_error",
}
}
#[must_use]
pub fn resolution_data(&self) -> Value {
json!({
"schema": "cortex_refusal_resolution.v1",
"kind": self.kind(),
"summary": self.summary(),
"detail": self.to_string(),
"next_actions": self.next_actions(),
})
}
fn summary(&self) -> &'static str {
match self {
Self::InvalidParams(_) => "Fix the request parameters and retry the tool.",
Self::PolicyRejected(_) => {
"Cortex preserved the policy boundary; inspect and repair the blocked input before retrying."
}
Self::SizeLimitExceeded(_) => "Reduce or split the payload, then retry.",
Self::Internal(_) => {
"An internal tool failure occurred; inspect server logs before retrying."
}
}
}
fn next_actions(&self) -> Vec<&'static str> {
match self {
Self::InvalidParams(_) => vec![
"Compare the supplied params with the tool schema.",
"Remove unknown fields or correct field types.",
"Retry the same tool with the corrected params.",
],
Self::PolicyRejected(_) => vec![
"Inspect the refusal detail for the blocked invariant or policy outcome.",
"Use read-only tools such as cortex_memory_health, cortex_memory_list, or cortex_search to find the affected memory.",
"Repair, re-admit, or mark the memory outcome through the appropriate Cortex flow.",
"Retry the original tool after the policy outcome is no longer Reject, Quarantine, or BreakGlass.",
],
Self::SizeLimitExceeded(_) => vec![
"Split the payload into smaller batches.",
"Keep each request under the documented tool size limit.",
"Retry with the reduced payload.",
],
Self::Internal(_) => vec![
"Inspect Cortex MCP stderr/server logs for the full diagnostic.",
"Retry only after confirming the backing store and runtime are healthy.",
],
}
}
}
impl fmt::Display for ToolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ToolError::InvalidParams(msg) => write!(f, "invalid params: {msg}"),
ToolError::PolicyRejected(msg) => write!(f, "policy rejected: {msg}"),
ToolError::SizeLimitExceeded(msg) => write!(f, "size limit exceeded: {msg}"),
ToolError::Internal(msg) => write!(f, "internal error: {msg}"),
}
}
}
pub trait ToolHandler: Send + Sync {
fn name(&self) -> &'static str;
fn gate_set(&self) -> &'static [GateId];
fn call(&self, params: serde_json::Value) -> Result<serde_json::Value, ToolError>;
}