use khive_runtime::pack::{PackRegistry, VerbRegistryBuilder};
use khive_runtime::{KhiveRuntime, RuntimeConfig};
#[allow(unused_imports)]
use khive_pack_brain::BrainPack as _;
#[allow(unused_imports)]
use khive_pack_comm::CommPack as _;
#[allow(unused_imports)]
use khive_pack_gtd::GtdPack as _;
#[allow(unused_imports)]
use khive_pack_kg::KgPack as _;
#[allow(unused_imports)]
use khive_pack_knowledge::KnowledgePack as _;
#[allow(unused_imports)]
use khive_pack_memory::MemoryPack as _;
#[allow(unused_imports)]
use khive_pack_schedule::SchedulePack as _;
const KG_SUBSTRATE_VERBS: &[&str] = &[
"create",
"get",
"list",
"stats",
"update",
"delete",
"restore",
"search",
"link",
"neighbors",
"traverse",
"query",
"merge",
"propose",
"review",
"withdraw",
"verbs",
"context",
"resolve",
"whoami",
"scan",
"db_diagnostics",
];
const KG_SUB_NAMESPACES: &[&str] = &["stream"];
fn violations(handlers: &[(String, String)]) -> Vec<String> {
let mut violations: Vec<String> = Vec::new();
for (pack_name, verb_name) in handlers {
let dot_count = verb_name.chars().filter(|&c| c == '.').count();
match dot_count {
0 => {
if !KG_SUBSTRATE_VERBS.contains(&verb_name.as_str()) {
violations.push(format!(
"pack={pack_name:?} verb={verb_name:?}: bare name is not in the \
kg-substrate allowlist. Add `{pack_name}.` prefix."
));
}
}
1 => {
let prefix = verb_name.split('.').next().unwrap_or("");
let documented_kg_sub_namespace =
pack_name == "kg" && KG_SUB_NAMESPACES.contains(&prefix);
if prefix != pack_name && !documented_kg_sub_namespace {
violations.push(format!(
"pack={pack_name:?} verb={verb_name:?}: prefix {prefix:?} does not \
match pack name {pack_name:?} and is not a documented kg \
sub-namespace ({KG_SUB_NAMESPACES:?}, ADR-174 section 2)."
));
}
}
_ => {
violations.push(format!(
"pack={pack_name:?} verb={verb_name:?}: name contains {dot_count} dots; \
sub-variants must use underscore, not nested dots. \
Example: `{pack_name}.recall_embed`, not `{pack_name}.recall.embed`."
));
}
}
}
violations
}
fn build_full_registry() -> Vec<(String, String)> {
let config = RuntimeConfig {
db_path: None,
default_namespace: khive_runtime::Namespace::parse("verb-contract-test")
.unwrap_or_else(|_| khive_runtime::Namespace::local()),
embedding_model: None,
additional_embedding_models: vec![],
..RuntimeConfig::default()
};
let runtime = KhiveRuntime::new(config).expect("runtime for contract test");
let mut builder = VerbRegistryBuilder::new();
let names: Vec<String> = PackRegistry::discovered_names()
.into_iter()
.map(str::to_string)
.collect();
PackRegistry::register_packs(&names, runtime, &mut builder)
.expect("all inventory packs must register cleanly");
let registry = builder.build_metadata().expect("pack metadata build");
registry
.all_handlers_with_names()
.into_iter()
.map(|(pack_name, handler)| (pack_name.to_string(), handler.name.to_string()))
.collect()
}
#[test]
fn every_non_kg_verb_is_namespaced() {
let handlers = build_full_registry();
let violations = violations(&handlers);
assert!(
violations.is_empty(),
"Verb namespace contract violations:\n{}",
violations.join("\n")
);
}
#[test]
fn a_dotted_kg_verb_outside_the_documented_sub_namespace_still_reddens() {
let live = build_full_registry();
assert!(
violations(&live).is_empty(),
"control: the live registry must pass before decoys are judged"
);
assert!(
live.iter()
.any(|(pack, verb)| pack == "kg" && verb == "stream.append"),
"control: the documented sub-namespace must be present in the live registry"
);
let decoys: Vec<(String, String)> = vec![
("kg".into(), "ledger.append".into()),
("memory".into(), "stream.read".into()),
("kg".into(), "stream.append.now".into()),
];
for decoy in &decoys {
let mut handlers = live.clone();
handlers.push(decoy.clone());
let found = violations(&handlers);
assert_eq!(
found.len(),
1,
"decoy {decoy:?} must produce exactly one violation; got {found:?}"
);
assert!(
found[0].contains(&format!("verb={:?}", decoy.1)),
"the violation must name the decoy verb: {found:?}"
);
}
}
#[test]
fn kg_pack_exposes_bare_verbs_or_a_documented_sub_namespace() {
let handlers = build_full_registry();
let kg_verbs: Vec<&str> = handlers
.iter()
.filter(|(pack, _)| pack == "kg")
.map(|(_, verb)| verb.as_str())
.collect();
let missing: Vec<&&str> = KG_SUBSTRATE_VERBS
.iter()
.filter(|v| !kg_verbs.contains(v))
.collect();
assert!(
missing.is_empty(),
"kg pack is missing substrate verbs: {missing:?}"
);
let undocumented: Vec<&&str> = kg_verbs
.iter()
.filter(|v| v.contains('.'))
.filter(|v| {
let mut parts = v.split('.');
let prefix = parts.next().unwrap_or("");
let rest = parts.next().unwrap_or("");
parts.next().is_some() || rest.is_empty() || !KG_SUB_NAMESPACES.contains(&prefix)
})
.collect();
assert!(
undocumented.is_empty(),
"kg pack may carry dotted names only under {KG_SUB_NAMESPACES:?} (ADR-174 section 2); \
found: {undocumented:?}"
);
}