use std::sync::Arc;
use khive_pack_brain::BrainPack;
use khive_pack_kg::KgPack;
use khive_runtime::{DispatchHook, KhiveRuntime, PackRuntime, VerbRegistryBuilder};
use serde_json::json;
async fn promote_namespace(brain: &BrainPack, rt: &KhiveRuntime, namespace: &str) {
use khive_runtime::{Namespace, VerbRegistryBuilder};
let registry = VerbRegistryBuilder::new()
.build()
.expect("minimal registry for promotion");
let ns = Namespace::parse(namespace).expect("valid namespace string");
let token = rt.authorize(ns).expect("authorize namespace token");
brain
.dispatch("brain.profiles", json!({}), ®istry, &token)
.await
.expect("brain.profiles must succeed to promote namespace via production path");
}
#[tokio::test]
async fn dispatch_hook_fires_on_cold_namespace_no_prior_activation() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let brain = Arc::new(BrainPack::new(rt.clone()));
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
let hook: Arc<dyn DispatchHook> = brain.clone();
builder.with_dispatch_hook(hook);
let registry = builder.build().expect("registry builds");
registry
.dispatch(
"create",
json!({
"kind": "entity",
"name": "ColdHookProbe",
"entity_kind": "concept"
}),
)
.await
.expect("create entity must succeed");
let cold_events = brain.cold_namespace_total_events("local");
assert!(
cold_events.is_some(),
"cold-namespace 'local' pending queue must have been initialised by the hook"
);
assert_eq!(
cold_events.unwrap(),
1,
"cold pending queue must hold 1 signal before ensure_loaded; got {:?}",
cold_events
);
promote_namespace(&brain, &rt, "local").await;
assert!(
brain.cold_namespace_total_events("local").is_none(),
"pending queue for 'local' must be empty after ensure_loaded drains it"
);
let snap = brain.snapshot();
assert_eq!(
snap.balanced_recall.total_events, 1,
"active snapshot total_events must be 1 after cold-hook signal survives promotion; got {}",
snap.balanced_recall.total_events
);
}
#[tokio::test]
async fn dispatch_hook_applies_signals_per_namespace_independently() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let brain = Arc::new(BrainPack::new(rt.clone()));
let build_registry = |ns: &str| {
let rt2 = rt.clone();
let brain2 = brain.clone();
let ns_owned = ns.to_string();
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt2));
builder.with_default_namespace(ns_owned);
let hook: Arc<dyn DispatchHook> = brain2;
builder.with_dispatch_hook(hook);
builder.build().expect("registry builds")
};
let reg_alpha = build_registry("ns-alpha");
let reg_beta = build_registry("ns-beta");
for i in 0..2u32 {
reg_alpha
.dispatch(
"create",
json!({"kind":"entity","name":format!("AlphaE{i}"),"entity_kind":"concept"}),
)
.await
.expect("alpha dispatch");
}
for i in 0..3u32 {
reg_beta
.dispatch(
"create",
json!({"kind":"entity","name":format!("BetaE{i}"),"entity_kind":"concept"}),
)
.await
.expect("beta dispatch");
}
assert_eq!(
brain.cold_namespace_total_events("ns-alpha"),
Some(2),
"ns-alpha pending queue must have exactly 2 signals before promotion"
);
assert_eq!(
brain.cold_namespace_total_events("ns-beta"),
Some(3),
"ns-beta pending queue must have exactly 3 signals before promotion"
);
promote_namespace(&brain, &rt, "ns-alpha").await;
let snap_alpha = brain.snapshot();
assert_eq!(
snap_alpha.balanced_recall.total_events, 2,
"ns-alpha active snapshot must show 2 events after promotion; got {}",
snap_alpha.balanced_recall.total_events
);
assert!(
brain.cold_namespace_total_events("ns-alpha").is_none(),
"ns-alpha pending queue must be empty after promotion"
);
assert_eq!(
brain.cold_namespace_total_events("ns-beta"),
Some(3),
"ns-beta pending queue must remain 3 while ns-alpha is active"
);
promote_namespace(&brain, &rt, "ns-beta").await;
let snap_beta = brain.snapshot();
assert_eq!(
snap_beta.balanced_recall.total_events, 3,
"ns-beta active snapshot must show 3 events after promotion; got {}",
snap_beta.balanced_recall.total_events
);
assert!(
brain.cold_namespace_total_events("ns-beta").is_none(),
"ns-beta pending queue must be empty after promotion"
);
}
#[tokio::test]
async fn brain_pack_hook_does_not_fire_on_unknown_verb() {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let brain = Arc::new(BrainPack::new(rt.clone()));
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt));
let hook: Arc<dyn DispatchHook> = brain.clone();
builder.with_dispatch_hook(hook);
let registry = builder.build().expect("registry builds");
let _ = registry.dispatch("frobnicate_nonexistent", json!({})).await;
assert!(
brain.cold_namespace_total_events("local").is_none(),
"failed dispatch must NOT initialise the cold namespace pending queue"
);
}
#[tokio::test]
async fn cold_hook_signal_applies_on_top_of_persisted_snapshot() {
use khive_runtime::Namespace;
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let brain = Arc::new(BrainPack::new(rt.clone()));
let full_registry = {
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(BrainPack::new(rt.clone()));
builder.build().expect("full registry for step 1")
};
let entity_result = full_registry
.dispatch(
"create",
json!({
"kind": "entity",
"name": "SnapshotProbeTarget",
"entity_kind": "concept"
}),
)
.await
.expect("create entity for feedback target");
let target_id = entity_result["id"]
.as_str()
.expect("created entity must have id")
.to_string();
let empty_registry = VerbRegistryBuilder::new()
.build()
.expect("minimal registry");
let local_ns = Namespace::parse("local").expect("local namespace");
let local_token = rt.authorize(local_ns).expect("local token");
brain
.dispatch("brain.profiles", json!({}), &empty_registry, &local_token)
.await
.expect("promote local namespace before feedback");
for _ in 0..5u32 {
brain
.dispatch(
"brain.feedback",
json!({ "target_id": target_id, "signal": "useful" }),
&empty_registry,
&local_token,
)
.await
.expect("brain.feedback must succeed");
}
let persisted_total = brain.snapshot().balanced_recall.total_events;
assert_eq!(
persisted_total, 5,
"after 5 feedback calls the active snapshot must show 5 total_events; got {persisted_total}"
);
promote_namespace(&brain, &rt, "ns-other").await;
let mut hook_builder = VerbRegistryBuilder::new();
hook_builder.register(KgPack::new(rt.clone()));
hook_builder.with_default_namespace("local".to_string());
let hook_arc: Arc<dyn DispatchHook> = brain.clone();
hook_builder.with_dispatch_hook(hook_arc);
let hook_registry = hook_builder.build().expect("hook registry");
hook_registry
.dispatch(
"create",
json!({"kind":"entity","name":"SavedPathProbe","entity_kind":"concept"}),
)
.await
.expect("kg dispatch must succeed");
brain
.dispatch("brain.profiles", json!({}), &empty_registry, &local_token)
.await
.expect("reload local via production path");
let snap = brain.snapshot();
assert_eq!(
snap.balanced_recall.total_events,
persisted_total + 1,
"active snapshot for 'local' must equal persisted total ({persisted_total}) + \
1 saved-state signal; got {}",
snap.balanced_recall.total_events
);
}