#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Justification {
OutOfDomain,
Debt(&'static str),
}
#[derive(Debug, Clone, Copy)]
pub struct SyncTool {
pub binary: &'static str,
pub resource: &'static str,
pub justification: Justification,
pub reason: &'static str,
}
pub fn sync_tools() -> &'static [SyncTool] {
&[
SyncTool {
binary: "rclone",
resource: "backup_sync",
justification: Justification::OutOfDomain,
reason: "backup_sync moves the NAS to Google Drive. copia has no cloud \
backends and is not trying to have any, so this is a different \
problem domain rather than a sovereignty gap.",
},
SyncTool {
binary: "curl",
resource: "model",
justification: Justification::OutOfDomain,
reason: "model downloads a weights file from an HTTPS URL. copia synchronises \
between filesystems and SSH hosts; it is not an HTTP client and is not \
trying to be one. Tracked separately as an undeclared dependency \
(forjar GH-224), which is a different problem from sovereignty.",
},
SyncTool {
binary: "rsync",
resource: "nas_archive",
justification: Justification::Debt("paiml/copia#46"),
reason: "nas_archive is local->NAS, which IS copia's domain. It stays on \
rsync only for the verify-before-delete pass: copia 0.2.0 cannot \
express a content-comparing, provably read-only diff, and \
migrating without one would REMOVE the property that protects \
755 GB. Swap it the day copia#46 lands, not before.",
},
]
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
const KNOWN_SYNC_BINARIES: &[&str] =
&["rsync", "rclone", "scp", "sftp", "wget", "curl", "copia"];
const SOVEREIGN: &[&str] = &["copia"];
fn resource_sources() -> Vec<(String, String)> {
fn walk(dir: &Path, out: &mut Vec<(String, String)>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
walk(&p, out);
} else if p.extension().is_some_and(|x| x == "rs") {
let name = p
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
if name.contains("test") {
continue;
}
if name == "sync_tools.rs" {
continue;
}
if let Ok(s) = fs::read_to_string(&p) {
out.push((p.to_string_lossy().to_string(), s));
}
}
}
}
let mut out = Vec::new();
walk(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src/resources")
.as_path(),
&mut out,
);
out
}
fn code_only(src: &str) -> String {
src.lines()
.map(|l| {
let t = l.trim_start();
if t.starts_with("//") || t.starts_with("#") {
""
} else {
l
}
})
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn every_external_sync_binary_is_justified() {
let declared: BTreeSet<&str> = sync_tools().iter().map(|t| t.binary).collect();
let mut found: BTreeSet<String> = BTreeSet::new();
for (path, src) in resource_sources() {
let code = code_only(&src);
for bin in KNOWN_SYNC_BINARIES {
if SOVEREIGN.contains(bin) {
continue;
}
if code.contains(&format!("\"{bin}\""))
|| code.contains(&format!("{bin} "))
&& (code.contains("Command::new") || code.contains("push_str"))
{
let _ = &path;
found.insert((*bin).to_string());
}
}
}
let undeclared: Vec<&String> = found
.iter()
.filter(|b| !declared.contains(b.as_str()))
.collect();
assert!(
undeclared.is_empty(),
"{} external sync binary/binaries are invoked by a resource with no entry in \
src/resources/sync_tools.rs: {:?}\n\
The Sovereign AI Stack ships copia as the rsync replacement. Using something \
else is a decision that must be written down with a reason — either \
OutOfDomain (copia cannot address this) or Debt (copia should, and an issue \
tracks it). Prose in CLAUDE.md is not a gate; this is.",
undeclared.len(),
undeclared
);
}
#[test]
fn the_partition_has_no_stale_entries() {
let all: String = resource_sources()
.iter()
.map(|(_, s)| code_only(s))
.collect::<Vec<_>>()
.join("\n");
for t in sync_tools() {
assert!(
all.contains(t.binary),
"sync_tools declares `{}` for {}, but no resource invokes it any more — \
delete the entry rather than leaving a standing exception",
t.binary,
t.resource
);
}
}
#[test]
fn every_entry_carries_a_real_reason() {
for t in sync_tools() {
assert!(
t.reason.len() > 60,
"{}: the reason must explain the decision to whoever inherits it",
t.binary
);
if let Justification::Debt(issue) = t.justification {
assert!(
issue.contains('#'),
"{}: Debt must cite the issue that closes it, got `{}`",
t.binary,
issue
);
}
}
}
#[test]
fn the_detector_finds_the_invocations_that_are_really_there() {
let sources = resource_sources();
let hits: Vec<&String> = sources
.iter()
.filter(|(_, s)| code_only(s).contains("rsync"))
.map(|(p, _)| p)
.collect();
assert!(
hits.iter().any(|p| p.contains("nas_archive")),
"the scanner found no rsync in nas_archive's non-test source — it is looking \
in the wrong place, and every assertion built on it is vacuous. Hits: {hits:?}"
);
assert!(
!hits.iter().any(|p| p.ends_with("sync_tools.rs")),
"the scanner is reading its own policy file — it would detect the binaries it \
merely NAMES as ones a resource invokes"
);
assert!(
!resource_sources().is_empty(),
"no resource sources were read at all"
);
}
}