- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
//! Enforcement for the durable state registry - 3
//! (`docs/design/46-stabilization-install-and-onboarding.md` Part VII.2). - 4
//! - 5
//! The registry's whole value is that it is complete. A declaration that - 6
//! drifts from what the code actually writes is worse than none, because - 7
//! `--purge`, backup coverage, and the upgrade gate all trust it. - 8
//! - 9
//! So this drives real work against a private home and fails on any - 10
//! durable file that appeared without a declaration. - 11
- 12
use std::path::{Path, PathBuf}; - 13
- 14
use vak_core::state::{self, Root}; - 15
- 16
/// Every file under `root`, as paths relative to it. - 17
fn files_under(root: &Path) -> Vec<PathBuf> { - 18
let mut found = Vec::new(); - 19
let mut stack = vec![root.to_path_buf()]; - 20
while let Some(dir) = stack.pop() { - 21
let Ok(entries) = std::fs::read_dir(&dir) else { - 22
continue; - 23
}; - 24
for entry in entries.flatten() { - 25
let path = entry.path(); - 26
if path.is_dir() { - 27
stack.push(path); - 28
} else if let Ok(rel) = path.strip_prefix(root) { - 29
found.push(rel.to_path_buf()); - 30
} - 31
} - 32
} - 33
found - 34
} - 35
- 36
/// Files the operating system or a build leaves behind, which are nobody's - 37
/// durable state and would otherwise make this test fail for reasons that - 38
/// have nothing to do with vak. - 39
fn is_incidental(relative: &Path) -> bool { - 40
relative - 41
.components() - 42
.any(|c| matches!(c.as_os_str().to_str(), Some(".DS_Store") | Some(".git"))) - 43
} - 44
- 45
/// Starting a session and recording a security event is enough to touch - 46
/// the ledgers, the sessions tree, and the trust store — the paths a - 47
/// first run actually creates. - 48
#[tokio::test] - 49
async fn a_real_run_writes_only_declared_durable_state() { - 50
let home = vak_config::paths::isolate_home_for_tests(); - 51
let workspace = tempfile::tempdir().expect("workspace"); - 52
- 53
let core = vak_core::Core::new_with_trust(workspace.path().to_path_buf(), true).expect("core"); - 54
let sessions_home = home.join("state-registry-run"); - 55
core.set_sessions_home(sessions_home.clone()); - 56
- 57
// Real durable writes: a session ledger, and an audit entry. - 58
let _session = core.start_session().await.expect("session"); - 59
vak_core::security_events::record( - 60
&sessions_home, - 61
vak_core::security_events::EventKind::ConfigChange, - 62
"registry_probe", - 63
"state-registry test", - 64
None, - 65
); - 66
- 67
let undeclared: Vec<PathBuf> = files_under(&sessions_home) - 68
.into_iter() - 69
.filter(|rel| !is_incidental(rel)) - 70
.filter(|rel| !state::is_declared(Root::Data, rel)) - 71
.collect(); - 72
- 73
assert!( - 74
undeclared.is_empty(), - 75
"durable files were written that the registry does not declare: {undeclared:?}\n\ - 76
Add them to `vak_core::state::REGISTRY` — `--purge`, backup coverage, and the \ - 77
upgrade gate all read it, so an undeclared file is one that silently survives a \ - 78
purge, never gets backed up, and is never checked across an update." - 79
); - 80
} - 81
- 82
/// Seeding writes the Shared layer, which is the other root. - 83
#[test] - 84
fn seeding_writes_only_declared_shared_state() { - 85
let home = vak_config::paths::isolate_home_for_tests(); - 86
let _ = vak_core::seed::seed_shared_capabilities(); - 87
- 88
let shared = vak_config::paths::default_workspace(); - 89
if !shared.exists() { - 90
// `isolate_home_for_tests` points the Shared root inside `home`; - 91
// if nothing was written there, there is nothing to check. - 92
assert!(home.exists()); - 93
return; - 94
} - 95
- 96
let undeclared: Vec<PathBuf> = files_under(&shared) - 97
.into_iter() - 98
.filter(|rel| !is_incidental(rel)) - 99
.filter(|rel| !state::is_declared(Root::Shared, rel)) - 100
.collect(); - 101
- 102
assert!( - 103
undeclared.is_empty(), - 104
"seeding wrote Shared files the registry does not declare: {undeclared:?}" - 105
); - 106
} - 107
- 108
/// A purge must be able to name what it removes, for every root. - 109
/// - 110
/// `--purge` derives its targets from this, so an entry with no disposition - 111
/// would be a file the purge simply never considered. - 112
#[test] - 113
fn every_entry_states_what_a_purge_does_with_it() { - 114
for entry in state::REGISTRY { - 115
// The type makes this total; the assertion is that the registry is - 116
// non-empty and every root is represented, so a whole root cannot - 117
// be forgotten. - 118
let _ = entry.on_purge; - 119
} - 120
for root in [Root::Data, Root::Shared, Root::Cache] { - 121
assert!( - 122
state::entries_for(root).next().is_some(), - 123
"{root:?} has no declared entries — a whole root would escape a purge" - 124
); - 125
} - 126
} - 127
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.