use crate::knowledge::{vamana, KnowledgeHandlers};
use async_trait::async_trait;
use khive_pack_kg::KgPack;
use khive_runtime::{
AllowAllGate, BackendId, EmbedderProvider, KhiveRuntime, Namespace, RuntimeConfig,
VerbRegistry, VerbRegistryBuilder,
};
use lattice_embed::{EmbedError, EmbeddingModel, EmbeddingService};
use serde_json::json;
use std::sync::Arc;
const MODEL_KEY: &str = "all-minilm-l6-v2";
const DIM: usize = 384;
struct FakeDimService;
#[async_trait]
impl EmbeddingService for FakeDimService {
async fn embed(
&self,
texts: &[String],
_model: EmbeddingModel,
) -> Result<Vec<Vec<f32>>, EmbedError> {
Ok(texts
.iter()
.enumerate()
.map(|(i, _)| {
let v = (i + 1) as f32;
let norm = (DIM as f32 * v * v).sqrt();
vec![v / norm; DIM]
})
.collect())
}
fn supports_model(&self, _model: EmbeddingModel) -> bool {
true
}
fn name(&self) -> &'static str {
"fake-dim"
}
}
struct FakeDimProvider;
#[async_trait]
impl EmbedderProvider for FakeDimProvider {
fn name(&self) -> &str {
MODEL_KEY
}
fn dimensions(&self) -> usize {
DIM
}
async fn build(&self) -> Result<Arc<dyn EmbeddingService>, khive_runtime::RuntimeError> {
Ok(Arc::new(FakeDimService))
}
}
fn rt_with_fake_embedder() -> KhiveRuntime {
let rt = KhiveRuntime::new(RuntimeConfig {
git_write: Default::default(),
db_path: None,
default_namespace: Namespace::local(),
embedding_model: Some(EmbeddingModel::AllMiniLmL6V2),
additional_embedding_models: vec![],
gate: Arc::new(AllowAllGate),
packs: vec!["kg".to_string(), "knowledge".to_string()],
backend_id: BackendId::main(),
brain_profile: None,
visible_namespaces: vec![],
allowed_outbound_namespaces: vec![],
actor_id: None,
})
.expect("in-memory runtime");
rt.register_embedder(FakeDimProvider);
rt
}
fn file_rt_with_fake_embedder(db_path: std::path::PathBuf) -> KhiveRuntime {
let rt = KhiveRuntime::new(RuntimeConfig {
git_write: Default::default(),
db_path: Some(db_path),
default_namespace: Namespace::local(),
embedding_model: Some(EmbeddingModel::AllMiniLmL6V2),
additional_embedding_models: vec![],
gate: Arc::new(AllowAllGate),
packs: vec!["kg".to_string(), "knowledge".to_string()],
backend_id: BackendId::main(),
brain_profile: None,
visible_namespaces: vec![],
allowed_outbound_namespaces: vec![],
actor_id: None,
})
.expect("file-backed runtime");
rt.register_embedder(FakeDimProvider);
rt
}
fn build_registry(rt: &KhiveRuntime) -> VerbRegistry {
let mut builder = VerbRegistryBuilder::new();
builder.register(KgPack::new(rt.clone()));
builder.register(crate::KnowledgePack::new(rt.clone()));
let registry = builder.build().expect("registry builds");
rt.install_edge_rules(registry.all_edge_rules());
registry
}
struct TimeoutOverrideReset;
impl Drop for TimeoutOverrideReset {
fn drop(&mut self) {
vamana::set_warm_wait_timeout_override_ms(0);
}
}
static TIMEOUT_OVERRIDE_SERIAL: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
#[tokio::test]
async fn suggest_sets_ann_unavailable_when_warming_times_out() {
let _serial = TIMEOUT_OVERRIDE_SERIAL.lock().await;
vamana::set_warm_wait_timeout_override_ms(50);
let _reset = TimeoutOverrideReset;
let rt = rt_with_fake_embedder();
let registry = build_registry(&rt);
registry
.dispatch(
"knowledge.upsert_atoms",
json!({
"atoms": [{
"slug": "degrade-suggest-atom",
"name": "Degrade Suggest Atom",
"content": "transformer neural network attention mechanism self-attention encoder decoder positional embedding layer normalization residual connection feed forward dense sparse retrieval vector index"
}]
}),
)
.await
.expect("upsert atom");
registry
.dispatch("knowledge.index", json!({ "rebuild_ann": false }))
.await
.expect("index");
let ann = vamana::new_shared();
let model = rt.default_embedder_name().to_string();
let key = vamana::AnnKey::new("local", &model);
vamana::simulate_warming_in_flight(&ann, key);
let token = rt.authorize(Namespace::local()).expect("authorize");
let result = KnowledgeHandlers::suggest(
&rt,
&token,
json!({ "query": "machine learning neural network transformer attention" }),
&ann,
)
.await
.expect("suggest must not Err");
assert_eq!(
result.get("ann_unavailable").and_then(|v| v.as_bool()),
Some(true),
"suggest must carry ann_unavailable=true when ANN warming times out \
and FTS hits are empty; got: {result}"
);
}
#[tokio::test]
async fn compose_propagates_ann_unavailable_in_auto_mode() {
let _serial = TIMEOUT_OVERRIDE_SERIAL.lock().await;
vamana::set_warm_wait_timeout_override_ms(50);
let _reset = TimeoutOverrideReset;
let rt = rt_with_fake_embedder();
let registry = build_registry(&rt);
registry
.dispatch(
"knowledge.upsert_atoms",
json!({
"atoms": [{
"slug": "degrade-compose-atom",
"name": "Degrade Compose Atom",
"content": "attention mechanism self-attention transformer encoder decoder positional embedding layer normalization residual connection feed forward dense sparse retrieval vector nearest neighbor"
}]
}),
)
.await
.expect("upsert atom");
registry
.dispatch("knowledge.index", json!({ "rebuild_ann": false }))
.await
.expect("index");
let ann = vamana::new_shared();
let model = rt.default_embedder_name().to_string();
let key = vamana::AnnKey::new("local", &model);
vamana::simulate_warming_in_flight(&ann, key);
let token = rt.authorize(Namespace::local()).expect("authorize");
let result = KnowledgeHandlers::compose(
&rt,
&token,
json!({
"query": "machine learning neural network transformer attention architecture multi head self attention"
}),
&ann,
std::collections::HashMap::new(),
)
.await
.expect("compose must not Err");
assert_eq!(
result
.get("data")
.and_then(|d| d.get("ann_unavailable"))
.and_then(|v| v.as_bool()),
Some(true),
"compose must propagate ann_unavailable=true from its internal suggest call; \
got: {result}"
);
}
#[tokio::test]
async fn warm_known_snapshots_loads_persisted_snapshot() {
let dir = tempfile::TempDir::new().expect("tempdir");
let rt = file_rt_with_fake_embedder(dir.path().join("test.db"));
let registry = build_registry(&rt);
registry
.dispatch(
"knowledge.upsert_atoms",
json!({
"atoms": [
{
"slug": "warm-snap-atom-a",
"name": "Warm Snapshot Atom A",
"content": "dense retrieval corpus benchmark search latency gradient descent vector index nearest neighbor ranking fusion pipeline embedding rerank cosine similarity unique warm78a"
},
{
"slug": "warm-snap-atom-b",
"name": "Warm Snapshot Atom B",
"content": "ranking fusion pipeline embedding rerank cosine similarity unique warm78b transformer attention mechanism self-attention encoder decoder positional feed forward dense neural network gradient"
}
]
}),
)
.await
.expect("upsert atoms");
let index_result = registry
.dispatch("knowledge.index", json!({ "rebuild_ann": true }))
.await
.expect("index with rebuild_ann=true");
let indexed = index_result
.get("indexed")
.and_then(|v| v.as_u64())
.unwrap_or(0);
assert!(
indexed >= 2,
"knowledge.index must embed at least 2 atoms for this test to be meaningful; \
got indexed={indexed}"
);
let ann = vamana::new_shared();
let model = rt.default_embedder_name().to_string();
let key = vamana::AnnKey::new("local", &model);
let dummy_query = vec![1.0f32 / (DIM as f32).sqrt(); DIM];
assert!(
vamana::search_loaded(&ann, &key, &dummy_query, 1)
.await
.is_none(),
"precondition: fresh SharedAnn must have no index loaded before warm_known_snapshots"
);
vamana::warm_known_snapshots(&rt, &ann).await;
assert!(
vamana::search_loaded(&ann, &key, &dummy_query, 1)
.await
.is_some(),
"search_loaded must return Some after warm_known_snapshots loads the snapshot; \
model={model}, key={key:?}"
);
}