//! Binary-level contract for inspection flags on a pristine config dir: //! exit 0 and NOTHING written into the config dir (no first-run wizard, //! no builtins bootstrap). Only --info guarantees output on stdout; the //! list flags legitimately emit an empty listing on a pristine dir, so //! their contract is exit 0 + zero writes. Vault-backed flags like //! --list-secrets depend on host state and are covered by //! scripts/usage-probe-inspection.sh instead. Agent runs, by contrast, //! must still bootstrap builtins even when combined with --info — see //! agent_info_on_empty_config_dir_bootstraps_builtins. use std::env; use std::fs; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; fn fresh_config_dir(label: &str) -> PathBuf { let unique = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let tmp_dir = env::temp_dir().join(format!("coyote-inspection-{label}-{unique}")); fs::create_dir_all(&tmp_dir).unwrap(); tmp_dir } fn probe_inspection_flag(flag: &str, require_stdout: bool) { let tmp_dir = fresh_config_dir(flag.trim_start_matches("--")); let output = Command::new(env!("CARGO_BIN_EXE_coyote")) .arg(flag) .env("COYOTE_CONFIG_DIR", &tmp_dir) .stdin(Stdio::null()) .output() .unwrap(); let leftover: Vec<_> = fs::read_dir(&tmp_dir) .unwrap() .map(|entry| entry.unwrap().file_name()) .collect(); let _ = fs::remove_dir_all(&tmp_dir); assert!( output.status.success(), "{flag}: expected exit 0 on an empty config dir, got {:?}\nstdout: {}\nstderr: {}", output.status, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); if require_stdout { assert!( !output.stdout.is_empty(), "{flag}: expected some output on stdout" ); } assert!( leftover.is_empty(), "{flag}: inspection flags must write nothing into the config dir: {leftover:?}" ); } #[test] fn info_on_empty_config_dir_writes_nothing() { probe_inspection_flag("--info", true); } // List flags on a pristine dir correctly print an empty listing, so the // contract is exit 0 + zero writes only (no stdout assertion). #[test] fn list_models_on_empty_config_dir_writes_nothing() { probe_inspection_flag("--list-models", false); } #[test] fn list_agents_on_empty_config_dir_writes_nothing() { probe_inspection_flag("--list-agents", false); } #[test] fn mcp_list_on_empty_config_dir_writes_nothing() { probe_inspection_flag("--mcp-list", false); } // The wizard bypass must not swallow the builtins bootstrap for agent runs: // `--agent --info` on a totally fresh config dir has to install // the bundled definitions before it can render the agent readout. #[test] fn agent_info_on_empty_config_dir_bootstraps_builtins() { let tmp_dir = fresh_config_dir("agent-info"); let output = Command::new(env!("CARGO_BIN_EXE_coyote")) .args(["--agent", "adversary", "--info"]) .env("COYOTE_CONFIG_DIR", &tmp_dir) .stdin(Stdio::null()) .output() .unwrap(); let agents_dir = tmp_dir.join("agents"); let builtins_installed = agents_dir.is_dir() && fs::read_dir(&agents_dir).unwrap().next().is_some(); let _ = fs::remove_dir_all(&tmp_dir); assert!( output.status.success(), "--agent adversary --info: expected exit 0 on an empty config dir, got {:?}\nstdout: {}\nstderr: {}", output.status, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert!( builtins_installed, "--agent adversary --info: expected builtin agents installed into {}", agents_dir.display() ); // The --info readout echoes the loaded agent definition including its // global_tools, so a builtin tool name there proves the definitions were // loaded into the agent's tool scope, not just written to disk. let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("fs_cat.sh"), "--agent adversary --info: expected builtin tool fs_cat.sh in the readout\nstdout: {stdout}" ); }