//! Repo invariant: the only place allowed to issue a direct //! `remove_file(... ONELINER_FILENAME)` (or the literal `"oneliner.json"`) //! is `server/src/paths.rs`, which hosts the canonical wrapper //! `delete_oneliner_unless_manual`. Every other call-site MUST go //! through that wrapper so the sticky-Manual rule //! (`OnelinerState::Manual { .. }` survives every server-driven wipe) //! is enforced uniformly. //! //! Static, AST-free scan: a line in a non-whitelisted source file is a //! violation iff it mentions `remove_file` together with either the //! constant `ONELINER_FILENAME` or the literal `"oneliner.json"` — //! pragmatic and good enough to catch the common regression pattern. //! Comment lines (starting with `//` after trim) are ignored so this //! file's own documentation does not self-trip. use std::fs; use std::path::{Path, PathBuf}; /// Files that may legitimately call `remove_file(... oneliner.json)`. /// Paths are relative to `server/src/`. const REMOVE_WHITELIST: &[&str] = &["paths.rs"]; #[test] fn no_direct_oneliner_remove_outside_paths_rs() { let server_src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); let mut rs_files = Vec::new(); collect_rs_files(&server_src, &mut rs_files); assert!( !rs_files.is_empty(), "expected to find at least one .rs file under {}", server_src.display() ); let mut violations: Vec = Vec::new(); for path in &rs_files { let rel = path.strip_prefix(&server_src).unwrap(); let rel_str = rel.to_string_lossy().replace('\\', "/"); if REMOVE_WHITELIST.iter().any(|w| rel_str == *w) { continue; } let contents = match fs::read_to_string(path) { Ok(c) => c, Err(e) => panic!("failed to read {}: {e}", path.display()), }; for (idx, line) in contents.lines().enumerate() { let trimmed = line.trim_start(); if trimmed.starts_with("//") { continue; } let mentions_remove = trimmed.contains("remove_file"); let mentions_target = trimmed.contains("ONELINER_FILENAME") || trimmed.contains("\"oneliner.json\""); if mentions_remove && mentions_target { violations.push(format!( "{}:{}: direct remove_file on oneliner.json — \ route through paths::delete_oneliner_unless_manual to honour \ the sticky-Manual rule. Offending line: {}", rel_str, idx + 1, line.trim() )); } } } assert!( violations.is_empty(), "Sticky-Manual oneliner invariant violated:\n - {}\n\n\ If this is a new legitimate exception, extend REMOVE_WHITELIST in \ tests/oneliner_disk_invariants_test.rs (and document why).", violations.join("\n - ") ); } fn collect_rs_files(dir: &Path, out: &mut Vec) { let entries = match fs::read_dir(dir) { Ok(e) => e, Err(e) => panic!("read_dir {}: {e}", dir.display()), }; for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { collect_rs_files(&path, out); } else if path.extension().and_then(|s| s.to_str()) == Some("rs") { out.push(path); } } }