use std::fs;
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
crate_dir
.parent() .and_then(Path::parent) .expect("crate dir has a repo-root grandparent")
.to_path_buf()
}
fn repo_files(root: &Path) -> Vec<PathBuf> {
fn is_skipped_dir(name: &str) -> bool {
matches!(name, "target" | ".git")
}
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = match fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let path = entry.path();
let file_type = match entry.file_type() {
Ok(t) => t,
Err(_) => continue,
};
if file_type.is_dir() {
let name = entry.file_name();
if !is_skipped_dir(&name.to_string_lossy()) {
stack.push(path);
}
} else if file_type.is_file() {
out.push(path);
}
}
}
out
}
fn invokes_go_tool(text: &str) -> bool {
const GO_SUBCOMMANDS: &[&str] = &["build", "test", "mod", "vet", "run", "install", "fmt"];
text.lines().any(|line| {
let tokens: Vec<&str> = line.split_whitespace().collect();
tokens.windows(2).any(|w| {
let cmd = w[0].trim_start_matches(['@', '-']);
cmd == "go" && GO_SUBCOMMANDS.contains(&w[1])
})
})
}
#[test]
fn invokes_go_tool_distinguishes_go_from_cargo() {
assert!(!invokes_go_tool("cargo build --workspace"));
assert!(!invokes_go_tool("\tcargo test --workspace"));
assert!(!invokes_go_tool("# golang is not used here"));
assert!(invokes_go_tool("\tgo build ./..."));
assert!(invokes_go_tool(" run: go test ./..."));
}
#[test]
fn no_go_sources_or_modules_remain() {
let root = repo_root();
let offenders: Vec<String> = repo_files(&root)
.into_iter()
.filter(|p| {
let name = p.file_name().map(|n| n.to_string_lossy().into_owned());
let is_go_src = p.extension().map(|e| e == "go").unwrap_or(false);
let is_go_mod = matches!(name.as_deref(), Some("go.mod") | Some("go.sum"));
is_go_src || is_go_mod
})
.map(|p| {
p.strip_prefix(&root)
.unwrap_or(&p)
.to_string_lossy()
.into_owned()
})
.collect();
assert!(
offenders.is_empty(),
"Go toolkit must be fully removed (all-Rust hard rule). Found Go files: {offenders:?}"
);
}
#[test]
fn makefile_drives_the_rust_workspace_not_go() {
let root = repo_root();
let makefile = fs::read_to_string(root.join("Makefile")).expect("Makefile must exist");
assert!(
makefile.contains("cargo build --workspace"),
"Makefile `build` must run `cargo build --workspace`, not the Go toolkit"
);
assert!(
makefile.contains("cargo test --workspace"),
"Makefile `test` must run `cargo test --workspace`"
);
assert!(
!invokes_go_tool(&makefile),
"Makefile must not invoke the `go` tool (`go build`/`go test`/…) — all-Rust hard rule"
);
for forbidden in FORBIDDEN_BINARY_NAMES {
assert!(
!makefile.contains(forbidden),
"Makefile must not reference the forbidden binary `{forbidden}` \
(one-binary `dbmd` hard rule)"
);
}
}
#[test]
fn ci_actually_builds_and_tests_the_rust_workspace() {
let root = repo_root();
let ci = fs::read_to_string(root.join(".github/workflows/test.yml"))
.expect(".github/workflows/test.yml must exist");
assert!(
ci.contains("cargo build --workspace"),
".github/workflows/test.yml must build the whole Rust workspace \
(`cargo build --workspace`); CI testing only part of (or none of) the \
workspace is the exact regression this guard exists to catch"
);
assert!(
ci.contains("cargo test --workspace"),
".github/workflows/test.yml must run `cargo test --workspace` so the \
dbmd-core + dbmd-cli tests (including the corpus e2e suites) actually \
run in CI — not just locally"
);
assert!(
!invokes_go_tool(&ci),
".github/workflows/test.yml must not invoke the `go` tool \
(`go build`/`go test`/…) — all-Rust hard rule"
);
}
const FORBIDDEN_BINARY_NAMES: &[&str] = &[
"dbmd-curator",
"dbmd-file-watcher",
"dbmd-email-imap",
"dbmd-mcp-fetcher",
"dbmd-watch",
"dbmd-imap",
"dbmd-mcp",
];
#[test]
fn build_surface_has_no_forbidden_binaries_or_ai_keys() {
let root = repo_root();
let build_surface = [
"Makefile",
".github/workflows/test.yml",
"CONTRIBUTING.md",
"SECURITY.md",
"Cargo.toml",
"crates/dbmd-cli/Cargo.toml",
"crates/dbmd-core/Cargo.toml",
];
for rel in build_surface {
let path = root.join(rel);
let contents = match fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
for forbidden in FORBIDDEN_BINARY_NAMES {
assert!(
!contents.contains(forbidden),
"{rel} references forbidden binary `{forbidden}` \
(one-binary `dbmd`, no curator/ingester binaries)"
);
}
assert!(
!invokes_go_tool(&contents),
"{rel} invokes the `go` tool (`go build`/`go test`/…) — all-Rust hard rule"
);
for key in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"] {
assert!(
!contents.contains(key),
"{rel} wires up `{key}` — `dbmd` ships zero AI and handles no API keys"
);
}
}
assert!(
!root.join("docker-compose.yml").exists(),
"docker-compose.yml ran the forbidden curator+ingester binaries with \
provider API keys; it must stay removed"
);
}