//! 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(_))); }