use std::fs;
use std::path::{Path, PathBuf};
use super::{DescriptorError, EMBEDDED_DESCRIPTORS};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Layer {
Embedded,
UserGlobal,
ProjectLocal,
HarnessFile,
}
impl Layer {
pub fn display_name(self) -> &'static str {
match self {
Layer::Embedded => "built-in",
Layer::UserGlobal => "user",
Layer::ProjectLocal => "project",
Layer::HarnessFile => "file",
}
}
}
#[derive(Debug, Clone)]
pub struct DescriptorSource {
pub layer: Layer,
pub path: String,
pub toml_src: String,
}
pub fn embedded_sources() -> Vec<DescriptorSource> {
EMBEDDED_DESCRIPTORS
.iter()
.map(|(path, toml_src)| DescriptorSource {
layer: Layer::Embedded,
path: (*path).to_string(),
toml_src: (*toml_src).to_string(),
})
.collect()
}
#[derive(Debug, thiserror::Error)]
#[error("cannot read --harness-file {path}: {message}")]
pub struct HarnessFileError {
pub path: String,
pub message: String,
}
pub fn discover_sources(
config_root: Option<&Path>,
project_root: &Path,
harness_file: Option<&Path>,
) -> Result<(Vec<DescriptorSource>, Vec<String>), HarnessFileError> {
let mut warnings = Vec::new();
let mut sources = embedded_sources();
if let Some(config_root) = config_root {
sources.extend(dir_sources(
&config_root.join("harnesses"),
Layer::UserGlobal,
&mut warnings,
));
}
sources.extend(dir_sources(
&project_root.join(".eval-magic").join("harnesses"),
Layer::ProjectLocal,
&mut warnings,
));
if let Some(file) = harness_file {
let toml_src = fs::read_to_string(file).map_err(|e| HarnessFileError {
path: file.display().to_string(),
message: e.to_string(),
})?;
sources.push(DescriptorSource {
layer: Layer::HarnessFile,
path: file.display().to_string(),
toml_src,
});
}
Ok((sources, warnings))
}
fn dir_sources(dir: &Path, layer: Layer, warnings: &mut Vec<String>) -> Vec<DescriptorSource> {
let Ok(entries) = fs::read_dir(dir) else {
return Vec::new();
};
let mut paths: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|ext| ext == "toml") && p.is_file())
.collect();
paths.sort();
paths
.into_iter()
.filter_map(|path| match fs::read_to_string(&path) {
Ok(toml_src) => Some(DescriptorSource {
layer,
path: path.display().to_string(),
toml_src,
}),
Err(e) => {
warnings.push(format!(
"skipping unreadable harness descriptor {}: {e}",
path.display()
));
None
}
})
.collect()
}
pub fn config_root_from(
explicit: Option<&str>,
xdg_config_home: Option<&str>,
home: Option<&Path>,
) -> Option<PathBuf> {
if let Some(explicit) = explicit {
if explicit.is_empty() {
return None;
}
return Some(PathBuf::from(explicit));
}
if let Some(xdg) = xdg_config_home
&& !xdg.is_empty()
{
return Some(Path::new(xdg).join("eval-magic"));
}
home.map(|home| home.join(".config").join("eval-magic"))
}
pub fn default_config_root() -> Option<PathBuf> {
config_root_from(
std::env::var("EVAL_MAGIC_CONFIG_DIR").ok().as_deref(),
std::env::var("XDG_CONFIG_HOME").ok().as_deref(),
std::env::home_dir().as_deref(),
)
}
pub fn check_user_layer_restrictions(
value: &serde_json::Value,
path: &str,
) -> Result<(), DescriptorError> {
let declares_guard = value.get("guard").is_some();
let flips_support = value
.pointer("/run/supports_guard")
.and_then(serde_json::Value::as_bool)
== Some(true);
if declares_guard || flips_support {
return Err(DescriptorError::Invariant {
path: path.to_string(),
message: "user-supplied descriptors may not declare [guard] or set \
run.supports_guard = true — the write guard fails open, so a mistyped \
guard block would silently disarm it; guard data stays restricted to \
built-in descriptors. Remove it; unguarded runs fall back to the \
detect-stray-writes audit."
.to_string(),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
#[test]
fn embedded_sources_cover_every_bundled_descriptor() {
let sources = embedded_sources();
assert_eq!(sources.len(), EMBEDDED_DESCRIPTORS.len());
assert!(sources.iter().all(|s| s.layer == Layer::Embedded));
assert_eq!(sources[0].path, "harnesses/claude-code.toml");
assert!(!sources[0].toml_src.is_empty());
}
#[test]
fn user_layer_may_not_declare_a_guard_table() {
let value: serde_json::Value =
toml::from_str("label = \"demo\"\n\n[guard]\narmed_message = \"x\"\n").unwrap();
let err = check_user_layer_restrictions(&value, "user.toml")
.unwrap_err()
.to_string();
assert!(err.contains("user.toml"), "{err}");
assert!(err.contains("may not declare [guard]"), "{err}");
assert!(
err.contains("detect-stray-writes"),
"names the fallback: {err}"
);
}
#[test]
fn user_layer_may_not_flip_supports_guard_on() {
let value: serde_json::Value =
toml::from_str("label = \"demo\"\n\n[run]\nsupports_guard = true\n").unwrap();
let err = check_user_layer_restrictions(&value, "user.toml")
.unwrap_err()
.to_string();
assert!(err.contains("run.supports_guard"), "{err}");
}
#[test]
fn user_layer_restrictions_pass_everything_else() {
let value: serde_json::Value = toml::from_str(
"label = \"demo\"\n\n[run]\nsupports_guard = false\n\n[model]\nflag = \"-m\"\n",
)
.unwrap();
assert!(check_user_layer_restrictions(&value, "user.toml").is_ok());
}
#[test]
fn config_root_prefers_explicit_env_then_xdg_then_home() {
assert_eq!(
config_root_from(Some("/explicit"), Some("/xdg"), Some(Path::new("/home/u"))),
Some("/explicit".into())
);
assert_eq!(
config_root_from(None, Some("/xdg"), Some(Path::new("/home/u"))),
Some("/xdg/eval-magic".into())
);
assert_eq!(
config_root_from(None, None, Some(Path::new("/home/u"))),
Some("/home/u/.config/eval-magic".into())
);
assert_eq!(config_root_from(None, None, None), None);
}
#[test]
fn empty_config_dir_env_disables_the_user_layer() {
assert_eq!(
config_root_from(Some(""), Some("/xdg"), Some(Path::new("/home/u"))),
None
);
}
#[test]
fn discovery_layers_in_precedence_order() {
let tmp = tempfile::TempDir::new().unwrap();
let config_root = tmp.path().join("config");
let project_root = tmp.path().join("project");
let user_dir = config_root.join("harnesses");
let project_dir = project_root.join(".eval-magic").join("harnesses");
fs::create_dir_all(&user_dir).unwrap();
fs::create_dir_all(&project_dir).unwrap();
fs::write(user_dir.join("b.toml"), "label = \"bee\"\n").unwrap();
fs::write(user_dir.join("a.toml"), "label = \"ay\"\n").unwrap();
fs::write(user_dir.join("notes.md"), "not a descriptor").unwrap();
fs::write(project_dir.join("p.toml"), "label = \"pea\"\n").unwrap();
let file = tmp.path().join("one-off.toml");
fs::write(&file, "label = \"one-off\"\n").unwrap();
let (sources, warnings) =
discover_sources(Some(&config_root), &project_root, Some(&file)).unwrap();
assert!(warnings.is_empty(), "{warnings:?}");
let tail: Vec<(Layer, &str)> = sources[EMBEDDED_DESCRIPTORS.len()..]
.iter()
.map(|s| (s.layer, s.path.as_str()))
.collect();
assert_eq!(sources[0].layer, Layer::Embedded);
assert_eq!(
tail,
vec![
(Layer::UserGlobal, user_dir.join("a.toml").to_str().unwrap()),
(Layer::UserGlobal, user_dir.join("b.toml").to_str().unwrap()),
(
Layer::ProjectLocal,
project_dir.join("p.toml").to_str().unwrap()
),
(Layer::HarnessFile, file.to_str().unwrap()),
]
);
}
#[test]
fn discovery_tolerates_missing_layer_dirs() {
let tmp = tempfile::TempDir::new().unwrap();
let (sources, warnings) = discover_sources(
Some(&tmp.path().join("nope")),
&tmp.path().join("also-nope"),
None,
)
.unwrap();
assert_eq!(sources.len(), EMBEDDED_DESCRIPTORS.len());
assert!(warnings.is_empty(), "{warnings:?}");
}
#[test]
fn discovery_fails_hard_on_an_unreadable_harness_file() {
let tmp = tempfile::TempDir::new().unwrap();
let missing = tmp.path().join("missing.toml");
let err = discover_sources(None, tmp.path(), Some(&missing))
.unwrap_err()
.to_string();
assert!(err.contains("--harness-file"), "{err}");
assert!(err.contains("missing.toml"), "{err}");
}
}