- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
- 3
use std::fs; - 4
- 5
#[test] - 6
fn learned_rules_persist_reload_and_cannot_shadow_denies() { - 7
let dir = tempfile::tempdir().expect("tempdir"); - 8
fs::create_dir_all(dir.path().join(".vak")).expect("mkdir"); - 9
- 10
vak_config::paths::isolate_home_for_tests(); - 11
let core = vak_core::Core::new_with_trust(dir.path().to_path_buf(), true).expect("core"); - 12
core.learn_allow_rule("bash(cargo *)").expect("learn"); - 13
core.learn_allow_rule("bash(cargo *)").expect("dedupe ok"); - 14
- 15
let file = dir.path().join(".vak/permissions.local.toml"); - 16
let text = fs::read_to_string(&file).expect("file written"); - 17
assert!(text.contains(r#""bash(cargo *)""#)); - 18
- 19
// Reload path: a fresh Core picks up the persisted rule. - 20
vak_config::paths::isolate_home_for_tests(); - 21
let core2 = vak_core::Core::new_with_trust(dir.path().to_path_buf(), true).expect("core2"); - 22
assert_eq!( - 23
core2.extra_allow_snapshot(), - 24
vec!["bash(cargo *)".to_string()] - 25
); - 26
- 27
// Engine behavior: learned allow approves matching calls… - 28
let cfg = vak_core::vak_config::Config::default(); - 29
let engine = vak_core::build_engine_with(&cfg, &core2.extra_allow_snapshot()).expect("engine"); - 30
let d = engine.evaluate( - 31
"bash", - 32
&serde_json::json!({"command": "cargo test"}), - 33
vak_permission::Mode::WorkspaceWrite, - 34
dir.path(), - 35
); - 36
assert!(matches!(d, vak_permission::Decision::Allow)); - 37
- 38
// …but an explicit deny from config still wins by severity. - 39
let mut cfg_deny = cfg.clone(); - 40
cfg_deny.deny = vec!["bash(cargo publish *)".to_string()]; - 41
let engine2 = - 42
vak_core::build_engine_with(&cfg_deny, &["bash(cargo *)".to_string()]).expect("engine2"); - 43
let d2 = engine2.evaluate( - 44
"bash", - 45
&serde_json::json!({"command": "cargo publish --dry-run"}), - 46
vak_permission::Mode::WorkspaceWrite, - 47
dir.path(), - 48
); - 49
assert!(matches!(d2, vak_permission::Decision::Deny { .. })); - 50
// …and the learned allow still approves other cargo invocations under - 51
// that same deny config. - 52
let d3 = engine2.evaluate( - 53
"bash", - 54
&serde_json::json!({"command": "cargo test"}), - 55
vak_permission::Mode::WorkspaceWrite, - 56
dir.path(), - 57
); - 58
assert!(matches!(d3, vak_permission::Decision::Allow)); - 59
- 60
// Untrusted workspaces refuse to persist grants. - 61
let untrusted_dir = tempfile::tempdir().expect("tempdir2"); - 62
vak_config::paths::isolate_home_for_tests(); - 63
let untrusted = - 64
vak_core::Core::new_with_trust(untrusted_dir.path().to_path_buf(), false).expect("u"); - 65
assert!(untrusted.learn_allow_rule("bash(cargo *)").is_err()); - 66
} - 67
- 68
/// Approvals learned at the same moment all persist, the file always parses, - 69
/// and a key the learner does not own survives every rewrite. - 70
#[test] - 71
fn concurrent_learned_rules_all_persist() { - 72
let dir = tempfile::tempdir().expect("tempdir"); - 73
fs::create_dir_all(dir.path().join(".vak")).expect("mkdir"); - 74
let file = dir.path().join(".vak/permissions.local.toml"); - 75
fs::write(&file, "note = \"kept\"\nallow = []\n").expect("seed"); - 76
- 77
vak_config::paths::isolate_home_for_tests(); - 78
let core = vak_core::Core::new_with_trust(dir.path().to_path_buf(), true).expect("core"); - 79
- 80
const ROUNDS: usize = 20; - 81
const WRITERS: usize = 8; - 82
let mut expected = Vec::new(); - 83
for round in 0..ROUNDS { - 84
let specs: Vec<String> = (0..WRITERS) - 85
.map(|writer| format!("bash(tool{round}x{writer} *)")) - 86
.collect(); - 87
let barrier = std::sync::Barrier::new(WRITERS); - 88
std::thread::scope(|scope| { - 89
for spec in &specs { - 90
let (core, barrier) = (&core, &barrier); - 91
scope.spawn(move || { - 92
barrier.wait(); - 93
core.learn_allow_rule(spec).expect("learn"); - 94
}); - 95
} - 96
}); - 97
expected.extend(specs); - 98
- 99
let text = fs::read_to_string(&file).expect("read"); - 100
let document: toml::Table = toml::from_str(&text) - 101
.unwrap_or_else(|error| panic!("round {round}: does not parse: {error}\n{text}")); - 102
assert_eq!( - 103
document.get("note").and_then(toml::Value::as_str), - 104
Some("kept") - 105
); - 106
let allow: Vec<String> = document["allow"] - 107
.as_array() - 108
.expect("allow array") - 109
.iter() - 110
.filter_map(|rule| rule.as_str().map(ToOwned::to_owned)) - 111
.collect(); - 112
let mut sorted = expected.clone(); - 113
sorted.sort(); - 114
let mut got = allow.clone(); - 115
got.sort(); - 116
assert_eq!(got, sorted, "round {round}: a learned rule was lost"); - 117
let mut snapshot = core.extra_allow_snapshot(); - 118
snapshot.sort(); - 119
assert_eq!(snapshot, sorted, "round {round}: engine inputs are stale"); - 120
} - 121
let strays: Vec<_> = fs::read_dir(dir.path().join(".vak")) - 122
.expect("list") - 123
.filter_map(Result::ok) - 124
.map(|entry| entry.file_name().to_string_lossy().into_owned()) - 125
.filter(|name| name.ends_with(".tmp")) - 126
.collect(); - 127
assert!(strays.is_empty(), "temporary files left behind: {strays:?}"); - 128
} - 129
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.