use std::collections::BTreeSet;
const ALLOW: &[(&str, &str)] = &[
(
"scc_context::rank::SemanticScorer",
"trait cast for the typed engine.context().surface() call — ranker resolution itself lives in scc_engine::inference",
),
(
"scc_context::surface::render_important",
"pure text formatter for engine-returned entries",
),
(
"scc_context::ContextPack",
"return/transport type of engine-built artifacts (no derivation)",
),
(
"scc_indexer::Config::default_yaml",
"workspace init writes the default config file (file installation, not domain logic)",
),
(
"scc_indexer::adapters::",
"integrations list/doctor/describe read the adapter registry metadata (capability scope display)",
),
(
"scc_indexer::adapters::hindsight::Lesson",
"lessons-add payload type (engine owns storage)",
),
];
#[test]
fn cli_commands_route_through_the_engine() {
let src = include_str!("../src/commands.rs");
let mut violations: Vec<String> = Vec::new();
for (i, line) in src.lines().enumerate() {
let t = line.trim();
if t.starts_with("//") || t.starts_with("///") {
continue;
}
for pat in [
"scc_context::",
"scc_graph::",
"scc_store::",
"scc_indexer::",
] {
if t.contains(pat) && !ALLOW.iter().any(|(a, _)| t.contains(a)) {
violations.push(format!("{}: {t}", i + 1));
}
}
}
assert!(
violations.is_empty(),
"CLI bypasses the engine (spec §89) — move behind scc_engine or allow-list with rationale:\n{}",
violations.join("\n")
);
let mut unused: Vec<&str> = Vec::new();
for (needle, _) in ALLOW {
if !src.contains(needle) {
unused.push(needle);
}
}
assert!(unused.is_empty(), "stale allow-list entries (remove them): {unused:?}");
let _ = BTreeSet::<String>::new();
}