use super::*;
pub(super) async fn dispatch_mem_query(
graph: &Arc<tokio::sync::RwLock<Graph>>,
req: &Request,
) -> Response {
use crate::mcp::handlers;
let request_id = req.id;
let input = match &req.cmd {
Command::MemQuery(i) => i,
_ => unreachable!("dispatch_mem_query guard"),
};
let g = graph.read().await;
match handlers::handle_mem_query(g.store(), &g, input).await {
Ok(data) => Response::ok(request_id, data),
Err((code, msg)) => Response::err(request_id, code, msg),
}
}
pub(super) async fn dispatch_side_effecting_read(
graph: &Arc<tokio::sync::RwLock<Graph>>,
ctx: &RequestContext,
req: &Request,
) -> Response {
use crate::mcp::handlers;
let request_id = req.id;
match &req.cmd {
Command::MemGet(input) => {
let g = graph.read().await;
match handlers::handle_mem_get(g.store(), graph, ctx, request_id, input).await {
Ok(data) => Response::ok(request_id, data),
Err((code, msg)) => {
let entry = build_audit_entry(
ctx,
request_id,
"mem_get",
&input.key,
false,
Some(code.clone()),
);
write_session_audit(g.store(), &entry).await;
Response::err(request_id, code, msg)
}
}
}
Command::MemBootstrap(input) => {
let g = graph.read().await;
match handlers::handle_mem_bootstrap(g.store(), &g, graph, ctx, request_id, input).await
{
Ok(injection) => Response::ok(request_id, serde_json::Value::String(injection)),
Err((code, msg)) => {
Response::err(request_id, code, msg)
}
}
}
_ => unreachable!("is_side_effecting_read guard"),
}
}
pub(super) async fn dispatch_via_v1(
graph: &Arc<tokio::sync::RwLock<Graph>>,
ctx: &RequestContext,
req: &Request,
) -> Response {
use crate::mcp::server::{socket_dispatch, SocketRequest};
let (cmd, args) = command_to_v1(&req.cmd);
if cmd == "put" || cmd == "delete" {
return Response::err(
req.id,
ErrorCode::Internal,
format!("v1 bridge produced forbidden mutation command: {cmd}"),
);
}
let v1_req = SocketRequest {
cmd,
version: Some(1),
args,
};
let v1_resp = socket_dispatch(graph, &ctx.repo_root, &v1_req).await;
if v1_resp.ok {
Response::ok(req.id, v1_resp.data.unwrap_or(serde_json::Value::Null))
} else {
let message = v1_resp.error.unwrap_or_else(|| "unknown error".to_string());
let code = classify_v1_error(&message);
Response::err(req.id, code, message)
}
}
fn classify_v1_error(message: &str) -> ErrorCode {
if message.contains("not found") {
ErrorCode::NotFound
} else if message.contains("already exists") {
ErrorCode::Conflict
} else if message.contains("tombstoned") || message.contains("cannot confirm") {
ErrorCode::InvalidStateTransition
} else if message.contains("store") {
ErrorCode::StoreError
} else {
ErrorCode::Internal
}
}
pub(super) fn command_to_v1(cmd: &Command) -> (String, serde_json::Value) {
use serde_json::json;
match cmd {
Command::Ping => ("ping".into(), json!({})),
Command::Metrics => ("metrics".into(), json!({})),
Command::Get(i) => ("get".into(), json!({ "key": i.key })),
Command::HookEvaluate(i) => (
"hook_evaluate".into(),
json!({ "file_key": i.file_key, "include_recent": i.include_recent, "actor": i.actor }),
),
Command::PolicyEvaluate(_) => {
unreachable!("PolicyEvaluate is handled natively, not via v1 bridge")
}
Command::ScanPrefix(i) => ("scan_prefix".into(), json!({ "prefix": i.prefix })),
Command::ScanKeys(i) => ("scan_keys".into(), json!({ "prefix": i.prefix })),
Command::History(i) => ("history".into(), json!({ "key": i.key, "limit": i.limit })),
Command::HistorySince(i) => (
"history_since".into(),
json!({ "key": i.key, "since_ts": i.since_ts, "limit": i.limit }),
),
Command::SessionCheckConsulted(i) => {
("session_check_consulted".into(), json!({ "key": i.key }))
}
Command::SessionCheckConsultedRecent(i) => (
"session_check_consulted_recent".into(),
json!({ "key": i.key, "ttl_secs": i.ttl_secs }),
),
Command::MemQuery(_) => {
unreachable!("MemQuery is handled natively, not via v1 bridge")
}
Command::ScanEnforcementEvents(i) => (
"scan_enforcement_events".into(),
json!({ "since_seq": i.since_seq, "until_seq": i.until_seq }),
),
Command::ScanEnforcementEventsWithSkips(i) => (
"scan_enforcement_events_with_skips".into(),
json!({ "since_seq": i.since_seq, "until_seq": i.until_seq }),
),
Command::ScanEnforcementEventsSinceMs(i) => (
"scan_enforcement_events_since_ms".into(),
json!({ "since_ms": i.since_ms, "until_ms": i.until_ms }),
),
Command::MemGet(_) | Command::MemBootstrap(_) => {
unreachable!("side-effecting reads are handled natively, not via v1 bridge")
}
Command::GotchaUpsert(_)
| Command::GotchaConfirm(_)
| Command::GotchaTombstone(_)
| Command::PolicyWrite(_)
| Command::FileEnrich(_)
| Command::FileReparse(_)
| Command::FileEditHook(_)
| Command::DocCapture(_)
| Command::DecisionUpsert(_)
| Command::DevNoteUpsert(_)
| Command::RecordImport(_) => {
unreachable!("knowledge-side mutations are handled natively, not via v1 bridge")
}
Command::SessionLog(_)
| Command::InstructionsLoaded(_)
| Command::ConsultationHit(_)
| Command::PolicyShadowObserve(_)
| Command::SessionFlush
| Command::SessionHarvest
| Command::SessionClearConsults
| Command::SubagentHarvest(_)
| Command::SubagentSpawned(_)
| Command::SubagentEdge(_) => {
unreachable!("session-side commands are handled natively, not via v1 bridge")
}
Command::ConfigGet(_) | Command::ConfigSet(_) | Command::SandboxAudit(_) => {
unreachable!("config commands are handled natively, not via v1 bridge")
}
}
}