113 lines
3.1 KiB
Rust
113 lines
3.1 KiB
Rust
//! Smoke-test contract from `docs/workpad/brainstorm/fermata.md` Appendix A.4.
|
|
|
|
use dirigent_fermata::core::{Decision, Op, Policy};
|
|
use std::fs;
|
|
use tempfile::TempDir;
|
|
|
|
fn fixture() -> TempDir {
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
fs::write(root.join(".botignore"), ".env\n.env.*\nconf/cert/**\nconf/mitmproxy/**\n").unwrap();
|
|
fs::write(
|
|
root.join("botignore.toml"),
|
|
r#"
|
|
[read]
|
|
patterns = [
|
|
"conf/localtestsettings.yaml",
|
|
"conf/localsettings.yaml",
|
|
"conf/default-secrets.yaml",
|
|
".claude/self-reflections/**",
|
|
]
|
|
|
|
[write]
|
|
patterns = [
|
|
"conf/localtestsettings.yaml",
|
|
"conf/localsettings.yaml",
|
|
"conf/default-secrets.yaml",
|
|
]
|
|
|
|
[bash]
|
|
deny = ["localtestsettings.yaml", "localsettings.yaml", "default-secrets.yaml", ".env"]
|
|
ask = ["rm *", "mv *"]
|
|
allow_prefixes = ["make test"]
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
fs::create_dir_all(root.join("conf")).unwrap();
|
|
fs::create_dir_all(root.join("datatap")).unwrap();
|
|
fs::create_dir_all(root.join(".claude/self-reflections")).unwrap();
|
|
for f in [".env", "conf/localsettings.yaml", "datatap/foo.py", ".claude/self-reflections/x.md"] {
|
|
fs::write(root.join(f), "").unwrap();
|
|
}
|
|
tmp
|
|
}
|
|
|
|
#[test]
|
|
fn read_dot_env_denied() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert!(matches!(p.check(Op::Read, &t.path().join(".env")).unwrap(), Decision::Deny(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn bash_cat_dot_env_denied() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert!(matches!(p.check_command("cat ./.env").unwrap(), Decision::Deny(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn bash_rm_localsettings_denied() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert!(matches!(
|
|
p.check_command("rm ./conf/localsettings.yaml").unwrap(),
|
|
Decision::Deny(_)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn write_localsettings_denied() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert!(matches!(
|
|
p.check(Op::Write, &t.path().join("conf/localsettings.yaml")).unwrap(),
|
|
Decision::Deny(_)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn edit_datatap_foo_py_allowed() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert_eq!(
|
|
p.check(Op::Write, &t.path().join("datatap/foo.py")).unwrap(),
|
|
Decision::Allow
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bash_make_test_allowed() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert_eq!(p.check_command("make test").unwrap(), Decision::Allow);
|
|
}
|
|
|
|
#[test]
|
|
fn bash_rm_somefile_asks() {
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
assert!(matches!(p.check_command("rm somefile").unwrap(), Decision::Ask(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn read_self_reflections_asks() {
|
|
// Note: A.4 has self-reflections under "ask" — current toml schema uses `[read].patterns`
|
|
// for hard reads. This documents the gap; once toml has a `[read].ask`, switch to Ask.
|
|
let t = fixture();
|
|
let p = Policy::load(t.path()).unwrap();
|
|
let d = p.check(Op::Read, &t.path().join(".claude/self-reflections/x.md")).unwrap();
|
|
assert!(matches!(d, Decision::Deny(_)));
|
|
}
|