use anyhow::Result;
use clap::Args;
use slugify::slugify;
use mati_core::mcp::protocol::PolicyWriteOp;
use mati_core::store::{
PolicyFreshness, PolicyMode, PolicyRecord, PolicyRequires, PolicyStage, PolicyTrigger,
Priority, ReceiptSource,
};
use crate::cli::protect::decision_record;
use crate::cli::proxy::StoreProxy;
#[derive(Args)]
pub struct GuardArgs {
glob: String,
#[arg(long)]
reason: Option<String>,
#[arg(long)]
steer: bool,
#[arg(long)]
enable: bool,
#[arg(long, default_value_t = 900)]
ttl: u64,
}
pub async fn run(args: GuardArgs) -> Result<()> {
let glob = args.glob.trim();
if glob.is_empty() {
anyhow::bail!("guard glob cannot be empty");
}
let slug = slugify!(glob);
if slug.is_empty() {
anyhow::bail!("could not derive a policy slug from glob '{glob}'");
}
let decision_key = format!("decision:guard-{slug}");
let policy_key = format!("policy:guard-{slug}");
let mode = if args.steer {
PolicyMode::Steer
} else {
PolicyMode::Block
};
let stage = if args.enable {
PolicyStage::Enforce
} else {
PolicyStage::Shadow
};
let reason = args.reason.unwrap_or_else(|| {
format!("`{glob}` is guarded; consult this record before running a matching command.")
});
let decision = decision_record(&decision_key, glob, &reason);
let policy = PolicyRecord {
name: format!("Guard {glob}"),
rule: format!("Consult {decision_key} before running `{glob}`."),
reason: reason.clone(),
scope: "repo".into(),
mode,
trigger: PolicyTrigger {
tool: None,
host_glob: None,
target_path_glob: None,
command_glob: Some(glob.to_string()),
},
requires: PolicyRequires {
key: decision_key.clone(),
via: vec![ReceiptSource::MemGet],
freshness: PolicyFreshness {
ttl_secs: args.ttl,
fingerprint: false,
},
},
stage,
severity: Priority::High,
created_by: "developer".into(),
};
mati_core::store::policy_ops::validate_trigger(&policy.trigger)?;
let cwd = std::env::current_dir()?;
let proxy = StoreProxy::open(&cwd).await?;
proxy.put(&decision_key, &decision).await?;
for warning in mati_core::store::policy_ops::author_warnings(&policy, true) {
eprintln!("{warning}");
}
let op = if proxy.get(&policy_key).await?.is_some() {
PolicyWriteOp::Edit
} else {
PolicyWriteOp::Create
};
let result = proxy.policy_write(op, &policy_key, Some(&policy)).await;
proxy.close_with_result(result).await?;
let stage_label = format!("{stage:?}").to_ascii_lowercase();
let mode_label = format!("{mode:?}").to_ascii_lowercase();
println!("Guarded `{glob}`");
println!(" {policy_key} ({mode_label}, {stage_label})");
println!(" {decision_key} authored (consult to unlock)");
if matches!(stage, PolicyStage::Shadow) {
println!("Run `mati guard '{glob}' --enable` to enforce, or `mati policy stage guard-{slug} enforce`.");
}
Ok(())
}