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 n = COUNTER.fetch_add(1, Ordering::SeqCst);
let base =
std::env::temp_dir().join(format!("mind-prefix-guard-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&base);
let source = base.join("agents");
let sb = Sandbox {
base: base.clone(),
source: source.clone(),
mind_home: base.join("mind"),
claude_home: base.join("claude"),
};
write(
&source.join("skills/review/SKILL.md"),
"---\nname: review\ndescription: Review the diff for bugs\n---\n# review skill\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"]);
std::fs::create_dir_all(&sb.mind_home).unwrap();
sb
}
fn mind(&self, args: &[&str]) -> Run {
let out = Command::new(env!("CARGO_BIN_EXE_mind"))
.args(args)
.env("MIND_HOME", &self.mind_home)
.env("CLAUDE_HOME", &self.claude_home)
.env_remove("MIND_AGENT_HOMES")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null())
.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 source_spec(&self) -> String {
self.source.to_string_lossy().into_owned()
}
fn declare_prefix(&self, prefix: &str) {
write(
&self.source.join("mind.toml"),
&format!("[source]\nprefix = \"{prefix}\"\n"),
);
git(&self.source, &["add", "-A"]);
git(&self.source, &["commit", "-qm", "declare prefix"]);
}
}
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(repo: &Path, args: &[&str]) {
std::fs::create_dir_all(repo).unwrap();
let status = Command::new("git")
.args(args)
.current_dir(repo)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
const BLOCKED_UNICODE_CAUSE: &str = "invisible, bidi, or zero-width character";
#[test]
fn meld_namespace_with_bidi_override_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("pay{}oot", '\u{202E}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a bidi-override prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_namespace_short_flag_with_bidi_override_is_refused() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("pay{}oot", '\u{202E}');
let r = sb.mind(&["meld", &spec, "-N", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld -N with a bidi-override prefix must be refused"
);
assert!(r.stderr.contains(BLOCKED_UNICODE_CAUSE), "{}", r.stderr);
}
#[test]
fn meld_namespace_with_tag_block_character_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("acme{}", '\u{E0041}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a tag-block character in the prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_namespace_with_variation_selector_is_refused_as_unsafe_prefix() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let bad_prefix = format!("acme{}", '\u{FE0F}');
let r = sb.mind(&["meld", &spec, "--namespace", &bad_prefix, "--yes"]);
assert!(
!r.success,
"meld --namespace with a variation-selector character in the prefix must be refused"
);
assert!(r.stderr.contains(BLOCKED_UNICODE_CAUSE), "{}", r.stderr);
}
#[test]
fn meld_declared_prefix_with_bidi_override_is_refused_on_load() {
let sb = Sandbox::new();
let bad_prefix = format!("pay{}oot", '\u{202E}');
sb.declare_prefix(&bad_prefix);
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--yes"]);
assert!(
!r.success,
"meld of a source declaring an unsafe [source].prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn meld_declared_prefix_with_tag_block_character_is_refused_on_load() {
let sb = Sandbox::new();
let bad_prefix = format!("acme{}", '\u{E0041}');
sb.declare_prefix(&bad_prefix);
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--yes"]);
assert!(
!r.success,
"meld of a source declaring a tag-block [source].prefix must be refused"
);
assert!(
r.stderr.contains(BLOCKED_UNICODE_CAUSE),
"UnsafePrefix cause must be reported generically: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
!recall.contains("review"),
"a refused meld must not install anything: {recall}"
);
}
#[test]
fn an_alias_the_reserved_list_caught_up_with_warns_but_keeps_scanning() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
let registry = sb.mind_home.join("sources.json");
let text = std::fs::read_to_string(®istry).expect("read sources.json");
let recorded = "\"alias\": \"acme\"";
assert!(
text.contains(recorded),
"the namespace prefix must be recorded as `{recorded}` to be rewritten: {text}"
);
std::fs::write(®istry, text.replace(recorded, "\"alias\": \"workflow\"")).unwrap();
let recall = sb.mind(&["recall"]);
assert!(
recall.success,
"a reserved recorded prefix must not fail the scan: {} {}",
recall.stdout, recall.stderr
);
assert!(
recall
.stderr
.contains("'workflow' is a word mind now reserves"),
"the scan must warn that the recorded prefix is now a reserved word: {}",
recall.stderr
);
assert!(
recall
.stderr
.contains("unmeld it, then re-meld with `--namespace <other>`"),
"the warning must name the way to rename it: {}",
recall.stderr
);
let probe = sb.mind(&["probe", "--no-tui"]);
assert!(
probe.stdout.contains("skill:workflow:review"),
"the items must still be listed under the recorded prefix: {}",
probe.stdout
);
}
#[test]
fn the_reserved_alias_warning_prints_once_per_source_per_run() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
let registry = sb.mind_home.join("sources.json");
let text = std::fs::read_to_string(®istry).expect("read sources.json");
std::fs::write(
®istry,
text.replace("\"alias\": \"acme\"", "\"alias\": \"workflow\""),
)
.unwrap();
write(
&sb.source.join("skills/review/SKILL.md"),
"---\nname: review\ndescription: Review the diff, v2\n---\n# review skill v2\n",
);
write(
&sb.source.join("skills/other/SKILL.md"),
"---\nname: other\ndescription: Another skill\n---\n# other\n",
);
git(&sb.source, &["add", "-A"]);
git(&sb.source, &["commit", "-qm", "v2"]);
for args in [
vec!["recall"],
vec!["upgrade", "--yes"],
vec!["learn", "skill:workflow:other", "--yes"],
vec!["probe", "--no-tui"],
vec!["sync", "--upgrade", "--yes"],
vec!["introspect"],
vec!["recall", "skill:workflow:review"],
vec!["--json", "probe", "--no-tui"],
vec!["upgrade", "skill:workflow:review", "--yes"],
] {
let r = sb.mind(&args);
assert!(
r.success,
"{args:?} must succeed: {} {}",
r.stdout, r.stderr
);
assert_eq!(
r.stderr.matches("now reserves").count(),
1,
"{args:?}: the warning must print exactly once: {}",
r.stderr
);
}
}
#[test]
fn following_the_reserved_alias_remedy_clears_the_warning() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
let registry = sb.mind_home.join("sources.json");
let text = std::fs::read_to_string(®istry).expect("read sources.json");
std::fs::write(
®istry,
text.replace("\"alias\": \"acme\"", "\"alias\": \"workflow\""),
)
.unwrap();
assert!(
sb.mind(&["recall"]).stderr.contains("now reserves"),
"precondition: the warning fires before the remedy"
);
let unmeld = sb.mind(&["unmeld", "*agents*", "--yes"]);
assert!(
unmeld.success,
"unmeld must succeed: {} {}",
unmeld.stdout, unmeld.stderr
);
let remeld = sb.mind(&["meld", &spec, "--namespace", "acme2", "--yes"]);
assert!(
remeld.success,
"re-meld under another prefix must succeed: {} {}",
remeld.stdout, remeld.stderr
);
for args in [vec!["recall"], vec!["probe", "--no-tui"]] {
let r = sb.mind(&args);
assert!(
r.success,
"{args:?} must succeed: {} {}",
r.stdout, r.stderr
);
assert!(
!r.stderr.contains("reserves"),
"{args:?}: the warning must be gone after the remedy: {}",
r.stderr
);
}
assert!(
sb.mind(&["recall"]).stdout.contains("acme2:review"),
"the item is installed under the new prefix"
);
}
#[test]
fn a_melded_source_declaring_a_reserved_prefix_names_unmeld_as_the_remedy() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
sb.declare_prefix("workflow");
for (verb, must_fail) in [
(vec!["recall"], true),
(vec!["probe", "--no-tui"], true),
(vec!["learn", "review"], true),
(vec!["upgrade", "--no-sync", "--yes"], false),
(vec!["introspect"], false),
] {
let r = sb.mind(&verb);
assert!(
!must_fail || !r.success,
"{verb:?} must refuse a source declaring a now-reserved prefix: {} {}",
r.stdout,
r.stderr
);
let combined = format!("{}{}", r.stdout, r.stderr);
assert!(
combined.contains("mind unmeld"),
"{verb:?}: the error must name `mind unmeld <source>` as the remedy: {combined}"
);
assert!(
combined.contains("agents"),
"{verb:?}: the error must name the source it is about: {combined}"
);
assert!(
combined.contains("workflow"),
"{verb:?}: the error must name the offending prefix: {combined}"
);
}
let unmeld = sb.mind(&["unmeld", "agents", "--yes"]);
assert!(
unmeld.success,
"the named remedy must work: {} {}",
unmeld.stdout, unmeld.stderr
);
let recall = sb.mind(&["recall"]);
assert!(
recall.success,
"after the remedy, scanning verbs must work again: {} {}",
recall.stdout, recall.stderr
);
}
#[test]
fn the_reserved_alias_warning_does_not_reach_the_json_document() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
let registry = sb.mind_home.join("sources.json");
let text = std::fs::read_to_string(®istry).expect("read sources.json");
std::fs::write(
®istry,
text.replace("\"alias\": \"acme\"", "\"alias\": \"workflow\""),
)
.unwrap();
for args in [
vec!["--json", "recall"],
vec!["--json", "probe", "--no-tui"],
] {
let r = sb.mind(&args);
assert!(
r.success,
"{args:?} must still succeed under the warning: {} {}",
r.stdout, r.stderr
);
let doc: serde_json::Value = serde_json::from_str(&r.stdout).unwrap_or_else(|e| {
panic!(
"{args:?}: stdout must be exactly one JSON document ({e}): {}",
r.stdout
)
});
assert!(doc.is_object(), "{args:?}: {doc}");
assert!(
!r.stdout.contains("reserve"),
"{args:?}: the warning must not be on stdout: {}",
r.stdout
);
assert!(
r.stderr.contains("reserve"),
"{args:?}: the warning must still be emitted, on stderr: {}",
r.stderr
);
}
}
#[test]
fn a_reserved_alias_does_not_block_sync_or_upgrade() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
let registry = sb.mind_home.join("sources.json");
let text = std::fs::read_to_string(®istry).expect("read sources.json");
std::fs::write(
®istry,
text.replace("\"alias\": \"acme\"", "\"alias\": \"workflow\""),
)
.unwrap();
for args in [
vec!["sync"],
vec!["upgrade", "--no-sync", "--yes"],
vec!["introspect"],
] {
let r = sb.mind(&args);
assert!(
r.success,
"{args:?} must not be blocked by an advisory prefix warning: {} {}",
r.stdout, r.stderr
);
}
assert!(
sb.claude_home.join("skills/workflow:review").exists(),
"the item must still be linked under the prefix it was installed with"
);
let recall = sb.mind(&["recall"]);
assert!(
recall.stdout.contains("skill:workflow:review"),
"the item must still be reported, as a skill: {}",
recall.stdout
);
}
#[test]
fn the_melded_reserved_prefix_error_has_its_own_json_envelope() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let meld = sb.mind(&["meld", &spec, "--yes"]);
assert!(meld.success, "setup meld must succeed: {}", meld.stderr);
sb.declare_prefix("workflow");
let r = sb.mind(&["--json", "recall"]);
assert!(!r.success, "the run must fail: {} {}", r.stdout, r.stderr);
let doc: serde_json::Value = serde_json::from_str(&r.stdout)
.unwrap_or_else(|e| panic!("stdout must be one JSON document ({e}): {}", r.stdout));
assert_eq!(doc["schema"], 1, "the envelope must be schema 1: {doc}");
assert_eq!(
doc["error"]["kind"], "melded-source-reserved-prefix",
"the melded-source case must be distinguishable from the pre-meld \
`reserved-prefix` one, whose remedy is different: {doc}"
);
let msg = doc["error"]["message"].as_str().unwrap_or_default();
for needle in ["agents", "workflow", "mind unmeld"] {
assert!(
msg.contains(needle),
"the envelope message must name '{needle}': {doc}"
);
}
}
#[test]
fn meld_namespace_with_clean_prefix_still_succeeds() {
let sb = Sandbox::new();
let spec = sb.source_spec();
let r = sb.mind(&["meld", &spec, "--namespace", "acme", "--yes"]);
assert!(
r.success,
"a clean prefix must still be accepted: {}",
r.stderr
);
let recall = sb.mind(&["recall"]).stdout;
assert!(
recall.contains("acme:review"),
"the clean prefix must apply normally: {recall}"
);
}