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",
"search",
"link",
"neighbors",
"traverse",
"query",
"merge",
"propose",
"review",
"withdraw",
"verbs",
];
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().expect("VerbRegistry 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 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-16 allowlist (ADR-023 §4). Add `{pack_name}.` prefix."
));
}
}
1 => {
let prefix = verb_name.split('.').next().unwrap_or("");
if prefix != pack_name {
violations.push(format!(
"pack={pack_name:?} verb={verb_name:?}: prefix {prefix:?} does not \
match pack name {pack_name:?} (ADR-023 §4)."
));
}
}
_ => {
violations.push(format!(
"pack={pack_name:?} verb={verb_name:?}: name contains {dot_count} dots; \
sub-variants must use underscore, not nested dots (ADR-023 §4). \
Example: `{pack_name}.recall_embed`, not `{pack_name}.recall.embed`."
));
}
}
}
assert!(
violations.is_empty(),
"Verb namespace contract violations (ADR-023 §4):\n{}",
violations.join("\n")
);
}
#[test]
fn kg_pack_exposes_bare_verbs_only() {
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:?} (ADR-024 + ADR-046)"
);
let dotted: Vec<&&str> = kg_verbs.iter().filter(|v| v.contains('.')).collect();
assert!(
dotted.is_empty(),
"kg pack must not use dotted verb names; found: {dotted:?}"
);
}