use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
struct Sandbox {
base: PathBuf,
source: PathBuf,
mind_home: PathBuf,
claude_home: PathBuf,
}
struct Run {
stdout: String,
stderr: String,
success: bool,
}
impl Sandbox {
fn new() -> Sandbox {
let sb = Sandbox::bare("agents");
sb.write_and_commit(
"skills/review/SKILL.md",
"---\ndescription: Review the diff for bugs\n---\n# review skill\n",
);
sb.write_and_commit(
"skills/extra/SKILL.md",
"---\ndescription: A second skill\n---\n# extra skill\n",
);
sb
}
fn bare(name: &str) -> Sandbox {
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base = std::env::temp_dir().join(format!("mind-pol-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join(name);
let sb = Sandbox {
base: base.clone(),
source: source.clone(),
mind_home: base.join("mind"),
claude_home: base.join("claude"),
};
write(&source.join("README.md"), "# fixture\n");
git(&source, &["-c", "init.defaultBranch=main", "init", "-q"]);
git(&source, &["config", "user.email", "t@t"]);
git(&source, &["config", "user.name", "t"]);
git(&source, &["add", "-A"]);
git(&source, &["commit", "-qm", "initial"]);
sb
}
fn mind_env(&self, args: &[&str], envs: &[(&str, &str)]) -> Run {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_mind"));
cmd.args(args)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null());
for (k, v) in envs {
cmd.env(k, v);
}
let out = cmd.output().expect("run mind");
Run {
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
success: out.status.success(),
}
}
fn write_and_commit(&self, rel: &str, contents: &str) {
write(&self.source.join(rel), contents);
git(&self.source, &["add", "-A"]);
git(&self.source, &["commit", "-qm", "fixture"]);
}
fn source_spec(&self) -> String {
self.source.to_string_lossy().into_owned()
}
fn link(&self, tail: &str) -> String {
format!("file://{}/{tail}", self.source.to_string_lossy())
}
fn write_policy(&self, body: &str) -> String {
let path = self.base.join("policy.toml");
write(&path, body);
path.to_string_lossy().into_owned()
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.base);
}
}
fn write(path: &Path, contents: &str) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, contents).unwrap();
}
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args(args)
.current_dir(dir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed in {dir:?}");
}
const NOT_PERMITTED: &str = "source not permitted by the managed policy's allowlist";
const SOURCE_NOT_ALLOWED: &str = "is not permitted by the managed policy's allowlist";
#[test]
fn sync_and_upgrade_admit_item_link_instance_under_locked_allowlist() {
let sb = Sandbox::new();
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let learn = sb.mind_env(
&["learn", &sb.link("tree/main/skills/review")],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
learn.success,
"learn of an item link into an allowed repo must succeed: {} {}",
learn.stdout, learn.stderr
);
let sync = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
sync.success,
"sync must succeed for an item-link instance of an allowed repo: {} {}",
sync.stdout, sync.stderr
);
assert!(
!sync.stdout.contains(NOT_PERMITTED),
"sync must not skip the item-link instance as not permitted: {}",
sync.stdout
);
let upgrade = sb.mind_env(
&["upgrade", "--no-sync"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
upgrade.success,
"upgrade must succeed for an item-link instance of an allowed repo: {} {}",
upgrade.stdout, upgrade.stderr
);
assert!(
!upgrade.stdout.contains(NOT_PERMITTED),
"upgrade must not skip the item-link instance as not permitted: {}",
upgrade.stdout
);
}
#[test]
fn sync_and_upgrade_admit_alias_instance_under_locked_allowlist() {
let sb = Sandbox::new();
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&["meld", &spec, "--as", "myalias", "--register-only"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
meld.success,
"melding an aliased instance of an allowed repo must succeed: {} {}",
meld.stdout, meld.stderr
);
let learn = sb.mind_env(
&["learn", "myalias:review"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
learn.success,
"learn of the aliased instance's item must succeed: {} {}",
learn.stdout, learn.stderr
);
let sync = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
sync.success,
"sync must succeed for an aliased instance of an allowed repo: {} {}",
sync.stdout, sync.stderr
);
assert!(
!sync.stdout.contains(NOT_PERMITTED),
"sync must not skip the aliased instance as not permitted: {}",
sync.stdout
);
let upgrade = sb.mind_env(
&["upgrade", "--no-sync"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
upgrade.success,
"upgrade must succeed for an aliased instance of an allowed repo: {} {}",
upgrade.stdout, upgrade.stderr
);
assert!(
!upgrade.stdout.contains(NOT_PERMITTED),
"upgrade must not skip the aliased instance as not permitted: {}",
upgrade.stdout
);
}
#[test]
fn sync_and_upgrade_admit_composed_link_and_alias_instance_under_locked_allowlist() {
let sb = Sandbox::new();
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let meld = sb.mind_env(
&[
"meld",
&sb.link("tree/main/skills/review"),
"--namespace",
"myalias",
"--register-only",
],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
meld.success,
"melding a namespaced item-link instance of an allowed repo must succeed: {} {}",
meld.stdout, meld.stderr
);
let learn = sb.mind_env(
&["learn", "myalias:review"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
learn.success,
"learn of the namespaced link instance's item must succeed: {} {}",
learn.stdout, learn.stderr
);
let sync = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
sync.success,
"sync must succeed for a composed link+alias instance of an allowed repo: {} {}",
sync.stdout, sync.stderr
);
assert!(
!sync.stdout.contains(NOT_PERMITTED),
"sync must not skip the composed instance as not permitted: {}",
sync.stdout
);
let upgrade = sb.mind_env(
&["upgrade", "--no-sync"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
upgrade.success,
"upgrade must succeed for a composed link+alias instance of an allowed repo: {} {}",
upgrade.stdout, upgrade.stderr
);
assert!(
!upgrade.stdout.contains(NOT_PERMITTED),
"upgrade must not skip the composed instance as not permitted: {}",
upgrade.stdout
);
}
#[test]
fn meld_still_refuses_alias_instance_of_a_different_disallowed_repo_under_lock() {
let sb = Sandbox::bare("other-repo");
sb.write_and_commit(
"skills/x/SKILL.md",
"---\ndescription: x skill\n---\n# x skill\n",
);
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&["meld", &spec, "--namespace", "myalias", "--register-only"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!meld.success,
"melding an aliased instance of a disallowed repo must still be refused: {} {}",
meld.stdout, meld.stderr
);
assert!(
meld.stderr.contains(SOURCE_NOT_ALLOWED),
"expected a SourceNotAllowed refusal in stderr: {}",
meld.stderr
);
}
#[test]
fn meld_refuses_alias_instance_of_a_repo_outside_the_allowlist() {
let sb = Sandbox::new();
let policy = sb.write_policy("[sources]\nlock = true\nallow = []\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&["meld", &spec, "--namespace", "myalias", "--register-only"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!meld.success,
"melding an aliased instance of a repo outside the allowlist must be refused: {} {}",
meld.stdout, meld.stderr
);
assert!(
meld.stderr.contains(SOURCE_NOT_ALLOWED),
"expected a SourceNotAllowed refusal in stderr: {}",
meld.stderr
);
}
#[test]
fn learn_refuses_item_link_instance_of_a_repo_outside_the_allowlist() {
let sb = Sandbox::new();
let policy = sb.write_policy("[sources]\nlock = true\nallow = []\n");
let learn = sb.mind_env(
&["learn", &sb.link("tree/main/skills/review")],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!learn.success,
"learn of an item link into a repo outside the allowlist must be refused: {} {}",
learn.stdout, learn.stderr
);
assert!(
learn.stderr.contains(SOURCE_NOT_ALLOWED),
"expected a SourceNotAllowed refusal in stderr: {}",
learn.stderr
);
}
#[test]
fn meld_refuses_a_repo_whose_name_merely_starts_with_an_allowed_name() {
let sb = Sandbox::bare("blessed@evil");
sb.write_and_commit(
"skills/x/SKILL.md",
"---\ndescription: x skill\n---\n# x skill\n",
);
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/blessed\"]\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&["meld", &spec, "--register-only"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!meld.success,
"a repo named `blessed@evil` must not be admitted by an allowlist naming `blessed`: {} {}",
meld.stdout, meld.stderr
);
assert!(
meld.stderr.contains("is not a usable repo spec") && meld.stderr.contains("repo"),
"expected the parse-time refusal naming the repo part: {}",
meld.stderr
);
}
#[test]
fn meld_admits_a_repo_whose_owner_segment_contains_a_marker() {
let sb = Sandbox::bare("proj@v2/agents");
sb.write_and_commit(
"skills/x/SKILL.md",
"---\ndescription: x skill\n---\n# x skill\n",
);
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&["meld", &spec, "--register-only"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
meld.success,
"an owner segment containing `@` must still match a wildcard over that segment: {} {}",
meld.stdout, meld.stderr
);
let sync = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
!sync.stdout.contains(NOT_PERMITTED) && !sync.stderr.contains(NOT_PERMITTED),
"sync must not skip an admitted source: {} {}",
sync.stdout,
sync.stderr
);
}
#[test]
fn meld_refuses_a_traversing_ssh_host_before_touching_the_filesystem() {
let sb = Sandbox::new();
let victim = sb.base.join("canary").join("owner").join("repo");
write(&victim.join("keep.txt"), "do not delete\n");
let meld = sb.mind_env(&["meld", "git@../../canary:owner/repo"], &[]);
assert!(
victim.join("keep.txt").exists(),
"the traversed directory outside the sources tree was deleted: {} {}",
meld.stdout,
meld.stderr
);
assert!(
!meld.success,
"a traversing ssh host must be refused: {} {}",
meld.stdout, meld.stderr
);
assert!(
meld.stderr.contains("is not a usable repo spec") && meld.stderr.contains("host"),
"expected an UnsafeRepoSpec refusal naming the host part: {}",
meld.stderr
);
}
#[test]
fn upgrade_reruns_install_hook_for_admitted_alias_instance_under_locked_allowlist() {
let sb = Sandbox::new();
sb.write_and_commit(
"mind.toml",
"[source]\ninstall = \"echo RAN >> counter.log\"\n",
);
let policy = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let spec = sb.source_spec();
let meld = sb.mind_env(
&[
"meld",
&spec,
"--namespace",
"myalias",
"--register-only",
"--accept-hooks=install,update,uninstall",
],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
meld.success,
"melding an aliased instance of an allowed repo must succeed: {} {}",
meld.stdout, meld.stderr
);
let counter = sb.source.join("counter.log");
assert_eq!(
std::fs::read_to_string(&counter).unwrap_or_default(),
"RAN\n",
"the install hook must have run once at meld time"
);
sb.write_and_commit(
"skills/review/SKILL.md",
"---\ndescription: Review the diff for bugs\n---\n# review skill\nedited\n",
);
let sync = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
sync.success,
"sync must succeed for the aliased instance: {} {}",
sync.stdout, sync.stderr
);
let upgrade = sb.mind_env(
&[
"upgrade",
"--no-sync",
"-y",
"--accept-hooks=install,update,uninstall",
],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
upgrade.success,
"upgrade must succeed for the aliased instance: {} {}",
upgrade.stdout, upgrade.stderr
);
assert!(
!upgrade.stdout.contains(NOT_PERMITTED),
"upgrade must not report the aliased instance's install hook as policy-blocked: {}",
upgrade.stdout
);
assert_eq!(
std::fs::read_to_string(&counter).unwrap_or_default(),
"RAN\nRAN\n",
"the install hook must have re-run on upgrade, proving the hook-gate site \
admitted the aliased instance rather than silently skipping it"
);
}
#[test]
fn remeld_pin_honors_require_pinned_policy() {
let sb = Sandbox::new();
git(&sb.source, &["tag", "v1.0"]);
git(&sb.source, &["branch", "stable"]);
let spec = sb.source_spec();
let policy =
sb.write_policy("[sources]\npinned = true\nlock = true\nallow = [\"local/*/agents\"]\n");
let first = sb.mind_env(
&["meld", &spec, "--register-only", "--pin", "v1.0"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
first.success,
"first meld: {} {}",
first.stdout, first.stderr
);
let r = sb.mind_env(
&["meld", &spec, "--register-only", "--pin", "branch=stable"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!r.success,
"a re-pin to a floating branch must be refused under require-pinned: {} {}",
r.stdout, r.stderr
);
assert!(
r.stderr.contains("must be pinned"),
"expected the require-pinned refusal, got: {}",
r.stderr
);
}
#[test]
fn remeld_pin_refused_when_source_falls_outside_locked_allowlist() {
let sb = Sandbox::new();
git(&sb.source, &["tag", "v1.0"]);
let spec = sb.source_spec();
let allowed = sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/agents\"]\n");
let first = sb.mind_env(
&["meld", &spec, "--register-only"],
&[("MIND_POLICY_FILE", allowed.as_str())],
);
assert!(
first.success,
"first meld: {} {}",
first.stdout, first.stderr
);
let narrowed =
sb.write_policy("[sources]\nlock = true\nallow = [\"local/*/some-other-repo\"]\n");
let r = sb.mind_env(
&["meld", &spec, "--register-only", "--pin", "v1.0"],
&[("MIND_POLICY_FILE", narrowed.as_str())],
);
assert!(
!r.success,
"a re-pin of a source dropped from the locked allowlist must be refused: {} {}",
r.stdout, r.stderr
);
assert!(
r.stderr.contains(SOURCE_NOT_ALLOWED),
"expected the SourceNotAllowed refusal, got: {}",
r.stderr
);
}
#[test]
fn auto_meld_install_records_a_source_it_cannot_scan_at_all() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let policy = sb.write_policy(&format!(
"[[sources.auto_meld]]\nrepo = \"{}\"\ninstall = true\n",
spec.replace('\\', "\\\\")
));
let first = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
first.success,
"auto_meld provisioning must succeed: {} {}",
first.stdout, first.stderr
);
assert!(
sb.claude_home.join("skills/review").exists(),
"the provisioned source's items must be installed by the first sync: {} {}",
first.stdout,
first.stderr
);
std::fs::remove_dir_all(&sb.source).expect("remove the linked working tree");
let second = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
second.stderr.contains("auto_meld item install failed"),
"the install pass must record the unscannable source as a failure, not \
return an empty item list: {} {}",
second.stdout,
second.stderr
);
assert!(
second.stderr.contains("linked working tree") && second.stderr.contains("is gone"),
"the recorded failure must carry the LinkedSourceGone cause: {} {}",
second.stdout,
second.stderr
);
assert!(
!second.success,
"POL-60 records the failure and exits non-zero: {} {}",
second.stdout, second.stderr
);
}
#[test]
fn auto_meld_install_records_one_bad_item_and_still_installs_the_rest() {
let sb = Sandbox::new();
sb.write_and_commit(
"skills/aaa-broken/SKILL.md",
"---\ndescription: references a sibling that does not exist\n---\n\
# broken\nSee {{ns:no-such-sibling}} for details.\n",
);
let spec = sb.source_spec();
let policy = sb.write_policy(&format!(
"[[sources.auto_meld]]\nrepo = \"{}\"\ninstall = true\n",
spec.replace('\\', "\\\\")
));
let r = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(
r.stderr.contains("auto_meld item install failed"),
"the failing item must be recorded through the POL-60 accounting: {} {}",
r.stdout,
r.stderr
);
assert!(
r.stderr.contains("skill:aaa-broken"),
"the recorded failure must name the ITEM, not just the source: {} {}",
r.stdout,
r.stderr
);
assert!(
!r.success,
"POL-60 exits non-zero when any item failed: {} {}",
r.stdout, r.stderr
);
assert!(
sb.claude_home.join("skills/review").exists(),
"a failing item must not abort the rest of the source's install: {} {}",
r.stdout,
r.stderr
);
assert!(
sb.claude_home.join("skills/extra").exists(),
"every other item must still install: {} {}",
r.stdout,
r.stderr
);
assert!(
!sb.claude_home.join("skills/aaa-broken").exists(),
"the failing item must not be left half-installed: {} {}",
r.stdout,
r.stderr
);
}
#[test]
fn auto_meld_install_under_json_emits_one_document() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let policy = sb.write_policy(&format!(
"[[sources.auto_meld]]\nrepo = \"{}\"\ninstall = true\n",
spec.replace('\\', "\\\\")
));
let r = sb.mind_env(
&["--json", "sync"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(r.success, "sync --json: {} {}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).unwrap_or_else(|e| {
panic!(
"stdout under --json must parse as a single JSON document ({e}): {:?}",
r.stdout
)
});
assert_eq!(
doc["action"], "sync",
"the one document is the invoked verb's, not a provisioned item's: {doc:#}"
);
assert!(
doc["installed"]
.as_array()
.is_some_and(|a| a.iter().any(|k| k == "skill:review")),
"sync must report what the auto_meld install pass installed: {doc:#}"
);
assert!(
sb.claude_home.join("skills/review").exists(),
"the provisioned item must actually be installed: {doc:#}"
);
}
#[test]
fn auto_meld_install_in_text_mode_still_narrates_each_item() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let policy = sb.write_policy(&format!(
"[[sources.auto_meld]]\nrepo = \"{}\"\ninstall = true\n",
spec.replace('\\', "\\\\")
));
let r = sb.mind_env(&["sync"], &[("MIND_POLICY_FILE", policy.as_str())]);
assert!(r.success, "sync: {} {}", r.stdout, r.stderr);
assert!(
r.stdout.contains("learned skill:review"),
"text-mode sync must narrate the provisioned install: stdout={} stderr={}",
r.stdout,
r.stderr
);
}
#[test]
fn auto_meld_install_under_json_records_one_bad_item_and_still_installs_the_rest() {
let sb = Sandbox::new();
sb.write_and_commit(
"skills/aaa-broken/SKILL.md",
"---\ndescription: references a sibling that does not exist\n---\n\
# broken\nSee {{ns:no-such-sibling}} for details.\n",
);
let spec = sb.source_spec();
let policy = sb.write_policy(&format!(
"[[sources.auto_meld]]\nrepo = \"{}\"\ninstall = true\n",
spec.replace('\\', "\\\\")
));
let r = sb.mind_env(
&["--json", "sync"],
&[("MIND_POLICY_FILE", policy.as_str())],
);
assert!(
!r.success,
"POL-60 exits non-zero when any item failed, even under --json: {} {}",
r.stdout, r.stderr
);
let doc: serde_json::Value = serde_json::from_str(r.stdout.trim()).unwrap_or_else(|e| {
panic!(
"stdout under --json must parse as a single JSON document even on a \
POL-60 failure ({e}): {:?}",
r.stdout
)
});
assert!(
doc.get("error").is_some(),
"a non-zero exit must answer with the CLI-181 error envelope: {doc:#}"
);
assert!(
!r.stderr.contains("auto_meld item install failed"),
"if this line starts appearing on stderr under --json, update this \
test to assert it (that would be a strict improvement over today's \
behavior, not a regression): {} {}",
r.stdout,
r.stderr
);
assert!(
sb.claude_home.join("skills/review").exists(),
"a failing item must not abort the rest of the source's install under \
--json: {} {}",
r.stdout,
r.stderr
);
assert!(
sb.claude_home.join("skills/extra").exists(),
"every other item must still install under --json: {} {}",
r.stdout,
r.stderr
);
assert!(
!sb.claude_home.join("skills/aaa-broken").exists(),
"the failing item must not be left half-installed: {} {}",
r.stdout,
r.stderr
);
}