refactor: the missions root has one definition, not five
`mission_workspace::missions_root()` is now the only place that answers "where
does mission state live". It had fragmented into five: this function, private
`env::var("CLAWMATES_MISSIONS_ROOT")` copies in security_scan, benchmark_runner
and mission_outputs, and a hardcoded `MISSIONS_HOST_ROOT` const in
mission_runtime that read no env at all.
They agree on the deployed value, so nothing has broken. The risk is entirely
in what comes next: anything that sweeps or reclaims this tree has to be
sweeping the same tree the writers use, and five definitions cannot promise
that — a reaper written against one would silently leave the others' directories
behind forever, which is how the orphans got there in the first place.
A source-walk test fails any module outside `mission_workspace` that reads the
env var itself.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e5f097c291
commit
91fbd2dc88
@@ -26,7 +26,17 @@ use std::path::PathBuf;
|
||||
use tokio::process::Command;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) fn missions_root() -> PathBuf {
|
||||
/// The ONE definition of where mission state lives on the docker host.
|
||||
///
|
||||
/// There used to be five: this function, three private copies of the same
|
||||
/// `env::var(...).unwrap_or(...)` in `security_scan`, `benchmark_runner` and
|
||||
/// `mission_outputs`, and a hardcoded `MISSIONS_HOST_ROOT` const in
|
||||
/// `mission_runtime` that read no env at all. They agree on today's
|
||||
/// deployment, which is why nothing had broken — but anything that sweeps or
|
||||
/// reclaims this tree has to be sure it is sweeping the same tree the writers
|
||||
/// use, and five definitions cannot promise that. A GC written against one of
|
||||
/// them would silently miss the others.
|
||||
pub fn missions_root() -> PathBuf {
|
||||
std::env::var("CLAWMATES_MISSIONS_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("/var/lib/clawmates-missions"))
|
||||
@@ -1074,4 +1084,43 @@ mod tests {
|
||||
mark_phase_started(repo);
|
||||
assert!(checkout_in_use(repo));
|
||||
}
|
||||
|
||||
/// Nobody re-derives the missions root.
|
||||
///
|
||||
/// It had fragmented into five definitions — this function, three private
|
||||
/// `env::var("CLAWMATES_MISSIONS_ROOT")` copies, and a hardcoded const
|
||||
/// that read no env at all. They agreed on the deployed value, so nothing
|
||||
/// ever broke; the risk is entirely in what comes next. Anything that
|
||||
/// sweeps, reclaims or reaps this tree has to be sweeping the same tree the
|
||||
/// writers use, and five definitions cannot promise that.
|
||||
#[test]
|
||||
fn the_missions_root_has_exactly_one_definition() {
|
||||
fn walk(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
|
||||
for entry in std::fs::read_dir(dir).expect("readable source dir") {
|
||||
let path = entry.expect("readable entry").path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else if path.extension().is_some_and(|e| e == "rs") {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||
let mut files = Vec::new();
|
||||
walk(&root, &mut files);
|
||||
|
||||
for path in files {
|
||||
if path.ends_with("mission_workspace.rs") {
|
||||
continue;
|
||||
}
|
||||
let src = std::fs::read_to_string(&path).expect("readable source");
|
||||
assert!(
|
||||
!src.contains("var(\"CLAWMATES_MISSIONS_ROOT\")"),
|
||||
"{} reads CLAWMATES_MISSIONS_ROOT itself — call \
|
||||
`mission_workspace::missions_root()` so a reaper and a writer \
|
||||
cannot disagree about which tree they are looking at",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user