1use std::sync::Mutex;
4
5use khive_runtime::{KhiveRuntime, NamespaceToken, RuntimeError};
6use khive_types::{HandlerDef, Pack};
7
8use khive_brain_core::BrainState;
9
10use crate::handlers::BRAIN_HANDLERS;
11use crate::persist;
12
13pub const ENTITY_CACHE_CAPACITY: usize = 10_000;
15
16#[cfg(test)]
20pub(crate) struct DispatchHook {
21 pub reached_tx: tokio::sync::oneshot::Sender<()>,
22 pub proceed_rx: tokio::sync::oneshot::Receiver<()>,
23}
24
25#[cfg(test)]
26pub(crate) static DISPATCH_INTERLEAVE_HOOK: std::sync::Mutex<Option<DispatchHook>> =
27 std::sync::Mutex::new(None);
28
29#[cfg(test)]
30pub(crate) fn set_dispatch_interleave_hook(hook: DispatchHook) {
31 *DISPATCH_INTERLEAVE_HOOK.lock().unwrap() = Some(hook);
32}
33
34#[cfg(test)]
35pub(crate) fn clear_dispatch_interleave_hook() {
36 *DISPATCH_INTERLEAVE_HOOK.lock().unwrap() = None;
37}
38
39pub(crate) fn sync_balanced_recall_record(state: &mut BrainState) {
41 let total_ev = state.balanced_recall.total_events;
42 let snap_val = serde_json::to_value(state.balanced_recall.to_snapshot()).ok();
43 if let Some(record) = state.profiles.get_mut("balanced-recall-v1") {
44 record.total_events = total_ev;
45 record.state_snapshot = snap_val;
46 }
47}
48
49pub struct BrainPack {
51 pub(crate) runtime: KhiveRuntime,
52 pub(crate) state: Mutex<BrainState>,
54 pub(crate) persistence: Mutex<persist::PersistenceTracker>,
56 pub(crate) dispatch_gate: tokio::sync::Mutex<()>,
64}
65
66impl Pack for BrainPack {
67 const NAME: &'static str = "brain";
68 const NOTE_KINDS: &'static [&'static str] = &[];
69 const ENTITY_KINDS: &'static [&'static str] = &[];
70 const HANDLERS: &'static [HandlerDef] = BRAIN_HANDLERS;
71 const REQUIRES: &'static [&'static str] = &["kg"];
72}
73
74impl BrainPack {
75 pub fn new(runtime: KhiveRuntime) -> Self {
77 let state = BrainState::new(ENTITY_CACHE_CAPACITY);
78 Self {
79 runtime,
80 state: Mutex::new(state),
81 persistence: Mutex::new(persist::PersistenceTracker::new()),
82 dispatch_gate: tokio::sync::Mutex::new(()),
83 }
84 }
85
86 #[cfg(test)]
87 pub fn activate_namespace_for_test(&self, namespace: &str) {
88 self.persistence
89 .lock()
90 .unwrap()
91 .mark_loaded(namespace.into());
92 }
93
94 pub(crate) async fn ensure_loaded(&self, token: &NamespaceToken) -> Result<(), RuntimeError> {
95 persist::ensure_loaded(
96 &self.runtime,
97 token,
98 &self.persistence,
99 &self.state,
100 ENTITY_CACHE_CAPACITY,
101 )
102 .await
103 }
104
105 pub fn snapshot(&self) -> khive_brain_core::BrainStateSnapshot {
107 self.state.lock().unwrap().to_snapshot()
108 }
109
110 #[cfg(test)]
117 pub fn cold_namespace_total_events(&self, namespace: &str) -> Option<u64> {
118 self.persistence.lock().unwrap().total_events_for(namespace)
119 }
120}
121
122struct BrainPackFactory;
123
124impl khive_runtime::PackFactory for BrainPackFactory {
125 fn name(&self) -> &'static str {
126 "brain"
127 }
128
129 fn requires(&self) -> &'static [&'static str] {
130 &["kg"]
131 }
132
133 fn create(&self, runtime: KhiveRuntime) -> Box<dyn khive_runtime::pack::PackRuntime> {
134 Box::new(BrainPack::new(runtime))
135 }
136
137 fn create_install(&self, runtime: KhiveRuntime) -> khive_runtime::PackInstall {
141 let brain = std::sync::Arc::new(BrainPack::new(runtime));
142 khive_runtime::PackInstall {
143 runtime: Box::new(BrainPackRuntime(std::sync::Arc::clone(&brain))),
144 resolver: None,
145 dispatch_hook: Some(brain),
146 }
147 }
148}
149
150struct BrainPackRuntime(std::sync::Arc<BrainPack>);
154
155#[async_trait::async_trait]
156impl khive_runtime::pack::PackRuntime for BrainPackRuntime {
157 fn name(&self) -> &str {
158 self.0.name()
159 }
160
161 fn note_kinds(&self) -> &'static [&'static str] {
162 self.0.note_kinds()
163 }
164
165 fn entity_kinds(&self) -> &'static [&'static str] {
166 self.0.entity_kinds()
167 }
168
169 fn handlers(&self) -> &'static [khive_runtime::HandlerDef] {
170 self.0.handlers()
171 }
172
173 fn edge_rules(&self) -> &'static [khive_types::EdgeEndpointRule] {
174 self.0.edge_rules()
175 }
176
177 fn requires(&self) -> &'static [&'static str] {
178 self.0.requires()
179 }
180
181 fn note_kind_specs(&self) -> &'static [khive_runtime::NoteKindSpec] {
182 self.0.note_kind_specs()
183 }
184
185 fn kind_hook(&self, kind: &str) -> Option<std::sync::Arc<dyn khive_runtime::KindHook>> {
186 self.0.kind_hook(kind)
187 }
188
189 fn schema_plan(&self) -> khive_runtime::SchemaPlan {
190 self.0.schema_plan()
191 }
192
193 fn validation_rules(&self) -> &'static [khive_runtime::ValidationRule] {
194 self.0.validation_rules()
195 }
196
197 fn register_embedders(&self, runtime: &KhiveRuntime) {
198 self.0.register_embedders(runtime)
199 }
200
201 fn register_entity_type_validator_with_types(
202 &self,
203 runtime: &KhiveRuntime,
204 pack_entity_types: &[khive_types::EntityTypeDef],
205 ) {
206 self.0
207 .register_entity_type_validator_with_types(runtime, pack_entity_types)
208 }
209
210 async fn warm(&self) {
211 self.0.warm().await
212 }
213
214 async fn dispatch(
215 &self,
216 verb: &str,
217 params: serde_json::Value,
218 registry: &khive_runtime::VerbRegistry,
219 token: &NamespaceToken,
220 ) -> Result<serde_json::Value, RuntimeError> {
221 self.0.dispatch(verb, params, registry, token).await
222 }
223}
224
225inventory::submit! { khive_runtime::PackRegistration(&BrainPackFactory) }