pub mod event;
pub mod fold;
pub mod state;
pub mod tunable;
use std::sync::Mutex;
use async_trait::async_trait;
use chrono::Utc;
use serde::Deserialize;
use serde_json::{json, Value};
use khive_fold::{Fold, FoldContext};
use khive_runtime::pack::PackRuntime;
use khive_runtime::{
DispatchHook, EventView, KhiveRuntime, NamespaceToken, RuntimeError, VerbRegistry,
};
use khive_storage::event::{Event, EventFilter};
use khive_storage::types::PageRequest;
use khive_types::{HandlerDef, Pack, VerbCategory, Visibility};
use crate::fold::BalancedRecallFold;
use crate::state::{BrainState, ProfileBinding, ProfileLifecycle, ProfileRecord};
const ENTITY_CACHE_CAPACITY: usize = 10_000;
static BRAIN_HANDLERS: &[HandlerDef] = &[
HandlerDef {
name: "brain.state",
description: "Return current BrainState snapshot for inspection",
visibility: Visibility::Subhandler,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.config",
description: "Return projected config for a named pack parameter",
visibility: Visibility::Subhandler,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.events",
description: "List recent brain-relevant events for debugging",
visibility: Visibility::Subhandler,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.profiles",
description: "List profiles, optionally filtered by lifecycle",
visibility: Visibility::Verb,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.profile",
description: "Profile metadata, latest snapshot, current state summary",
visibility: Visibility::Verb,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.resolve",
description: "Show which profile would serve a caller context",
visibility: Visibility::Verb,
category: VerbCategory::Assertive,
},
HandlerDef {
name: "brain.activate",
description: "Move a profile to Active (start live update loop)",
visibility: Visibility::Verb,
category: VerbCategory::Commissive,
},
HandlerDef {
name: "brain.deactivate",
description: "Move a profile to Inactive (stop live updates, retain state)",
visibility: Visibility::Verb,
category: VerbCategory::Commissive,
},
HandlerDef {
name: "brain.archive",
description: "Move a profile to Archived (read-only, audit-retained)",
visibility: Visibility::Verb,
category: VerbCategory::Declaration,
},
HandlerDef {
name: "brain.reset",
description: "Reset posteriors to priors (preserves event history)",
visibility: Visibility::Verb,
category: VerbCategory::Declaration,
},
HandlerDef {
name: "brain.feedback",
description: "Emit a FeedbackExplicit event into the shared log",
visibility: Visibility::Verb,
category: VerbCategory::Commissive,
},
HandlerDef {
name: "brain.bind",
description: "Write a row in the profile resolution table",
visibility: Visibility::Verb,
category: VerbCategory::Declaration,
},
HandlerDef {
name: "brain.unbind",
description: "Remove rows from the profile resolution table",
visibility: Visibility::Verb,
category: VerbCategory::Declaration,
},
HandlerDef {
name: "brain.emit",
description: "Manually emit a feedback event (deprecated; use brain.feedback)",
visibility: Visibility::Subhandler,
category: VerbCategory::Commissive,
},
];
pub struct BrainPack {
runtime: KhiveRuntime,
state: Mutex<BrainState>,
fold: BalancedRecallFold,
}
impl Pack for BrainPack {
const NAME: &'static str = "brain";
const NOTE_KINDS: &'static [&'static str] = &[];
const ENTITY_KINDS: &'static [&'static str] = &[];
const HANDLERS: &'static [HandlerDef] = BRAIN_HANDLERS;
const REQUIRES: &'static [&'static str] = &["kg"];
}
impl BrainPack {
pub fn new(runtime: KhiveRuntime) -> Self {
let fold = BalancedRecallFold::new(ENTITY_CACHE_CAPACITY);
let state = BrainState::new(ENTITY_CACHE_CAPACITY);
Self {
runtime,
state: Mutex::new(state),
fold,
}
}
pub fn snapshot(&self) -> crate::state::BrainStateSnapshot {
self.state.lock().unwrap().to_snapshot()
}
async fn handle_state(&self, _params: Value) -> Result<Value, RuntimeError> {
let state = self.state.lock().unwrap();
let snapshot = state.to_snapshot();
serde_json::to_value(&snapshot).map_err(|e| RuntimeError::InvalidInput(e.to_string()))
}
async fn handle_config(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct ConfigParams {
parameter: Option<String>,
}
let p: ConfigParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let state = self.state.lock().unwrap();
let br = &state.balanced_recall;
let param_map = [
("recall::relevance_weight", &br.relevance),
("recall::importance_weight", &br.importance),
("recall::temporal_weight", &br.temporal),
];
match p.parameter {
Some(key) => {
let posterior = param_map
.iter()
.find(|(k, _)| *k == key)
.map(|(_, p)| *p)
.ok_or_else(|| {
RuntimeError::NotFound(format!(
"parameter {key:?}; valid: {}",
param_map
.iter()
.map(|(k, _)| *k)
.collect::<Vec<_>>()
.join(", ")
))
})?;
Ok(json!({
"parameter": key,
"mean": posterior.mean(),
"variance": posterior.variance(),
"ess": posterior.effective_sample_size(),
"alpha": posterior.alpha,
"beta": posterior.beta,
}))
}
None => {
let configs: serde_json::Map<String, Value> = param_map
.iter()
.map(|(k, p)| {
(
(*k).to_owned(),
json!({
"mean": p.mean(),
"variance": p.variance(),
"ess": p.effective_sample_size(),
}),
)
})
.collect();
Ok(Value::Object(configs))
}
}
}
async fn handle_events(
&self,
token: &NamespaceToken,
params: Value,
) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct EventsParams {
limit: Option<u32>,
}
let p: EventsParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let limit = p.limit.unwrap_or(20).min(100);
let ns = token.namespace().as_str().to_string();
let store = self.runtime.events(token)?;
let filter = EventFilter {
verbs: vec![
"recall".into(),
"search".into(),
"brain.feedback".into(),
"brain.emit".into(), "get".into(),
"remember".into(),
],
..EventFilter::default()
};
let _ = ns;
let page = store
.query_events(filter, PageRequest { offset: 0, limit })
.await
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let events: Vec<Value> = page
.items
.iter()
.map(|e| {
json!({
"id": e.id.to_string(),
"verb": e.verb,
"outcome": e.outcome,
"target_id": e.target_id.map(|t| t.to_string()),
"duration_us": e.duration_us,
"created_at": e.created_at,
"payload": e.payload,
})
})
.collect();
Ok(json!({
"count": events.len(),
"events": events,
}))
}
async fn handle_profiles(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct ProfilesParams {
lifecycle: Option<String>,
}
let p: ProfilesParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let state = self.state.lock().unwrap();
let filter_lc: Option<ProfileLifecycle> = p
.lifecycle
.as_deref()
.map(|s| serde_json::from_value(Value::String(s.to_owned())))
.transpose()
.map_err(|e| RuntimeError::InvalidInput(format!("invalid lifecycle: {e}")))?;
let profiles: Vec<&ProfileRecord> = state
.profiles
.values()
.filter(|r| filter_lc.as_ref().is_none_or(|lc| &r.lifecycle == lc))
.collect();
let items: Vec<Value> = profiles
.iter()
.map(|r| {
json!({
"id": r.id,
"description": r.description,
"consumer_kind": r.consumer_kind,
"state_class": r.state_class,
"lifecycle": r.lifecycle,
"total_events": r.total_events,
"exploration_epoch": r.exploration_epoch,
"created_at": r.created_at,
})
})
.collect();
Ok(json!({ "count": items.len(), "profiles": items }))
}
async fn handle_profile(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct ProfileParams {
id: String,
}
let p: ProfileParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let state = self.state.lock().unwrap();
let record = state
.profiles
.get(&p.id)
.ok_or_else(|| RuntimeError::NotFound(format!("profile {:?}", p.id)))?;
Ok(json!({
"id": record.id,
"description": record.description,
"consumer_kind": record.consumer_kind,
"state_class": record.state_class,
"lifecycle": record.lifecycle,
"total_events": record.total_events,
"exploration_epoch": record.exploration_epoch,
"created_at": record.created_at,
"state_snapshot": record.state_snapshot,
}))
}
async fn handle_resolve(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct ResolveParams {
actor: Option<String>,
namespace: Option<String>,
consumer_kind: String,
}
let p: ResolveParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let state = self.state.lock().unwrap();
match state.resolve(p.actor.as_deref(), p.namespace.as_deref(), &p.consumer_kind) {
Some(record) => Ok(json!({
"resolved_profile_id": record.id,
"lifecycle": record.lifecycle,
"consumer_kind": record.consumer_kind,
})),
None => Err(RuntimeError::NotFound(format!(
"no profile resolved for consumer_kind={:?}",
p.consumer_kind
))),
}
}
async fn handle_activate(&self, params: Value) -> Result<Value, RuntimeError> {
self.set_lifecycle(params, ProfileLifecycle::Active).await
}
async fn handle_deactivate(&self, params: Value) -> Result<Value, RuntimeError> {
self.set_lifecycle(params, ProfileLifecycle::Inactive).await
}
async fn handle_archive(&self, params: Value) -> Result<Value, RuntimeError> {
self.set_lifecycle(params, ProfileLifecycle::Archived).await
}
async fn set_lifecycle(
&self,
params: Value,
lifecycle: ProfileLifecycle,
) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct LifecycleParams {
profile_id: String,
}
let p: LifecycleParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let mut state = self.state.lock().unwrap();
let record = state
.profiles
.get_mut(&p.profile_id)
.ok_or_else(|| RuntimeError::NotFound(format!("profile {:?}", p.profile_id)))?;
record.lifecycle = lifecycle.clone();
Ok(json!({
"profile_id": p.profile_id,
"lifecycle": lifecycle,
}))
}
async fn handle_reset(&self, _params: Value) -> Result<Value, RuntimeError> {
let mut state = self.state.lock().unwrap();
state.reset_posteriors();
Ok(json!({
"reset": true,
"exploration_epoch": state.balanced_recall.exploration_epoch,
}))
}
async fn handle_feedback(
&self,
token: &NamespaceToken,
params: Value,
) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct FeedbackParams {
target_id: String,
signal: String,
served_by_profile_id: Option<String>,
}
let p: FeedbackParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let target: uuid::Uuid = p
.target_id
.parse()
.map_err(|e| RuntimeError::InvalidInput(format!("invalid target_id: {e}")))?;
let signal = match p.signal.as_str() {
"useful" => "useful",
"not_useful" => "not_useful",
"wrong" => "wrong",
other => {
return Err(RuntimeError::InvalidInput(format!(
"unknown signal {other:?}; valid: useful | not_useful | wrong"
)))
}
};
let mut data = json!({"signal": signal});
if let Some(ref profile_id) = p.served_by_profile_id {
data["served_by_profile_id"] = json!(profile_id);
}
let event = Event::new(
token.namespace().as_str().to_string(),
"brain.feedback",
khive_types::EventKind::FeedbackExplicit,
khive_types::SubstrateKind::Event,
"brain",
)
.with_target(target)
.with_payload(data);
let store = self.runtime.events(token)?;
store
.append_event(event.clone())
.await
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let ctx = FoldContext::new();
let mut state = self.state.lock().unwrap();
let current_recall = std::mem::replace(
&mut state.balanced_recall,
crate::state::BalancedRecallState::new(0),
);
let updated = self.fold.reduce(current_recall, &event, &ctx);
state.balanced_recall = updated;
let total_ev = state.balanced_recall.total_events;
let snap_val = serde_json::to_value(state.balanced_recall.to_snapshot()).ok();
if let Some(record) = state.profiles.get_mut("balanced-recall-v1") {
record.total_events = total_ev;
record.state_snapshot = snap_val;
}
Ok(json!({
"emitted": true,
"event_id": event.id.to_string(),
"verb": "brain.feedback",
"signal": signal,
"target_id": target.to_string(),
}))
}
async fn handle_emit(
&self,
token: &NamespaceToken,
params: Value,
) -> Result<Value, RuntimeError> {
self.handle_feedback(token, params).await
}
async fn handle_bind(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct BindParams {
profile_id: String,
actor: Option<String>,
namespace: Option<String>,
consumer_kind: Option<String>,
priority: Option<i32>,
}
let p: BindParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let mut state = self.state.lock().unwrap();
if !state.profiles.contains_key(&p.profile_id) {
return Err(RuntimeError::NotFound(format!(
"profile {:?}",
p.profile_id
)));
}
let actor = p.actor.unwrap_or_else(|| "*".into());
let namespace = p.namespace.unwrap_or_else(|| "*".into());
let consumer_kind = p.consumer_kind.unwrap_or_else(|| "*".into());
for (field, val) in [
("actor", &actor),
("namespace", &namespace),
("consumer_kind", &consumer_kind),
] {
if val.as_str() != "*" && val.contains('*') {
return Err(RuntimeError::InvalidInput(format!(
"{field}: '*' is reserved as the wildcard sentinel and cannot appear inside a real value"
)));
}
}
state.bindings.retain(|b| {
!(b.actor == actor && b.namespace == namespace && b.consumer_kind == consumer_kind)
});
state.bindings.push(ProfileBinding {
actor: actor.clone(),
namespace: namespace.clone(),
consumer_kind: consumer_kind.clone(),
profile_id: p.profile_id.clone(),
priority: p.priority.unwrap_or(0),
created_at: Utc::now(),
});
Ok(json!({
"bound": true,
"profile_id": p.profile_id,
"actor": actor,
"namespace": namespace,
"consumer_kind": consumer_kind,
}))
}
async fn handle_unbind(&self, params: Value) -> Result<Value, RuntimeError> {
#[derive(Deserialize)]
struct UnbindParams {
profile_id: Option<String>,
actor: Option<String>,
namespace: Option<String>,
consumer_kind: Option<String>,
}
let p: UnbindParams = serde_json::from_value(params)
.map_err(|e| RuntimeError::InvalidInput(e.to_string()))?;
let mut state = self.state.lock().unwrap();
let before = state.bindings.len();
state.bindings.retain(|b| {
let pid_match = p.profile_id.as_ref().is_none_or(|id| &b.profile_id == id);
let actor_match = p.actor.as_ref().is_none_or(|a| &b.actor == a);
let ns_match = p.namespace.as_ref().is_none_or(|n| &b.namespace == n);
let kind_match = p
.consumer_kind
.as_ref()
.is_none_or(|k| &b.consumer_kind == k);
!(pid_match && actor_match && ns_match && kind_match)
});
let removed = before - state.bindings.len();
Ok(json!({ "unbound": removed }))
}
}
struct BrainPackFactory;
impl khive_runtime::PackFactory for BrainPackFactory {
fn name(&self) -> &'static str {
"brain"
}
fn requires(&self) -> &'static [&'static str] {
&["kg"]
}
fn create(&self, runtime: KhiveRuntime) -> Box<dyn PackRuntime> {
Box::new(BrainPack::new(runtime))
}
}
inventory::submit! { khive_runtime::PackRegistration(&BrainPackFactory) }
#[async_trait]
impl PackRuntime for BrainPack {
fn name(&self) -> &str {
<BrainPack as Pack>::NAME
}
fn note_kinds(&self) -> &'static [&'static str] {
<BrainPack as Pack>::NOTE_KINDS
}
fn entity_kinds(&self) -> &'static [&'static str] {
<BrainPack as Pack>::ENTITY_KINDS
}
fn handlers(&self) -> &'static [HandlerDef] {
BRAIN_HANDLERS
}
fn requires(&self) -> &'static [&'static str] {
<BrainPack as Pack>::REQUIRES
}
async fn dispatch(
&self,
verb: &str,
params: Value,
_registry: &VerbRegistry,
token: &NamespaceToken,
) -> Result<Value, RuntimeError> {
match verb {
"brain.state" => self.handle_state(params).await,
"brain.config" => self.handle_config(params).await,
"brain.events" => self.handle_events(token, params).await,
"brain.profiles" => self.handle_profiles(params).await,
"brain.profile" => self.handle_profile(params).await,
"brain.resolve" => self.handle_resolve(params).await,
"brain.activate" => self.handle_activate(params).await,
"brain.deactivate" => self.handle_deactivate(params).await,
"brain.archive" => self.handle_archive(params).await,
"brain.reset" => self.handle_reset(params).await,
"brain.feedback" => self.handle_feedback(token, params).await,
"brain.bind" => self.handle_bind(params).await,
"brain.unbind" => self.handle_unbind(params).await,
"brain.emit" => self.handle_emit(token, params).await,
_ => Err(RuntimeError::InvalidInput(format!(
"brain pack does not handle verb {verb:?}"
))),
}
}
}
#[async_trait]
impl DispatchHook for BrainPack {
async fn on_dispatch(&self, view: &EventView) {
let ctx = FoldContext::new();
let mut state = self.state.lock().unwrap();
let current = std::mem::replace(
&mut state.balanced_recall,
crate::state::BalancedRecallState::new(0),
);
let updated = self.fold.reduce(current, &view.event, &ctx);
state.balanced_recall = updated;
}
}
#[cfg(test)]
mod tests {
use super::*;
use khive_runtime::{Namespace, VerbRegistryBuilder};
use serde_json::json;
fn make_pack() -> (BrainPack, KhiveRuntime) {
let rt = KhiveRuntime::memory().expect("in-memory runtime");
let pack = BrainPack::new(rt.clone());
(pack, rt)
}
fn empty_registry() -> VerbRegistry {
VerbRegistryBuilder::new()
.build()
.expect("empty registry builds successfully")
}
#[tokio::test]
async fn dispatch_unknown_verb_returns_invalid_input() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let err = pack
.dispatch(
"brain.unknown",
json!({}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap_err();
if let RuntimeError::InvalidInput(msg) = &err {
assert!(
msg.contains("brain.unknown"),
"expected verb name in error: {msg}"
);
} else {
panic!("expected InvalidInput, got {err:?}");
}
}
#[tokio::test]
async fn dispatch_reset_returns_true_and_increments_epoch() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.reset",
json!({}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert_eq!(result["reset"], json!(true));
assert_eq!(result["exploration_epoch"], json!(1u64));
}
#[tokio::test]
async fn dispatch_feedback_invalid_signal_returns_invalid_input() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let target = "00000000-0000-0000-0000-000000000001";
let err = pack
.dispatch(
"brain.feedback",
json!({"target_id": target, "signal": "bad_signal"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap_err();
if let RuntimeError::InvalidInput(msg) = &err {
assert!(
msg.contains("bad_signal"),
"expected signal name in error: {msg}"
);
assert!(
msg.contains("valid"),
"expected hint about valid values: {msg}"
);
} else {
panic!("expected InvalidInput, got {err:?}");
}
}
#[tokio::test]
async fn dispatch_state_returns_snapshot_fields() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.state",
json!({}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert!(result.get("profiles").is_some(), "missing profiles");
assert!(
result.get("balanced_recall").is_some(),
"missing balanced_recall"
);
assert!(result.get("bindings").is_some(), "missing bindings");
}
#[tokio::test]
async fn dispatch_profiles_returns_default_profile() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.profiles",
json!({}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
let profiles = result["profiles"].as_array().unwrap();
assert!(!profiles.is_empty(), "expected at least one profile");
assert_eq!(profiles[0]["id"], json!("balanced-recall-v1"));
}
#[tokio::test]
async fn dispatch_profiles_filtered_by_lifecycle() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.profiles",
json!({"lifecycle": "active"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
let profiles = result["profiles"].as_array().unwrap();
for p in profiles {
assert_eq!(p["lifecycle"], json!("active"));
}
}
#[tokio::test]
async fn dispatch_profile_returns_profile_details() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.profile",
json!({"id": "balanced-recall-v1"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert_eq!(result["id"], json!("balanced-recall-v1"));
assert_eq!(result["state_class"], json!("Bayesian"));
assert_eq!(result["consumer_kind"], json!("recall"));
}
#[tokio::test]
async fn dispatch_profile_not_found_returns_not_found() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let err = pack
.dispatch(
"brain.profile",
json!({"id": "nonexistent"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap_err();
assert!(matches!(err, RuntimeError::NotFound(_)));
}
#[tokio::test]
async fn dispatch_resolve_returns_default_profile_for_recall() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.resolve",
json!({"consumer_kind": "recall"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert_eq!(result["resolved_profile_id"], json!("balanced-recall-v1"));
}
#[tokio::test]
async fn dispatch_activate_and_deactivate_profile() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let token = rt.authorize(Namespace::local());
let result = pack
.dispatch(
"brain.deactivate",
json!({"profile_id": "balanced-recall-v1"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(result["lifecycle"], json!("inactive"));
let state = pack
.dispatch(
"brain.profile",
json!({"id": "balanced-recall-v1"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(state["lifecycle"], json!("inactive"));
let result = pack
.dispatch(
"brain.activate",
json!({"profile_id": "balanced-recall-v1"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(result["lifecycle"], json!("active"));
}
#[tokio::test]
async fn dispatch_archive_profile() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.archive",
json!({"profile_id": "balanced-recall-v1"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert_eq!(result["lifecycle"], json!("archived"));
}
#[tokio::test]
async fn dispatch_activate_nonexistent_profile_returns_not_found() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let err = pack
.dispatch(
"brain.activate",
json!({"profile_id": "ghost-profile"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap_err();
assert!(matches!(err, RuntimeError::NotFound(_)));
}
#[tokio::test]
async fn dispatch_bind_and_resolve_explicit_binding() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let token = rt.authorize(Namespace::local());
let result = pack
.dispatch(
"brain.bind",
json!({
"profile_id": "balanced-recall-v1",
"actor": "agent-x",
"consumer_kind": "recall"
}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(result["bound"], json!(true));
assert_eq!(result["actor"], json!("agent-x"));
let resolved = pack
.dispatch(
"brain.resolve",
json!({"actor": "agent-x", "consumer_kind": "recall"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(resolved["resolved_profile_id"], json!("balanced-recall-v1"));
}
#[tokio::test]
async fn dispatch_bind_nonexistent_profile_returns_not_found() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let err = pack
.dispatch(
"brain.bind",
json!({"profile_id": "ghost", "consumer_kind": "recall"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap_err();
assert!(matches!(err, RuntimeError::NotFound(_)));
}
#[tokio::test]
async fn dispatch_unbind_removes_binding() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let token = rt.authorize(Namespace::local());
pack.dispatch(
"brain.bind",
json!({"profile_id": "balanced-recall-v1", "actor": "agent-y", "consumer_kind": "recall"}),
®istry,
&token,
)
.await
.unwrap();
let result = pack
.dispatch(
"brain.unbind",
json!({"actor": "agent-y"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(result["unbound"], json!(1u64));
}
#[tokio::test]
async fn dispatch_unbind_uses_and_not_or() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let token = rt.authorize(Namespace::local());
pack.dispatch(
"brain.bind",
json!({"profile_id": "balanced-recall-v1", "namespace": "ns-a", "consumer_kind": "recall"}),
®istry,
&token,
)
.await
.unwrap();
pack.dispatch(
"brain.bind",
json!({"profile_id": "balanced-recall-v1", "namespace": "ns-b", "consumer_kind": "recall"}),
®istry,
&token,
)
.await
.unwrap();
let result = pack
.dispatch(
"brain.unbind",
json!({"namespace": "ns-a", "profile_id": "balanced-recall-v1"}),
®istry,
&token,
)
.await
.unwrap();
assert_eq!(
result["unbound"],
json!(1u64),
"should remove exactly one binding"
);
let state = pack.state.lock().unwrap();
let remaining: Vec<_> = state
.bindings
.iter()
.filter(|b| b.namespace == "ns-b")
.collect();
assert_eq!(remaining.len(), 1, "ns-b binding must survive the unbind");
}
#[tokio::test]
async fn dispatch_config_all_parameters() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.config",
json!({}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
let obj = result.as_object().unwrap();
assert!(obj.contains_key("recall::relevance_weight"));
assert!(obj.contains_key("recall::importance_weight"));
assert!(obj.contains_key("recall::temporal_weight"));
}
#[tokio::test]
async fn dispatch_config_single_parameter() {
let (pack, rt) = make_pack();
let registry = empty_registry();
let result = pack
.dispatch(
"brain.config",
json!({"parameter": "recall::relevance_weight"}),
®istry,
&rt.authorize(Namespace::local()),
)
.await
.unwrap();
assert_eq!(result["parameter"], json!("recall::relevance_weight"));
let mean = result["mean"].as_f64().unwrap();
assert!((mean - 0.7).abs() < 1e-6);
}
}