pub mod action;
pub mod audit;
pub mod audit_replay;
pub mod completions;
pub mod ctl;
pub mod drift;
pub mod envs;
pub mod explain;
pub mod lint;
pub mod mcp;
pub mod versions;
pub const SUBCOMMANDS: &[&str] = &[
"envs",
"action",
"ctl",
"lint",
"drift",
"audit",
"mcp",
"explain",
"versions",
"completions",
];
pub(crate) use crate::deploy_poll::{decide_poll, PollDecision};
pub(crate) use crate::util::{json_escape as cli_esc, json_string};
pub(crate) fn refuse_if_frozen(prog: &str, action_label: &str) {
if let Some(m) = crate::freeze::read_active() {
let refusal = crate::write_gate::Refusal::Frozen;
crate::audit::append_action_refused(
None,
std::env::var("AWS_PROFILE").ok().as_deref(),
"-",
action_label,
"-",
refusal.rule(),
&refusal.remedy(),
);
eprintln!("{prog}: refusing — {}", crate::freeze::refusal_message(&m));
std::process::exit(3);
}
}
pub(crate) fn write_refusal(
safety_cfg: &crate::config::Config,
env: &str,
profile: &Option<String>,
active_freeze: Option<crate::freeze::FreezeMarker>,
region: Option<&str>,
action_label: &str,
) -> Option<String> {
let (refusal, message, pin_profile) =
write_refusal_unaudited(safety_cfg, env, profile, active_freeze)?;
crate::audit::append_action_refused(
None,
pin_profile.as_deref(),
region.unwrap_or("-"),
action_label,
env,
refusal.rule(),
&refusal.remedy(),
);
Some(message)
}
pub(crate) fn write_refusal_unaudited(
safety_cfg: &crate::config::Config,
env: &str,
profile: &Option<String>,
active_freeze: Option<crate::freeze::FreezeMarker>,
) -> Option<(crate::write_gate::Refusal, String, Option<String>)> {
let pin_profile = profile
.clone()
.or_else(|| std::env::var("AWS_PROFILE").ok());
let refusal = crate::write_gate::decide(&crate::write_gate::WriteContext {
env,
profile: pin_profile.as_deref(),
safety_parse_errors: &safety_cfg.safety_parse_errors,
global_read_only: false,
frozen: active_freeze.is_some(),
safety_envs: &safety_cfg.safety_envs,
safety_accounts: &safety_cfg.safety_accounts,
})?;
let message = match &refusal {
crate::write_gate::Refusal::SafetyConfigUnreadable { problem } => {
format!("refusing {env} — safety config unreadable: {problem}")
}
crate::write_gate::Refusal::Frozen => match active_freeze.as_ref() {
Some(m) => crate::freeze::refusal_message(m),
None => format!("refusing {env} — deploys frozen"),
},
crate::write_gate::Refusal::EnvPinned { env: e } => {
format!("refusing {env} — pinned by safety.envs.{e}.read_only")
}
crate::write_gate::Refusal::AccountPinned { profile: p } => {
format!("refusing {env} — pinned by safety.accounts.{p}.read_only")
}
crate::write_gate::Refusal::GlobalReadOnly => {
format!("refusing {env} — read-only mode")
}
};
Some((refusal, message, pin_profile))
}
pub(crate) fn refuse_write(
prog: &str,
subject: &str,
env: &str,
profile: Option<&str>,
region: Option<&str>,
action_label: &str,
) {
let profile = profile.map(str::to_string);
if let Some(reason) = write_refusal(
&crate::config::load(),
env,
&profile,
crate::freeze::read_active(),
region,
action_label,
) {
let reason = reason
.strip_prefix(&format!("refusing {env} — "))
.map(|r| format!("refusing {subject} — {r}"))
.unwrap_or(reason);
eprintln!("{prog}: {reason}");
std::process::exit(3);
}
}
pub(crate) fn take_value<'a, I: Iterator<Item = &'a String>>(
iter: &mut I,
prog: &str,
flag: &str,
what: &str,
) -> Result<String, String> {
let Some(v) = iter.next() else {
return Err(format!("{prog}: {flag} expects {what}"));
};
if v.starts_with("--") {
return Err(format!("{prog}: {flag} expects {what}, got flag '{v}'"));
}
Ok(v.clone())
}
pub(crate) async fn exit_after_drain(code: i32) -> ! {
crate::audit::drain_webhooks(std::time::Duration::from_secs(12)).await;
std::process::exit(code);
}
pub(crate) async fn drain_before_return() {
crate::audit::drain_webhooks(std::time::Duration::from_secs(12)).await;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_esc_escapes_quotes_and_backslashes() {
assert_eq!(cli_esc("hello"), "hello");
assert_eq!(cli_esc("a\"b"), "a\\\"b");
assert_eq!(cli_esc("a\\b"), "a\\\\b");
assert_eq!(cli_esc("a\nb"), "a\\nb");
assert_eq!(cli_esc("a\tb"), "a\\tb");
}
#[test]
fn json_string_wraps_in_quotes_and_escapes() {
assert_eq!(json_string(""), "\"\"");
assert_eq!(json_string("hello"), "\"hello\"");
assert_eq!(json_string("a\"b"), "\"a\\\"b\"");
let s = "line1\nline2 \"with quotes\"";
let escaped = json_string(s);
let parsed: String =
serde_json::from_str(&escaped).expect("hand-rolled JSON must be valid JSON");
assert_eq!(parsed, s);
}
}
#[cfg(test)]
mod write_gate_guard {
fn reaches_past_the_gate(code: &str) -> bool {
code.contains("safety_envs")
|| code.contains("safety_accounts")
|| code.contains("write_gate::decide")
}
#[test]
fn the_gate_guard_detects_what_it_is_looking_for() {
assert!(reaches_past_the_gate("if cfg.safety_envs.get(env) {"));
assert!(reaches_past_the_gate("cfg.safety_accounts.contains_key(p)"));
assert!(reaches_past_the_gate("crate::write_gate::decide(&ctx)"));
assert!(!reaches_past_the_gate(
"if let Some(r) = write_refusal(&cfg, env, &p, f) {"
));
assert!(!reaches_past_the_gate(
"refuse_write(prog, subject, env, profile)"
));
}
#[test]
fn write_refusal_paths_are_audited() {
const ALLOWED: &[(&str, usize, &str)] = &[
(
"src/cli/lint.rs",
1,
"a --fix dry run dispatched nothing; recording refusals \
of writes that were never going to happen is noise",
),
(
"src/cli/mcp/writes.rs",
1,
"demo mode reaches the same verdict and writes nothing \
real — the refusal is genuine, the fleet is not",
),
];
let mut found: Vec<(String, usize)> = Vec::new();
let mut stack = vec![std::path::PathBuf::from("src/cli")];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("src/cli") {
let path = entry.expect("entry").path();
if path.is_dir() {
stack.push(path);
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
if path.file_name().and_then(|f| f.to_str()) == Some("mod.rs") {
continue;
}
let text = std::fs::read_to_string(&path).expect("read");
let prod = text.split("#[cfg(test)]").next().unwrap_or("");
let n = prod.matches("write_refusal_unaudited(").count();
if n > 0 {
found.push((path.display().to_string(), n));
}
}
}
found.sort();
let mut expected: Vec<(String, usize)> = ALLOWED
.iter()
.map(|(p, n, _)| ((*p).to_string(), *n))
.collect();
expected.sort();
assert_eq!(
found, expected,
"the non-auditing gate gained or lost a caller. A refusal that \
writes no `stage=refused` line is invisible — the exact blind \
spot 0.37 closed. Justify the new site before listing it."
);
}
#[test]
fn the_unaudited_gate_guard_can_see_its_needle() {
let sample = "let r = crate::cli::write_refusal_unaudited(&cfg, env, &p, None);";
assert_eq!(sample.matches("write_refusal_unaudited(").count(), 1);
assert_eq!(
"crate::cli::write_refusal(&cfg, env, &p, None, None, \"X\")"
.matches("write_refusal_unaudited(")
.count(),
0,
"the auditing funnel must not be counted as the unaudited one"
);
}
#[test]
fn cli_write_paths_do_not_reach_past_the_shared_gate() {
let mut offenders: Vec<String> = Vec::new();
let mut stack = vec![std::path::PathBuf::from("src/cli")];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("src/cli") {
let path = entry.expect("entry").path();
if path.is_dir() {
stack.push(path);
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
if path.file_name().and_then(|f| f.to_str()) == Some("mod.rs") {
continue;
}
let text = std::fs::read_to_string(&path).expect("read");
let prod = text.split("#[cfg(test)]").next().unwrap_or("");
for (n, line) in prod.lines().enumerate() {
let code = crate::app::tests::scan::strip_line_comment(line);
if reaches_past_the_gate(code) {
offenders.push(format!("{}:{}", path.display(), n + 1));
}
}
}
}
assert!(
offenders.is_empty(),
"these CLI paths reach the safety config directly instead of going \
through `cli::write_refusal`, which also checks the freeze — \
the exact half-composition 0.14.1 shipped: {offenders:?}"
);
}
}
#[cfg(test)]
mod write_refusal_tests {
use super::write_refusal;
use crate::config::Config;
#[test]
fn an_account_pin_is_resolved_against_the_profile_passed_in() {
let mut cfg = Config::default();
cfg.safety_accounts.insert("prod-admin".into(), true);
let refused = write_refusal(
&cfg,
"api-prod",
&Some("prod-admin".into()),
None,
None,
"Test",
);
assert!(
refused.is_some_and(|r| r.contains("prod-admin")),
"a pinned account must refuse when it is the profile the write runs under"
);
assert_eq!(
write_refusal(&cfg, "api-prod", &Some("dev".into()), None, None, "Test"),
None
);
}
}
#[cfg(test)]
mod write_gate_input_guard {
#[test]
fn a_subcommand_with_its_own_profile_flag_passes_it_to_the_gate() {
let src = std::fs::read_to_string("src/cli/action.rs").expect("read action.rs");
let mut offenders: Vec<String> = Vec::new();
let mut current_fn = String::new();
let mut body = String::new();
let check = |name: &str, body: &str, offenders: &mut Vec<String>| {
if name.is_empty() || !body.contains("\"--profile\"") {
return;
}
for line in body.lines() {
let t = line.trim_start();
if t.starts_with("refuse_write(") && t.contains(", None)") {
offenders.push(format!("{name}: {}", t.trim()));
}
}
};
fn is_top_level_fn(line: &str) -> bool {
if line.starts_with(char::is_whitespace) {
return false;
}
let rest = match line.find(") ") {
Some(i) if line.starts_with("pub(") => &line[i + 2..],
_ => line.strip_prefix("pub ").unwrap_or(line),
};
rest.starts_with("fn ") || rest.starts_with("async fn ")
}
for line in src.lines() {
if is_top_level_fn(line) {
check(¤t_fn, &body, &mut offenders);
current_fn = line
.split("fn ")
.nth(1)
.unwrap_or("")
.split('(')
.next()
.unwrap_or("")
.to_string();
body.clear();
}
body.push('\n');
body.push_str(line);
}
check(¤t_fn, &body, &mut offenders);
assert!(
offenders.is_empty(),
"these subcommands parse `--profile` but hand the write gate \
`None`, so the account pin is resolved against the ambient \
AWS_PROFILE instead of the account the write runs under: \
{offenders:?}"
);
}
}