use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::*;
impl Command {
pub fn kind(&self) -> &'static str {
match self {
Self::Ping => "ping",
Self::Metrics => "metrics",
Self::Get(_) => "get",
Self::HookEvaluate(_) => "hook_evaluate",
Self::PolicyEvaluate(_) => "policy_evaluate",
Self::ScanPrefix(_) => "scan_prefix",
Self::ScanKeys(_) => "scan_keys",
Self::History(_) => "history",
Self::HistorySince(_) => "history_since",
Self::SessionCheckConsulted(_) => "session_check_consulted",
Self::SessionCheckConsultedRecent(_) => "session_check_consulted_recent",
Self::MemQuery(_) => "mem_query",
Self::ScanEnforcementEvents(_) => "scan_enforcement_events",
Self::ScanEnforcementEventsWithSkips(_) => "scan_enforcement_events_with_skips",
Self::ScanEnforcementEventsSinceMs(_) => "scan_enforcement_events_since_ms",
Self::ConfigGet(_) => "config_get",
Self::ConfigSet(_) => "config_set",
Self::SandboxAudit(_) => "sandbox_audit",
Self::MemGet(_) => "mem_get",
Self::MemBootstrap(_) => "mem_bootstrap",
Self::GotchaUpsert(_) => "gotcha_upsert",
Self::GotchaConfirm(_) => "gotcha_confirm",
Self::GotchaTombstone(_) => "gotcha_tombstone",
Self::PolicyWrite(_) => "policy_write",
Self::FileEnrich(_) => "file_enrich",
Self::FileReparse(_) => "file_reparse",
Self::FileEditHook(_) => "file_edit_hook",
Self::DocCapture(_) => "doc_capture",
Self::DecisionUpsert(_) => "decision_upsert",
Self::DevNoteUpsert(_) => "dev_note_upsert",
Self::SessionLog(_) => "session_log",
Self::InstructionsLoaded(_) => "instructions_loaded",
Self::ConsultationHit(_) => "consultation_hit",
Self::PolicyShadowObserve(_) => "policy_shadow_observe",
Self::SessionFlush => "session_flush",
Self::SessionHarvest => "session_harvest",
Self::SessionClearConsults => "session_clear_consults",
Self::SubagentHarvest(_) => "subagent_harvest",
Self::SubagentSpawned(_) => "subagent_spawned",
Self::SubagentEdge(_) => "subagent_edge",
Self::RecordImport(_) => "record_import",
}
}
pub fn target_key(&self) -> &str {
match self {
Self::Get(i) => &i.key,
Self::HookEvaluate(i) => &i.file_key,
Self::PolicyEvaluate(_) => "",
Self::ScanPrefix(i) => &i.prefix,
Self::ScanKeys(i) => &i.prefix,
Self::History(i) => &i.key,
Self::HistorySince(i) => &i.key,
Self::SessionCheckConsulted(i) => &i.key,
Self::SessionCheckConsultedRecent(i) => &i.key,
Self::MemQuery(i) => &i.query,
Self::MemGet(i) => &i.key,
Self::GotchaUpsert(i) => &i.key,
Self::GotchaConfirm(i) => &i.key,
Self::GotchaTombstone(i) => &i.key,
Self::PolicyWrite(i) => &i.key,
Self::FileEnrich(i) => &i.path,
Self::FileReparse(i) => &i.path,
Self::FileEditHook(i) => &i.path,
Self::DocCapture(i) => &i.path,
Self::DecisionUpsert(i) => &i.slug,
Self::DevNoteUpsert(i) => i.key.as_deref().unwrap_or(""),
Self::SessionLog(i) => &i.key,
Self::InstructionsLoaded(i) => &i.payload.file_path,
Self::ConsultationHit(i) => &i.key,
Self::PolicyShadowObserve(i) => &i.policy_key,
Self::ConfigGet(i) => &i.key,
Self::ConfigSet(i) => &i.key,
Self::SandboxAudit(i) => &i.setting,
Self::Ping
| Self::Metrics
| Self::MemBootstrap(_)
| Self::ScanEnforcementEvents(_)
| Self::ScanEnforcementEventsWithSkips(_)
| Self::ScanEnforcementEventsSinceMs(_)
| Self::SessionFlush
| Self::SessionHarvest
| Self::SessionClearConsults
| Self::SubagentHarvest(_)
| Self::SubagentSpawned(_)
| Self::SubagentEdge(_)
| Self::RecordImport(_) => "",
}
}
pub fn is_mutation(&self) -> bool {
matches!(
self,
Self::MemGet(_)
| Self::MemBootstrap(_)
| Self::GotchaUpsert(_)
| Self::GotchaConfirm(_)
| Self::GotchaTombstone(_)
| Self::PolicyWrite(_)
| Self::FileEnrich(_)
| Self::FileReparse(_)
| Self::FileEditHook(_)
| Self::DocCapture(_)
| Self::DecisionUpsert(_)
| Self::DevNoteUpsert(_)
| Self::SessionLog(_)
| Self::InstructionsLoaded(_)
| Self::ConsultationHit(_)
| Self::PolicyShadowObserve(_)
| Self::ConfigSet(_)
| Self::SandboxAudit(_)
| Self::SessionFlush
| Self::SessionHarvest
| Self::SessionClearConsults
| Self::SubagentHarvest(_)
| Self::SubagentSpawned(_)
| Self::SubagentEdge(_)
| Self::RecordImport(_)
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub ts: u64,
pub peer_uid: u32,
pub peer_pid: Option<u32>,
pub daemon_session: Uuid,
pub request_id: Uuid,
pub command_kind: String,
pub target_key: String,
pub accepted: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
}
pub fn v1_to_v2_command(cmd: &str, args: &serde_json::Value) -> serde_json::Value {
use serde_json::json;
match cmd {
"ping" => json!({"type": "ping"}),
"metrics" => json!({"type": "metrics"}),
"get" => json!({"type": "get", "key": args["key"]}),
"hook_evaluate" => json!({
"type": "hook_evaluate",
"file_key": args["file_key"],
"include_recent": args.get("include_recent").and_then(|v| v.as_bool()).unwrap_or(false),
"actor": args["actor"],
}),
"scan_prefix" => json!({"type": "scan_prefix", "prefix": args["prefix"]}),
"scan_keys" => json!({"type": "scan_keys", "prefix": args["prefix"]}),
"history" => {
json!({"type": "history", "key": args["key"], "limit": args.get("limit").and_then(|v| v.as_u64()).unwrap_or(50)})
}
"history_since" => json!({
"type": "history_since",
"key": args["key"],
"since_ts": args.get("since_ts").and_then(|v| v.as_u64()).unwrap_or(0),
"limit": args.get("limit").and_then(|v| v.as_u64()).unwrap_or(50),
}),
"session_check_consulted" => json!({"type": "session_check_consulted", "key": args["key"]}),
"session_check_consulted_recent" => json!({
"type": "session_check_consulted_recent",
"key": args["key"],
"ttl_secs": args.get("ttl_secs").and_then(|v| v.as_u64()).unwrap_or(900),
}),
"mem_query" => json!({
"type": "mem_query",
"query": args["query"],
"mode": args.get("mode").and_then(|v| v.as_str()).unwrap_or("text"),
"limit": args.get("limit").and_then(|v| v.as_u64()).unwrap_or(20),
"since": args.get("since").and_then(|v| v.as_u64()),
}),
"scan_enforcement_events" => json!({
"type": "scan_enforcement_events",
"since_seq": args.get("since_seq").and_then(|v| v.as_u64()).unwrap_or(0),
"until_seq": args.get("until_seq").and_then(|v| v.as_u64()).unwrap_or(u64::MAX),
}),
"scan_enforcement_events_with_skips" => json!({
"type": "scan_enforcement_events_with_skips",
"since_seq": args.get("since_seq").and_then(|v| v.as_u64()).unwrap_or(0),
"until_seq": args.get("until_seq").and_then(|v| v.as_u64()).unwrap_or(u64::MAX),
}),
"scan_enforcement_events_since_ms" => json!({
"type": "scan_enforcement_events_since_ms",
"since_ms": args.get("since_ms").and_then(|v| v.as_u64()).unwrap_or(0),
"until_ms": args.get("until_ms").and_then(|v| v.as_u64()).unwrap_or(u64::MAX),
}),
"mem_get" => json!({"type": "mem_get", "key": args["key"], "actor": args["actor"]}),
"mem_bootstrap" => json!({
"type": "mem_bootstrap",
"context_files": args.get("context_files").cloned().unwrap_or_else(|| serde_json::json!([])),
}),
other => {
panic!(
"v1_to_v2_command called with unsupported command '{other}' — \
only pure reads are supported; mutation/side-effecting callers \
must use daemon_v2() with typed Command"
);
}
}
}