use super::*;
use crate::opslog::{OpEntry, Origin, YOG_CONTROL, append};
use serde_json::json;
use std::io::Write;
use tempfile::{TempDir, tempdir};
struct World {
dir: TempDir,
}
impl World {
fn new() -> World {
World {
dir: tempdir().unwrap(),
}
}
fn workspace(&self) -> PathBuf {
self.dir.path().join("workspaces").join("cobalt-gecko")
}
fn state(&self) -> PathBuf {
self.dir.path().join("state").join("yog")
}
fn balls(&self) -> PathBuf {
self.dir.path().join("state").join("balls")
}
fn consult(&self) -> Consult {
Consult {
workspace: self.workspace(),
balls_state_root: self.balls(),
state_root: self.state(),
home: self.dir.path().join("home"),
cwd: None,
policy: crate::control::policy::Policy::default(),
}
}
fn claim(&self, project: &str, id: &str, claimant: &str) {
self.row(
&["bl", "claim", id, "--as", claimant],
project,
Origin::Balls,
);
}
fn answer(&self, words: &[&str]) {
self.row(words, "", Origin::World);
}
fn policy(&self, text: &str) {
let repo = self.workspace().join("repo.git");
std::fs::create_dir_all(&repo).unwrap();
let git = |args: &[&str]| {
let out = crate::git_env::git()
.arg("--git-dir")
.arg(&repo)
.args(args)
.env("GIT_AUTHOR_DATE", "2026-08-04T00:00:00Z")
.env("GIT_COMMITTER_DATE", "2026-08-04T00:00:00Z")
.env("GIT_AUTHOR_NAME", "t")
.env("GIT_AUTHOR_EMAIL", "t@t")
.env("GIT_COMMITTER_NAME", "t")
.env("GIT_COMMITTER_EMAIL", "t@t")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_SYSTEM", "/dev/null")
.output()
.unwrap();
assert!(out.status.success(), "git {args:?}");
String::from_utf8_lossy(&out.stdout).trim().to_owned()
};
git(&["init", "--bare", "-q"]);
let staged = self
.dir
.path()
.join(crate::control::policy::CAPABILITY_YAML);
std::fs::write(&staged, text).unwrap();
let blob = git(&["hash-object", "-w", "--", &staged.to_string_lossy()]);
git(&[
"update-index",
"--add",
"--cacheinfo",
"100644",
&blob,
crate::control::policy::CAPABILITY_YAML,
]);
let tree = git(&["write-tree"]);
let commit = git(&["commit-tree", &tree, "-m", "policy"]);
git(&["update-ref", "refs/heads/config/default", &commit]);
}
fn row(&self, words: &[&str], cwd: &str, origin: Origin) {
append(
&self.state(),
&OpEntry {
ts: "TS".to_owned(),
argv: words.iter().map(|s| (*s).to_owned()).collect(),
cwd: cwd.to_owned(),
exit: 0,
stdout: String::new(),
stderr: String::new(),
origin,
},
)
.unwrap();
}
}
struct Closed;
impl std::io::Write for Closed {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("closed"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn request(name: &str, input: serde_json::Value) -> String {
json!({
"id": "toolu_01",
"name": name,
"input": input,
"role": "worker",
"agent_id": "amber",
})
.to_string()
}
#[test]
fn the_request_parse_requires_every_field_and_ignores_the_rest() {
let r = Request::parse(&request("bash", json!({"command": "ls"}))).unwrap();
assert_eq!(r.id, "toolu_01");
assert_eq!(r.name, "bash");
assert_eq!(r.role, "worker");
assert_eq!(r.agent_id, "amber");
assert_eq!(r.field("command"), "ls");
assert_eq!(r.field("path"), "");
let extra = json!({"id":"i","name":"n","input":{},"role":"r","agent_id":"a","sandbox":true});
assert!(Request::parse(&extra.to_string()).is_some());
for key in ["id", "name", "input", "role", "agent_id"] {
let mut value = json!({"id":"i","name":"n","input":{},"role":"r","agent_id":"a"});
value.as_object_mut().unwrap().remove(key);
assert!(Request::parse(&value.to_string()).is_none(), "{key}");
}
assert!(Request::parse("not json").is_none());
}
#[test]
fn a_pass_carries_no_reason_and_the_other_two_require_one() {
assert_eq!(Verdict::Pass.json(), r#"{"verdict":"pass"}"#);
assert_eq!(
Verdict::Hold("why".to_owned()).json(),
r#"{"reason":"why","verdict":"hold"}"#
);
assert_eq!(
Verdict::Refuse("why".to_owned()).json(),
r#"{"reason":"why","verdict":"refuse"}"#
);
}
#[test]
fn the_writable_root_is_the_agent_worktree_plus_the_claim_this_workspace_made() {
let w = World::new();
w.claim("/dev/proj", "bl-1a2b", "cobalt-gecko");
let entries = opslog::tail(&w.state(), usize::MAX);
let root = w.consult().root("amber", &entries);
assert_eq!(root.cwd, w.workspace().join("agents").join("amber"));
assert!(root.holds(&w.workspace().join("agents/amber/src/x")));
assert!(
root.holds(
&w.balls()
.join("plugins/bl-delivery/dev/proj/bl-1a2b/README.md")
)
);
assert!(!root.holds(Path::new("/etc/hosts")));
w.claim("/dev/proj", "bl-9999", "other-name");
let entries = opslog::tail(&w.state(), usize::MAX);
let root = w.consult().root("amber", &entries);
assert!(!root.holds(&w.balls().join("plugins/bl-delivery/dev/proj/bl-9999/x")));
}
#[test]
fn the_default_table_decides_an_unanswered_invocation() {
let w = World::new();
let c = w.consult();
let verdict = |name: &str, input: serde_json::Value| {
adjudicate(&c, &Request::parse(&request(name, input)).unwrap())
};
assert_eq!(
verdict("bash", json!({"command": "cargo test"})),
Verdict::Pass
);
assert_eq!(verdict("read_file", json!({"path": "x"})), Verdict::Pass);
assert_eq!(
verdict("dispatch", json!({"role": "worker", "goal": "g"})),
Verdict::Pass
);
assert_eq!(
verdict("bash", json!({"command": "curl https://x"})),
Verdict::Pass
);
let Verdict::Refuse(reason) = verdict("bash", json!({"command": "rm -rf /etc"})) else {
panic!("loss is declined in band");
};
assert!(reason.contains("destructive"), "{reason}");
assert!(reason.contains("bash"), "{reason}");
}
#[test]
fn the_operator_s_answers_fold_over_the_table() {
let w = World::new();
w.answer(&[YOG_CONTROL, "answer", "toolu_01", "pass"]);
assert_eq!(
adjudicate(
&w.consult(),
&Request::parse(&request("bash", json!({"command": "rm -rf /etc"}))).unwrap()
),
Verdict::Pass
);
w.answer(&[YOG_CONTROL, "floor", "amber", "raise"]);
let held = adjudicate(
&w.consult(),
&Request::parse(
&json!({"id":"toolu_02","name":"bash","input":{"command":"cargo test"},
"role":"worker","agent_id":"amber"})
.to_string(),
)
.unwrap(),
);
assert!(matches!(held, Verdict::Hold(_)), "{held:?}");
}
#[test]
fn a_reason_never_hands_the_reader_a_section_number() {
let w = World::new();
let verdict = adjudicate(
&w.consult(),
&Request::parse(&request("bash", json!({"command": "rm -rf /etc"}))).unwrap(),
);
let Verdict::Refuse(reason) = verdict else {
panic!("loss is declined in band");
};
assert!(!reason.contains('§'), "{reason}");
}
mod policy;
mod shim;
#[test]
fn the_workspace_is_lernie_s_own_env_var_else_the_cwd_it_runs_in() {
let env = crate::xdg::Env::from_pairs([("LERNIE_CONV_REPO", "/w/ws")]);
assert_eq!(workspace_of(&env), PathBuf::from("/w/ws"));
let env = crate::xdg::Env::from_pairs([("LERNIE_CONV_REPO", "")]);
assert_eq!(workspace_of(&env), std::env::current_dir().unwrap());
}