Files
fermata/tests/fixtures_a4.rs
T
g4borg 168aefd415 🏗️ fermata: redaction-first security model, unified .botsecrets config
Realign fermata around redaction (PostToolUse) as the primary security
layer, with access control (PreToolUse) as supplementary write/bash
protection. Remove botignore.toml — policy rules now live in .botsecrets
[policy] section. Add fermata.toml as an alias for .botsecrets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 01:10:07 +02:00

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(".botsecrets"),
r#"
[policy.read]
patterns = [
"conf/localtestsettings.yaml",
"conf/localsettings.yaml",
"conf/default-secrets.yaml",
".claude/self-reflections/**",
]
[policy.write]
patterns = [
"conf/localtestsettings.yaml",
"conf/localsettings.yaml",
"conf/default-secrets.yaml",
]
[policy.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 `[policy.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(_)));
}