use std::path::{Path, PathBuf};
const IDS: [&str; 5] = ["claude-code", "codex", "gemini-cli", "opencode", "pi"];
pub(super) fn validate(id: &str) -> anyhow::Result<()> {
anyhow::ensure!(
IDS.contains(&id),
"harness.id refused: expected {}",
IDS.join(", ")
);
Ok(())
}
pub(super) fn executable(program: &Path) -> anyhow::Result<PathBuf> {
if program.is_absolute() {
return check(program);
}
anyhow::ensure!(
program.components().count() == 1,
"harness.program must be absolute or a bare executable name"
);
let path = std::env::var_os("PATH")
.ok_or_else(|| anyhow::anyhow!("harness.program unavailable: PATH is absent"))?;
for directory in std::env::split_paths(&path).filter(|directory| directory.is_absolute()) {
let candidate = directory.join(program);
if let Ok(candidate) = check(&candidate) {
return Ok(candidate);
}
}
anyhow::bail!("harness.program unavailable on the launch PATH")
}
fn check(path: &Path) -> anyhow::Result<PathBuf> {
use std::os::unix::fs::PermissionsExt as _;
let metadata = std::fs::metadata(path)
.map_err(|error| anyhow::anyhow!("harness.program unavailable: {}", error.kind()))?;
anyhow::ensure!(
metadata.is_file() && metadata.permissions().mode() & 0o111 != 0,
"harness.program is not an executable file"
);
Ok(path.to_path_buf())
}