use khive_pack_brain::BrainPack;
use khive_pack_kg::KgPack;
use khive_pack_knowledge::KnowledgePack;
use khive_runtime::{KhiveRuntime, Namespace, RuntimeConfig, VerbRegistryBuilder};
use serde_json::json;
fn make_rt(brain_profile: Option<String>, with_brain: bool) -> KhiveRuntime {
let packs: Vec<String> = if with_brain {
vec!["kg".into(), "knowledge".into(), "brain".into()]
} else {
vec!["kg".into(), "knowledge".into()]
};
KhiveRuntime::new(RuntimeConfig {
db_path: None,
embedding_model: None,
additional_embedding_models: vec![],
packs,
brain_profile,
..RuntimeConfig::default()
})
.expect("runtime")
}
async fn make_entity(registry: &khive_runtime::VerbRegistry, ns: &str) -> String {
let r = registry
.dispatch(
"create",
json!({
"namespace": ns,
"kind": "concept",
"name": "TestConcept",
"description": "A test concept entity for knowledge feedback tests",
}),
)
.await
.expect("create entity");
r["id"].as_str().expect("entity id from create").to_string()
}
#[tokio::test]
async fn feedback_tier1_explicit_profile_routes_to_brain() {
let rt = make_rt(Some("balanced-recall-v1".into()), false);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
let registry = builder.build().expect("registry");
let atom_id = make_entity(®istry, ns.as_str()).await;
let result = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"target_id": atom_id,
"section_signals": {"overview": "useful"},
}),
)
.await;
assert!(
result.is_err(),
"tier-1 with no brain pack must error (verb not found), got: {result:?}"
);
}
#[tokio::test]
async fn feedback_tier1_explicit_wins_over_bound_profile() {
let rt = make_rt(Some("balanced-recall-v1".into()), true);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
builder.register(BrainPack::new(rt.clone()));
let registry = builder.build().expect("registry");
rt.install_edge_rules(registry.all_edge_rules());
let atom_id = make_entity(®istry, ns.as_str()).await;
registry
.dispatch(
"brain.create_profile",
json!({"namespace": ns.as_str(), "name": "alt-profile", "consumer_kind": "recall"}),
)
.await
.expect("create alt profile");
registry
.dispatch(
"brain.activate",
json!({"namespace": ns.as_str(), "profile_id": "alt-profile"}),
)
.await
.expect("activate alt profile");
registry
.dispatch(
"brain.bind",
json!({"namespace": ns.as_str(), "profile_id": "alt-profile", "consumer_kind": "recall"}),
)
.await
.expect("bind alt profile");
let r = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"target_id": atom_id,
"section_signals": {"overview": "useful"},
}),
)
.await
.expect("feedback ok");
assert_eq!(
r["emitted"], true,
"tier-1 explicit profile must route to brain pack: {r:?}"
);
assert_eq!(
r.get("brain_profile").and_then(|v| v.as_str()),
Some("balanced-recall-v1"),
"knowledge.feedback tier-1 must include the explicit brain_profile in response: {r:?}"
);
let alt = registry
.dispatch(
"brain.profile",
json!({"namespace": ns.as_str(), "profile_id": "alt-profile"}),
)
.await
.expect("brain.profile alt");
assert_eq!(
alt["total_events"].as_u64().unwrap_or(0),
0,
"alt-profile must NOT receive events when tier-1 is active"
);
}
#[tokio::test]
async fn feedback_tier2_namespace_bound_profile_credited() {
let rt = make_rt(None, true);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
builder.register(BrainPack::new(rt.clone()));
let registry = builder.build().expect("registry");
rt.install_edge_rules(registry.all_edge_rules());
let atom_id = make_entity(®istry, ns.as_str()).await;
registry
.dispatch(
"brain.create_profile",
json!({"namespace": ns.as_str(), "name": "ns-bound-recall", "consumer_kind": "recall"}),
)
.await
.expect("create ns-bound profile");
registry
.dispatch(
"brain.activate",
json!({"namespace": ns.as_str(), "profile_id": "ns-bound-recall"}),
)
.await
.expect("activate ns-bound profile");
registry
.dispatch(
"brain.bind",
json!({"namespace": ns.as_str(), "profile_id": "ns-bound-recall", "consumer_kind": "recall"}),
)
.await
.expect("bind ns-bound profile");
let resolve = registry
.dispatch(
"brain.resolve",
json!({"namespace": ns.as_str(), "consumer_kind": "recall"}),
)
.await
.expect("brain.resolve");
assert_eq!(
resolve["resolved_profile_id"], "ns-bound-recall",
"brain.resolve must return the bound profile"
);
assert_eq!(
resolve["matched_binding"], true,
"must be matched_binding=true for an explicit binding"
);
let r = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"target_id": atom_id,
"section_signals": {"overview": "useful"},
}),
)
.await
.expect("feedback ok");
assert_eq!(
r["emitted"], true,
"tier-2 feedback must route to brain pack: {r:?}"
);
let prof = registry
.dispatch(
"brain.profile",
json!({"namespace": ns.as_str(), "profile_id": "ns-bound-recall"}),
)
.await
.expect("brain.profile");
assert_eq!(
prof["total_events"].as_u64().unwrap_or(0),
1,
"ns-bound-recall must receive the feedback event"
);
}
#[tokio::test]
async fn feedback_tier3_global_fallback_no_explicit_binding() {
let rt = make_rt(None, true);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
builder.register(BrainPack::new(rt.clone()));
let registry = builder.build().expect("registry");
rt.install_edge_rules(registry.all_edge_rules());
let atom_id = make_entity(®istry, ns.as_str()).await;
let resolve = registry
.dispatch(
"brain.resolve",
json!({"namespace": ns.as_str(), "consumer_kind": "recall"}),
)
.await
.expect("brain.resolve");
assert_eq!(
resolve["matched_binding"], false,
"no explicit binding: matched_binding must be false (system default)"
);
let r = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"target_id": atom_id,
"section_signals": {"overview": "useful"},
}),
)
.await
.expect("tier-3 feedback must not error");
assert_eq!(r["ok"], true, "tier-3 must return ok=true: {r:?}");
assert!(
r.get("total_events").is_some(),
"tier-3 must include total_events from section_posteriors: {r:?}"
);
assert!(
r.get("emitted").is_none(),
"tier-3 must not route to brain.feedback (no emitted key): {r:?}"
);
}
#[tokio::test]
async fn feedback_tier3_no_brain_pack() {
let rt = make_rt(None, false);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
let registry = builder.build().expect("registry");
rt.install_edge_rules(registry.all_edge_rules());
let atom_id = make_entity(®istry, ns.as_str()).await;
let r = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"target_id": atom_id,
"section_signals": {"overview": "not_useful"},
}),
)
.await
.expect("tier-3 feedback must not error");
assert_eq!(r["ok"], true, "tier-3 must return ok=true: {r:?}");
assert!(
r.get("total_events").is_some(),
"tier-3 must include total_events: {r:?}"
);
}
#[tokio::test]
async fn feedback_tier3_no_target_id() {
let rt = make_rt(None, false);
let ns = Namespace::parse("local").expect("ns");
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(KnowledgePack::new(rt.clone()));
let registry = builder.build().expect("registry");
let r = registry
.dispatch(
"knowledge.feedback",
json!({
"namespace": ns.as_str(),
"section_signals": {"overview": "wrong"},
}),
)
.await
.expect("feedback without target_id must not error");
assert_eq!(r["ok"], true, "ok=true even without target_id: {r:?}");
assert!(
r.get("total_events").is_some(),
"total_events must be present: {r:?}"
);
}