use std::sync::{Arc, Mutex};
use chrono::Utc;
use cortex_core::{
accepted_axiom_source_commits, is_axiom_source_commit_fresh, parse_authority_feedback_loop,
parse_axiom_execution_trust, parse_cortex_context_trust, MemoryId,
AXIOM_EXECUTION_TRUST_SOURCE_COMMIT_STALE_INVARIANT,
};
use cortex_memory::{
AdmissionLifecycle, AxiomTrustExchangeAdmissionRequest, TrustExchangeAdmission,
};
use cortex_store::repo::{MemoryCandidate, MemoryRepo};
use cortex_store::Pool;
use serde_json::{json, Value};
use crate::tool_handler::{GateId, ToolError, ToolHandler};
#[derive(Debug)]
pub struct CortexAdmitAxiomTool {
pool: Arc<Mutex<Pool>>,
}
impl CortexAdmitAxiomTool {
#[must_use]
pub fn new(pool: Arc<Mutex<Pool>>) -> Self {
Self { pool }
}
}
impl ToolHandler for CortexAdmitAxiomTool {
fn name(&self) -> &'static str {
"cortex_admit_axiom"
}
fn gate_set(&self) -> &'static [GateId] {
&[GateId::SessionWrite]
}
fn call(&self, params: Value) -> Result<Value, ToolError> {
let exec_json = params["axiom_execution_trust_json"]
.as_str()
.filter(|s| !s.is_empty())
.ok_or_else(|| {
ToolError::InvalidParams("axiom_execution_trust_json is required".into())
})?;
let ctx_json = params["cortex_context_trust_json"]
.as_str()
.filter(|s| !s.is_empty())
.ok_or_else(|| {
ToolError::InvalidParams("cortex_context_trust_json is required".into())
})?;
let loop_json = params["authority_feedback_loop_json"]
.as_str()
.filter(|s| !s.is_empty())
.ok_or_else(|| {
ToolError::InvalidParams("authority_feedback_loop_json is required".into())
})?;
let persist = params["persist"].as_bool().unwrap_or(false);
let exec = parse_axiom_execution_trust(exec_json).map_err(|err| {
ToolError::InvalidParams(format!(
"axiom_execution_trust_json schema error: {}",
err.reason
))
})?;
let ctx = parse_cortex_context_trust(ctx_json).map_err(|err| {
ToolError::InvalidParams(format!(
"cortex_context_trust_json schema error: {}",
err.reason
))
})?;
let loop_rec = parse_authority_feedback_loop(loop_json).map_err(|err| {
ToolError::InvalidParams(format!(
"authority_feedback_loop_json schema error: {}",
err.reason
))
})?;
let accepted = accepted_axiom_source_commits();
if !is_axiom_source_commit_fresh(&exec.tool_provenance.source_commit, &accepted) {
return Err(ToolError::PolicyRejected(format!(
"{AXIOM_EXECUTION_TRUST_SOURCE_COMMIT_STALE_INVARIANT}: \
tool_provenance.source_commit `{}` is not on the Cortex-side acceptance list",
exec.tool_provenance.source_commit,
)));
}
let request = AxiomTrustExchangeAdmissionRequest::new(
exec.clone(),
AdmissionLifecycle::CandidateOnly,
)
.with_cortex_context_trust(ctx)
.with_authority_feedback_loop(loop_rec);
let decision = request.decide();
let policy = decision.policy_decision();
let policy_outcome = format!("{:?}", policy.final_outcome);
let failing_edges: Vec<&str> = decision.invariants();
let forbidden_uses: Vec<String> = decision
.forbidden_uses()
.map(|uses| {
uses.iter()
.map(|u| {
serde_json::to_value(u)
.ok()
.and_then(|v| v.as_str().map(ToOwned::to_owned))
.unwrap_or_else(|| "unknown".to_string())
})
.collect()
})
.unwrap_or_default();
let decision_name = decision.decision_name();
if persist {
if let TrustExchangeAdmission::AdmitCandidate { .. } = &decision {
let now = Utc::now();
let candidate_id = MemoryId::new();
let claim = format!(
"{}: {}",
exec.action_id,
exec.source_anchors
.first()
.map(|a| a.r#ref.as_str())
.unwrap_or("")
);
let source_anchors_json: Vec<Value> = exec
.source_anchors
.iter()
.map(|a| {
json!({
"source_id": a.source_id,
"source_type": serde_json::to_value(a.source_type)
.unwrap_or(Value::Null),
"ref": a.r#ref,
"hash": a.hash,
})
})
.collect();
let candidate = MemoryCandidate {
id: candidate_id,
memory_type: "semantic".to_string(),
claim,
source_episodes_json: json!([]),
source_events_json: json!(exec.action_id),
domains_json: json!([]),
salience_json: json!({ "score": 0.5 }),
confidence: 0.5,
authority: "axiom_candidate".to_string(),
applies_when_json: json!([]),
does_not_apply_when_json: json!([]),
created_at: now,
updated_at: now,
};
let pool = self
.pool
.lock()
.map_err(|err| ToolError::Internal(format!("pool lock poisoned: {err}")))?;
let repo = MemoryRepo::new(&pool);
let _ = source_anchors_json;
repo.insert_candidate(&candidate).map_err(|err| {
ToolError::Internal(format!("insert_candidate failed: {err}"))
})?;
repo.set_pending_mcp_commit(&candidate.id, now)
.map_err(|err| {
ToolError::Internal(format!("set_pending_mcp_commit failed: {err}"))
})?;
tracing::info!(
"cortex_admit_axiom: persisted candidate {} as pending_mcp_commit",
candidate.id
);
return Ok(json!({
"decision": decision_name,
"policy_outcome": policy_outcome,
"failing_edges": failing_edges,
"forbidden_uses": forbidden_uses,
"persisted": true,
"memory_id": candidate.id.to_string(),
}));
}
}
Ok(json!({
"decision": decision_name,
"policy_outcome": policy_outcome,
"failing_edges": failing_edges,
"forbidden_uses": forbidden_uses,
"persisted": false,
}))
}
}