use std::collections::HashSet;
use super::*;
use crate::store::KnowledgeStore;
#[test]
fn test_open_in_memory() {
let _db = SurrealDatabase::open_in_memory().unwrap();
}
#[test]
fn test_schema_applies_without_error() {
let _db = SurrealDatabase::open_in_memory().unwrap();
}
#[test]
fn test_embedded_connect_creates_directory() {
use tempfile::tempdir;
let temp_dir = tempdir().unwrap();
let db_path = temp_dir.path().join("test.surreal");
let _db = SurrealDatabase::open_file_backed_for_test(&db_path).unwrap();
assert!(db_path.exists());
assert!(db_path.is_dir());
}
#[test]
fn test_open_file_backed_for_test_rejects_non_temp_path() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let canon_manifest =
std::fs::canonicalize(manifest_dir).unwrap_or_else(|_| manifest_dir.to_path_buf());
let temp_root = std::env::temp_dir();
let canon_temp = std::fs::canonicalize(&temp_root).unwrap_or(temp_root);
if canon_manifest.starts_with(&canon_temp) {
eprintln!(
"skipping test_open_file_backed_for_test_rejects_non_temp_path: \
CARGO_MANIFEST_DIR ({}) is itself under temp_dir ({}) — \
temp-sandbox build, the guardrail would (correctly) accept it",
canon_manifest.display(),
canon_temp.display()
);
return;
}
let non_temp = manifest_dir.join("fixed.surreal");
let result = std::panic::catch_unwind(|| {
let _ = SurrealDatabase::open_file_backed_for_test(&non_temp);
});
let payload = result.expect_err(
"open_file_backed_for_test must panic when handed a non-temp path, but it returned",
);
let msg = payload
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("");
assert!(
msg.contains("outside the OS temp dir"),
"guardrail panicked with an unexpected message: {msg:?}"
);
}
#[test]
fn test_open_file_backed_for_test_accepts_nested_nonexistent_parent() {
use tempfile::tempdir;
let temp_dir = tempdir().unwrap();
let nested = temp_dir.path().join("a/b/store.surreal");
assert!(!nested.parent().unwrap().exists());
let _db = SurrealDatabase::open_file_backed_for_test(&nested).unwrap();
assert!(nested.exists());
}
#[test]
fn test_upsert_applicability_type_with_datetime() {
use crate::types::ApplicabilityType;
let db = SurrealDatabase::open_in_memory().unwrap();
let atype = ApplicabilityType {
id: "test_type".to_string(),
description: "Test applicability type".to_string(),
scope: Some("test".to_string()),
created_at: "2025-11-29T12:00:00Z".to_string(),
};
db.upsert_applicability_type(&atype).unwrap();
}
#[test]
fn test_upsert_project_with_datetime() {
use crate::types::Project;
let db = SurrealDatabase::open_in_memory().unwrap();
let project = Project {
id: "test_project".to_string(),
name: "Test Project".to_string(),
path: Some("/test/path".to_string()),
repo_url: None,
description: Some("Test description".to_string()),
active: true,
created_at: "2025-11-29T12:00:00Z".to_string(),
updated_at: "2025-11-29T12:30:00Z".to_string(),
};
db.upsert_project(&project).unwrap();
}
#[test]
fn test_upsert_agent_with_datetime() {
use crate::types::Agent;
let db = SurrealDatabase::open_in_memory().unwrap();
let agent = Agent {
id: "test_agent".to_string(),
description: Some("Test agent".to_string()),
domain: Some("testing".to_string()),
created_at: Some("2025-11-29T12:00:00Z".to_string()),
updated_at: Some("2025-11-29T12:30:00Z".to_string()),
};
db.upsert_agent(&agent).unwrap();
}
fn make_test_entry(id: &str, resonance: i32, decay_rate: f64) -> crate::knowledge::KnowledgeEntry {
use chrono::Utc;
let now = Utc::now().to_rfc3339();
crate::knowledge::KnowledgeEntry {
id: id.to_string(),
category_id: "test".to_string(),
title: format!("Test Entry {}", id),
body: Some("Test body".to_string()),
summary: None,
applicability: vec![],
source_project_id: None,
source_agent_id: None,
file_path: None,
tags: vec![],
created_at: Some(now.clone()),
updated_at: Some(now.clone()),
content_hash: Some("test-hash".to_string()),
source_type_id: Some("manual".to_string()),
entry_type_id: Some("primary".to_string()),
session_id: None,
ephemeral: false,
content_type_id: Some("text".to_string()),
owner: None,
visibility: "public".to_string(),
resonance,
resonance_type: Some("ephemeral".to_string()),
last_activated: Some(now),
activation_count: 0,
decay_rate,
anchors: vec![],
wake_phrases: vec![],
triggers: vec![],
wake_order: None,
wake_phrase: None,
embedding: None,
embedding_model: None,
embedded_at: None,
chunk_count: 0,
format: "markdown".to_string(),
effective_resonance: None,
}
}
#[test]
fn test_id_normalization_double_prefix() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-test123", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let result = db.get("kn-kn-test123", &ctx).unwrap();
assert!(result.is_none(), "Double prefix should not match");
let result = db.get("kn-test123", &ctx).unwrap();
assert!(result.is_some(), "Normal prefix should match");
}
#[test]
fn test_id_normalization_case_sensitivity() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-test456", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let result = db.get("KN-test456", &ctx).unwrap();
assert!(
result.is_none(),
"Uppercase KN should not match lowercase kn"
);
}
#[test]
fn test_id_normalization_empty_suffix() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-empty-suffix-control", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
let result = db.get("kn-", &ctx);
assert!(result.is_ok(), "Empty suffix should not panic");
assert!(
result.unwrap().is_none(),
"Empty suffix must resolve to Ok(None), not an error or a match"
);
}
#[test]
fn test_id_normalization_no_prefix() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-test789", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let result = db.get("test789", &ctx).unwrap();
assert!(result.is_some(), "ID without prefix should still match");
}
#[test]
fn test_decay_formula_zero_days() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-fresh", 10, 0.5);
db.upsert_knowledge(&entry).unwrap();
let facts = db.query_recent_facts(1).unwrap();
assert!(!facts.is_empty(), "Should find fresh facts");
}
#[test]
fn test_decay_formula_negative_days() {
let db = SurrealDatabase::open_in_memory().unwrap();
let result = db.query_recent_facts(-1);
assert!(result.is_ok(), "Negative days should not panic");
}
#[test]
fn test_decay_formula_extreme_resonance() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-transcendent", 13, 0.0);
entry.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db.query_recent_facts(30);
assert!(
result.is_ok(),
"Extreme resonance should not break decay formula"
);
let facts = result.unwrap();
assert!(!facts.is_empty(), "Should find transcendent fact");
}
#[test]
fn test_decay_formula_max_int_resonance() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-maxres", i32::MAX, 0.0);
entry.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db.query_recent_facts(30);
assert!(result.is_ok(), "MAX resonance should not overflow");
}
#[test]
fn test_tiered_decay_low_resonance_ephemeral() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-low-res", 2, 0.0);
entry.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db.query_recent_facts(7).unwrap();
assert!(
!result.is_empty(),
"Low-resonance ephemeral entry should be returned when freshly created"
);
}
#[test]
fn test_tiered_decay_mid_resonance_ephemeral() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-mid-res", 5, 0.0);
entry.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db.query_recent_facts(7).unwrap();
assert!(
!result.is_empty(),
"Mid-resonance ephemeral entry should be returned when freshly created"
);
}
#[test]
fn test_tiered_decay_high_resonance_ephemeral() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-high-res", 7, 0.0);
entry.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db.query_recent_facts(7).unwrap();
assert!(
!result.is_empty(),
"High-resonance ephemeral entry should be returned when freshly created"
);
}
#[test]
fn test_tiered_decay_ordering_over_time() {
use chrono::Utc;
let db = SurrealDatabase::open_in_memory().unwrap();
let thirty_days_ago = (Utc::now() - chrono::Duration::days(30)).to_rfc3339();
let mut low = make_test_entry("kn-decay-low", 3, 0.0);
low.resonance_type = Some("ephemeral".to_string());
low.last_activated = Some(thirty_days_ago.clone());
db.upsert_knowledge(&low).unwrap();
let mut high = make_test_entry("kn-decay-high", 7, 0.0);
high.resonance_type = Some("ephemeral".to_string());
high.last_activated = Some(thirty_days_ago);
db.upsert_knowledge(&high).unwrap();
let results = db.query_recent_facts(60).unwrap();
let low_found = results.iter().any(|e| e.id == "kn-decay-low");
let high_found = results.iter().any(|e| e.id == "kn-decay-high");
assert!(
low_found,
"Low-resonance entry should still pass > 0.5 filter after 30 days"
);
assert!(
high_found,
"High-resonance entry should pass > 0.5 filter after 30 days"
);
let low_pos = results.iter().position(|e| e.id == "kn-decay-low").unwrap();
let high_pos = results
.iter()
.position(|e| e.id == "kn-decay-high")
.unwrap();
assert!(
high_pos < low_pos,
"High-resonance entry (slower decay) should rank above low-resonance entry after 30 days"
);
}
#[test]
fn test_bloom_exemption_foundational() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-foundational", 9, 0.0);
entry.resonance_type = Some("foundational".to_string());
db.upsert_knowledge(&entry).unwrap();
let ephemeral_results = db.query_recent_facts(30).unwrap();
let found_in_ephemeral = ephemeral_results.iter().any(|e| e.id == "kn-foundational");
assert!(
!found_in_ephemeral,
"Foundational entry should not appear in ephemeral fact query"
);
let ctx = crate::store::AgentContext::public_only();
let direct = db.get("kn-foundational", &ctx).unwrap();
assert!(
direct.is_some(),
"Foundational entry should be directly retrievable"
);
}
#[test]
fn test_bloom_exemption_transformative() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-transformative", 8, 0.0);
entry.resonance_type = Some("transformative".to_string());
db.upsert_knowledge(&entry).unwrap();
let ephemeral_results = db.query_recent_facts(30).unwrap();
let found_in_ephemeral = ephemeral_results
.iter()
.any(|e| e.id == "kn-transformative");
assert!(
!found_in_ephemeral,
"Transformative entry should not appear in ephemeral fact query"
);
let ctx = crate::store::AgentContext::public_only();
let direct = db.get("kn-transformative", &ctx).unwrap();
assert!(
direct.is_some(),
"Transformative entry should be directly retrievable"
);
}
#[test]
fn test_increment_activation_count_no_timestamp_reset() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-incr-test", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let before = db.get("kn-incr-test", &ctx).unwrap().unwrap();
let initial_count = before.activation_count;
let initial_last_activated = before.last_activated.clone();
db.increment_activation_count(&["kn-incr-test".to_string()])
.unwrap();
let after = db.get("kn-incr-test", &ctx).unwrap().unwrap();
assert_eq!(
after.activation_count,
initial_count + 1,
"activation_count should increment by 1"
);
assert_eq!(
after.last_activated, initial_last_activated,
"last_activated should not be reset by increment_activation_count"
);
}
#[test]
fn test_thread_duplicate_detection() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry1 = make_test_entry("kn-thread1", 5, 0.5);
let mut entry2 = make_test_entry("kn-thread2", 5, 0.5);
entry2.body = Some(" TEST BODY ".to_string());
db.upsert_knowledge(&entry1).unwrap();
db.upsert_knowledge(&entry2).unwrap();
let ctx = crate::store::AgentContext::public_only();
assert!(db.get("kn-thread1", &ctx).unwrap().is_some());
assert!(db.get("kn-thread2", &ctx).unwrap().is_some());
}
#[test]
fn test_session_linkage_round_trip() {
let db = SurrealDatabase::open_in_memory().unwrap();
let session = make_test_entry("kn-session123", 0, 0.0);
db.upsert_knowledge(&session).unwrap();
let mut fact = make_test_entry("kn-fact456", 5, 0.5);
fact.session_id = Some("kn-session123".to_string());
db.upsert_knowledge(&fact).unwrap();
db.add_relationship("kn-fact456", "kn-session123", "extracted_from")
.unwrap();
let facts = db.get_facts_for_session("kn-session123").unwrap();
assert_eq!(facts.len(), 1, "Should find one fact for session");
assert_eq!(
facts[0], "kn-fact456",
"Should return full fact ID with prefix"
);
let session_id = db.get_session_for_fact("kn-fact456").unwrap();
assert!(session_id.is_some(), "Should find session for fact");
assert_eq!(
session_id.unwrap(),
"kn-session123",
"Should return full session ID with prefix"
);
}
#[test]
fn test_session_linkage_multiple_facts() {
let db = SurrealDatabase::open_in_memory().unwrap();
let session = make_test_entry("kn-multisession", 0, 0.0);
db.upsert_knowledge(&session).unwrap();
for i in 1..=5 {
let mut fact = make_test_entry(&format!("kn-fact{}", i), 5, 0.5);
fact.session_id = Some("kn-multisession".to_string());
db.upsert_knowledge(&fact).unwrap();
db.add_relationship(
&format!("kn-fact{}", i),
"kn-multisession",
"extracted_from",
)
.unwrap();
}
let facts = db.get_facts_for_session("kn-multisession").unwrap();
assert_eq!(facts.len(), 5, "Should find all 5 facts for session");
}
#[test]
fn test_session_linkage_orphaned_fact() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut fact = make_test_entry("kn-orphan", 5, 0.5);
fact.session_id = Some("kn-ghost".to_string());
db.upsert_knowledge(&fact).unwrap();
let facts = db.get_facts_for_session("kn-ghost").unwrap();
assert_eq!(
facts.len(),
0,
"Orphaned fact should not appear without relationship"
);
let session = db.get_session_for_fact("kn-orphan").unwrap();
assert!(session.is_none(), "Orphaned fact should have no session");
}
#[test]
fn test_normalize_content_edge_cases() {
use crate::knowledge::KnowledgeEntry;
assert_eq!(KnowledgeEntry::normalize_content(""), "");
assert_eq!(KnowledgeEntry::normalize_content(" \n\t "), "");
let unicode = "Hello 世界! Привет мир!";
let normalized = KnowledgeEntry::normalize_content(unicode);
assert!(normalized.contains("hello"), "Should lowercase ASCII");
assert!(normalized.contains("世界"), "Should preserve unicode");
let messy = " hello\n\n world\t\ttest ";
assert_eq!(KnowledgeEntry::normalize_content(messy), "hello world test");
}
#[test]
fn test_wake_cascade_empty_anchors() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-solo", 9, 0.0);
entry.resonance_type = Some("foundational".to_string());
entry.anchors = vec![];
db.upsert_knowledge(&entry).unwrap();
let cascade = db.wake_cascade(&ctx, 50, Some(7), 7, false).unwrap();
assert!(!cascade.core.is_empty(), "Should find core bloom");
}
#[test]
fn test_wake_cascade_circular_anchors() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut bloom_a = make_test_entry("kn-circular-a", 9, 0.0);
bloom_a.resonance_type = Some("foundational".to_string());
bloom_a.anchors = vec!["kn-circular-b".to_string()];
let mut bloom_b = make_test_entry("kn-circular-b", 9, 0.0);
bloom_b.resonance_type = Some("foundational".to_string());
bloom_b.anchors = vec!["kn-circular-a".to_string()];
db.upsert_knowledge(&bloom_a).unwrap();
db.upsert_knowledge(&bloom_b).unwrap();
let ctx = crate::store::AgentContext::public_only();
let result = db.wake_cascade(&ctx, 50, Some(7), 7, false);
assert!(
result.is_ok(),
"Circular anchors should not cause infinite loop"
);
}
#[test]
fn test_privacy_filtering_public_only() {
let db = SurrealDatabase::open_in_memory().unwrap();
let public_entry = make_test_entry("kn-public", 5, 0.5);
db.upsert_knowledge(&public_entry).unwrap();
let mut private_entry = make_test_entry("kn-private", 5, 0.5);
private_entry.visibility = "private".to_string();
private_entry.owner = Some("test_agent".to_string());
db.upsert_knowledge(&private_entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
assert!(
db.get("kn-public", &ctx).unwrap().is_some(),
"Should see public entry"
);
assert!(
db.get("kn-private", &ctx).unwrap().is_none(),
"Should not see private entry"
);
}
#[test]
fn test_privacy_filtering_agent_context() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut private_entry = make_test_entry("kn-my-private", 5, 0.5);
private_entry.visibility = "private".to_string();
private_entry.owner = Some("test_agent".to_string());
db.upsert_knowledge(&private_entry).unwrap();
let mut other_entry = make_test_entry("kn-other-private", 5, 0.5);
other_entry.visibility = "private".to_string();
other_entry.owner = Some("other_agent".to_string());
db.upsert_knowledge(&other_entry).unwrap();
let ctx = crate::store::AgentContext::for_agent("test_agent");
assert!(
db.get("kn-my-private", &ctx).unwrap().is_some(),
"Should see own private entry"
);
assert!(
db.get("kn-other-private", &ctx).unwrap().is_none(),
"Should not see other's private entry"
);
}
#[test]
fn test_delete_cross_agent_visibility_blocked() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-private-del-target", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let result = db.delete("kn-private-del-target", &ctx_b).unwrap();
assert!(
!result,
"agent-b should not be able to delete agent-a's private entry"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let still_exists = db.get("kn-private-del-target", &ctx_a).unwrap();
assert!(
still_exists.is_some(),
"Entry should still exist for agent-a after failed cross-agent delete"
);
}
#[test]
fn test_update_summary_cross_agent_visibility_blocked() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-private-summary-target", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
entry.summary = Some(r#"{"state":"open"}"#.to_string());
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let result = db
.update_summary(
"kn-private-summary-target",
r#"{"state":"compromised"}"#,
&ctx_b,
)
.unwrap();
assert!(
!result,
"agent-b should not be able to update summary on agent-a's private entry"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let unchanged = db
.get("kn-private-summary-target", &ctx_a)
.unwrap()
.unwrap();
let summary: serde_json::Value =
serde_json::from_str(unchanged.summary.as_deref().unwrap()).unwrap();
assert_eq!(
summary["state"], "open",
"Summary should be unchanged after failed cross-agent update"
);
}
#[test]
fn test_owned_private_matching_scopes_to_caller_only() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut mine = make_test_entry("kn-mine-priv", 5, 0.0);
mine.visibility = "private".to_string();
mine.owner = Some("agent-a".to_string());
db.upsert_knowledge(&mine).unwrap();
let mut theirs = make_test_entry("kn-their-priv", 5, 0.0);
theirs.visibility = "private".to_string();
theirs.owner = Some("agent-b".to_string());
db.upsert_knowledge(&theirs).unwrap();
let public = make_test_entry("kn-pub", 5, 0.0);
db.upsert_knowledge(&public).unwrap();
let filter = crate::store::KnowledgeFilter::default();
let got = db.owned_private_matching("agent-a", None, &filter).unwrap();
let ids: Vec<&str> = got.iter().map(|e| e.id.as_str()).collect();
assert_eq!(
ids,
vec!["kn-mine-priv"],
"must return only the caller's OWN private entries — never public, \
never another agent's private rows"
);
}
#[test]
fn test_owned_private_matching_empty_when_none_private() {
let db = SurrealDatabase::open_in_memory().unwrap();
let public = make_test_entry("kn-only-pub", 5, 0.0);
db.upsert_knowledge(&public).unwrap();
let filter = crate::store::KnowledgeFilter::default();
let got = db.owned_private_matching("agent-a", None, &filter).unwrap();
assert!(
got.is_empty(),
"no owned-private entries -> empty result (hint will be suppressed)"
);
}
#[test]
fn test_owned_private_matching_honors_category_filter() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut in_cat = make_test_entry("kn-priv-decision", 5, 0.0);
in_cat.visibility = "private".to_string();
in_cat.owner = Some("agent-a".to_string());
in_cat.category_id = "decision".to_string();
db.upsert_knowledge(&in_cat).unwrap();
let mut other_cat = make_test_entry("kn-priv-test", 5, 0.0);
other_cat.visibility = "private".to_string();
other_cat.owner = Some("agent-a".to_string());
other_cat.category_id = "test".to_string();
db.upsert_knowledge(&other_cat).unwrap();
let filter = crate::store::KnowledgeFilter {
categories: Some(vec!["decision".to_string()]),
..Default::default()
};
let got = db.owned_private_matching("agent-a", None, &filter).unwrap();
let ids: Vec<&str> = got.iter().map(|e| e.id.as_str()).collect();
assert_eq!(
ids,
vec!["kn-priv-decision"],
"category filter must match the main query's category scoping"
);
}
#[test]
fn test_owned_private_matching_honors_search_terms() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut hit = make_test_entry("kn-priv-widget", 5, 0.0);
hit.visibility = "private".to_string();
hit.owner = Some("agent-a".to_string());
hit.title = "unique searchable widget".to_string();
hit.body = Some("unique searchable widget content".to_string());
db.upsert_knowledge(&hit).unwrap();
let mut miss = make_test_entry("kn-priv-sprocket", 5, 0.0);
miss.visibility = "private".to_string();
miss.owner = Some("agent-a".to_string());
miss.title = "unrelated sprocket".to_string();
miss.body = Some("unrelated sprocket content".to_string());
db.upsert_knowledge(&miss).unwrap();
let filter = crate::store::KnowledgeFilter::default();
let got = db
.owned_private_matching("agent-a", Some("widget"), &filter)
.unwrap();
let ids: Vec<&str> = got.iter().map(|e| e.id.as_str()).collect();
assert_eq!(
ids,
vec!["kn-priv-widget"],
"search-term (@@) filter must exclude owned-private entries that don't \
match the query"
);
}
#[test]
fn test_owned_private_matching_honors_effective_resonance() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut faded = make_test_entry("kn-priv-faded", 2, 0.0);
faded.visibility = "private".to_string();
faded.owner = Some("agent-a".to_string());
faded.resonance_type = Some("ephemeral".to_string());
faded.last_activated = Some((chrono::Utc::now() - chrono::Duration::days(365)).to_rfc3339());
faded.created_at = faded.last_activated.clone();
db.upsert_knowledge(&faded).unwrap();
let filter = crate::store::KnowledgeFilter {
min_resonance: Some(3),
..Default::default()
};
let got = db.owned_private_matching("agent-a", None, &filter).unwrap();
assert!(
got.is_empty(),
"an owned-private entry whose EFFECTIVE (decayed) resonance is below \
min_resonance must be excluded, like list/search"
);
}
#[test]
fn test_reinforce_basic() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-test-reinforce", 5, 0.0);
entry.activation_count = 10;
db.upsert_knowledge(&entry).unwrap();
let result = db
.reinforce("kn-test-reinforce", 2, Some(10), &ctx)
.unwrap()
.expect("reinforce should return Some for visible entry");
assert_eq!(result.id, "kn-test-reinforce");
assert_eq!(result.old_resonance, 5);
assert_eq!(result.new_resonance, 7);
assert_eq!(result.amount_added, 2);
assert!(!result.capped);
assert_eq!(result.activation_count, 11);
let updated = db.get("kn-test-reinforce", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 7);
assert_eq!(updated.activation_count, 11);
assert!(updated.last_activated.is_some());
}
#[test]
fn test_reinforce_with_cap() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-test-cap", 9, 0.0);
db.upsert_knowledge(&entry).unwrap();
let result = db
.reinforce("kn-test-cap", 5, Some(10), &ctx)
.unwrap()
.expect("reinforce should return Some for visible entry");
assert_eq!(result.old_resonance, 9);
assert_eq!(result.new_resonance, 10);
assert_eq!(result.amount_added, 5);
assert!(result.capped);
let updated = db.get("kn-test-cap", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 10);
}
#[test]
fn test_reinforce_without_cap() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-test-no-cap", 9, 0.0);
db.upsert_knowledge(&entry).unwrap();
let result = db
.reinforce("kn-test-no-cap", 5, None, &ctx)
.unwrap()
.expect("reinforce should return Some for visible entry");
assert_eq!(result.old_resonance, 9);
assert_eq!(result.new_resonance, 14);
assert!(!result.capped);
let updated = db.get("kn-test-no-cap", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 14);
}
#[test]
fn test_reinforce_nonexistent() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let result = db.reinforce("kn-nonexistent", 1, Some(10), &ctx).unwrap();
assert!(
result.is_none(),
"reinforce should return None for nonexistent entry"
);
}
#[test]
fn test_reinforce_id_normalization() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-test-norm", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
let result = db
.reinforce("test-norm", 2, Some(10), &ctx)
.unwrap()
.expect("reinforce should return Some for visible entry");
assert_eq!(result.id, "kn-test-norm");
assert_eq!(result.new_resonance, 7);
}
#[test]
fn test_reinforce_cross_agent_visibility_blocked() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-private-reinforce-target", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
entry.activation_count = 3;
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let result = db
.reinforce("kn-private-reinforce-target", 2, Some(10), &ctx_b)
.unwrap();
assert!(
result.is_none(),
"agent-b should not be able to reinforce agent-a's private entry"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let unchanged = db
.get("kn-private-reinforce-target", &ctx_a)
.unwrap()
.unwrap();
assert_eq!(
unchanged.resonance, 5,
"Resonance should be unchanged after failed cross-agent reinforce"
);
assert_eq!(
unchanged.activation_count, 3,
"Activation count should be unchanged after failed cross-agent reinforce"
);
}
#[test]
fn test_reinforce_own_private_entry() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-private-reinforce-own", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
entry.activation_count = 3;
db.upsert_knowledge(&entry).unwrap();
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let result = db
.reinforce("kn-private-reinforce-own", 2, Some(10), &ctx_a)
.unwrap()
.expect("agent-a should be able to reinforce their own private entry");
assert_eq!(result.old_resonance, 5);
assert_eq!(result.new_resonance, 7);
assert_eq!(result.activation_count, 4);
let updated = db.get("kn-private-reinforce-own", &ctx_a).unwrap().unwrap();
assert_eq!(updated.resonance, 7);
assert_eq!(updated.activation_count, 4);
}
#[test]
fn test_update_summary_persists() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-summary-test", 5, 0.0);
entry.summary = Some(r#"{"state":"open","topic":"test thread"}"#.to_string());
db.upsert_knowledge(&entry).unwrap();
let new_summary = r#"{"state":"closed","topic":"test thread"}"#;
let result = db
.update_summary("kn-summary-test", new_summary, &ctx)
.unwrap();
assert!(
result,
"update_summary should return true for visible entry"
);
let updated = db.get("kn-summary-test", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
assert_eq!(summary["topic"], "test thread");
}
#[test]
fn test_update_summary_id_normalization() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-summary-norm", 5, 0.0);
entry.summary = Some(r#"{"state":"open"}"#.to_string());
db.upsert_knowledge(&entry).unwrap();
let result = db
.update_summary("summary-norm", r#"{"state":"closed"}"#, &ctx)
.unwrap();
assert!(result, "update_summary should return true with raw ID");
let updated = db.get("kn-summary-norm", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
let result2 = db
.update_summary("kn-summary-norm", r#"{"state":"reopened"}"#, &ctx)
.unwrap();
assert!(
result2,
"update_summary should return true with prefixed ID"
);
let updated2 = db.get("kn-summary-norm", &ctx).unwrap().unwrap();
let summary2: serde_json::Value =
serde_json::from_str(updated2.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary2["state"], "reopened");
}
#[test]
fn test_close_thread_with_no_summary() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-no-summary-thread", 5, 0.0);
entry.summary = None;
db.upsert_knowledge(&entry).unwrap();
let closed_summary = r#"{"state":"closed","topic":"pre-convention thread"}"#;
let result = db
.update_summary("kn-no-summary-thread", closed_summary, &ctx)
.unwrap();
assert!(
result,
"update_summary should return true for entry with no prior summary"
);
let updated = db.get("kn-no-summary-thread", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
assert_eq!(summary["topic"], "pre-convention thread");
}
#[test]
fn test_get_summary_state_returns_none_for_no_summary() {
let entry = make_test_entry("kn-state-none", 5, 0.0);
assert!(
entry.summary.is_none(),
"make_test_entry should produce summary: None"
);
assert_eq!(
entry.get_summary_state(),
None,
"get_summary_state() must return None when summary is absent"
);
}
fn as_store(db: &SurrealDatabase) -> &dyn KnowledgeStore {
db
}
#[test]
fn test_update_builder_single_field_sets_only_that_field() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-single", 5, 0.0);
entry.summary = Some(r#"{"state":"open"}"#.to_string());
entry.activation_count = 7;
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-single")
.summary(r#"{"state":"closed"}"#)
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
assert!(!outcome.no_op);
let updated = db.get("kn-builder-single", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
assert_eq!(updated.resonance, 5, "resonance must be untouched");
assert_eq!(
updated.activation_count, 7,
"activation_count must be untouched"
);
}
#[test]
fn test_update_builder_multiple_fields_compose() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-multi", 5, 0.0);
entry.summary = Some(r#"{"state":"open"}"#.to_string());
entry.activation_count = 1;
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-multi")
.summary(r#"{"state":"closed"}"#)
.resonance(9)
.activation_count(42)
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
let updated = db.get("kn-builder-multi", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
assert_eq!(updated.resonance, 9);
assert_eq!(updated.activation_count, 42);
}
#[test]
fn test_update_builder_unset_field_preserved() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-preserve", 6, 0.0);
entry.summary = Some(r#"{"keep":"me"}"#.to_string());
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-preserve")
.resonance(2)
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
let updated = db.get("kn-builder-preserve", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 2);
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(
summary["keep"], "me",
"summary must be preserved when only resonance is set"
);
}
#[test]
fn test_update_builder_increment_activation_count_is_relative() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-incr", 5, 0.0);
entry.activation_count = 10;
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-incr")
.increment_activation_count(3)
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
let updated = db.get("kn-builder-incr", &ctx).unwrap().unwrap();
assert_eq!(
updated.activation_count, 13,
"increment_activation_count(3) should be a relative +=, not a set"
);
}
#[test]
fn test_update_builder_empty_is_noop() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-builder-empty", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-empty")
.execute(&ctx)
.unwrap();
assert!(outcome.no_op, "empty builder must be a no-op");
assert!(!outcome.applied);
let updated = db.get("kn-builder-empty", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 5);
}
#[test]
fn test_update_builder_not_found_returns_not_applied() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let outcome = as_store(&db)
.update("kn-does-not-exist")
.resonance(3)
.execute(&ctx)
.unwrap();
assert!(!outcome.applied, "missing entry => applied=false");
assert!(
!outcome.no_op,
"a real (non-empty) update still ran the check"
);
}
#[test]
fn test_update_builder_add_tag() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-tag", 5, 0.0);
entry.tags = vec!["existing".to_string()];
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-tag")
.add_tag("fresh")
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
let mut tags = db.get_tags_for_entry("kn-builder-tag").unwrap();
tags.sort();
assert_eq!(tags, vec!["existing".to_string(), "fresh".to_string()]);
}
#[test]
fn test_update_builder_add_tag_idempotent() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry = make_test_entry("kn-builder-tag-idem", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
as_store(&db)
.update("kn-builder-tag-idem")
.add_tag("dup")
.execute(&ctx)
.unwrap();
as_store(&db)
.update("kn-builder-tag-idem")
.add_tag("dup")
.execute(&ctx)
.unwrap();
let tags = db.get_tags_for_entry("kn-builder-tag-idem").unwrap();
assert_eq!(
tags,
vec!["dup".to_string()],
"adding the same tag twice should yield a single edge"
);
}
#[test]
fn test_update_builder_field_and_tag_together() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-builder-combo", 5, 0.0);
entry.activation_count = 2;
db.upsert_knowledge(&entry).unwrap();
let outcome = as_store(&db)
.update("kn-builder-combo")
.resonance(8)
.add_tag("combo")
.execute(&ctx)
.unwrap();
assert!(outcome.applied);
let updated = db.get("kn-builder-combo", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 8);
assert_eq!(updated.activation_count, 2);
let tags = db.get_tags_for_entry("kn-builder-combo").unwrap();
assert_eq!(tags, vec!["combo".to_string()]);
}
#[test]
fn test_update_builder_column_write_bumps_updated_at() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-updated-at", 5, 0.0);
entry.updated_at = Some("2020-01-01T00:00:00Z".to_string());
db.upsert_knowledge(&entry).unwrap();
as_store(&db)
.update("kn-updated-at")
.resonance(8)
.execute(&ctx)
.unwrap();
let after_col = db.get("kn-updated-at", &ctx).unwrap().unwrap();
let col_ts = after_col.updated_at.clone().unwrap();
assert!(
col_ts.as_str() > "2020-01-01T00:00:00Z",
"column write must bump updated_at, got {col_ts}"
);
as_store(&db)
.update("kn-updated-at")
.add_tag("tag-only")
.execute(&ctx)
.unwrap();
let after_tag = db.get("kn-updated-at", &ctx).unwrap().unwrap();
assert_eq!(
after_tag.updated_at.unwrap(),
col_ts,
"tag-only update must NOT bump updated_at"
);
}
#[test]
fn test_update_builder_respects_visibility() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-builder-private", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let outcome = as_store(&db)
.update("kn-builder-private")
.resonance(1)
.execute(&ctx_b)
.unwrap();
assert!(
!outcome.applied,
"agent-b must not see/update agent-a's private entry"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let unchanged = db.get("kn-builder-private", &ctx_a).unwrap().unwrap();
assert_eq!(
unchanged.resonance, 5,
"private entry resonance must be unchanged after blocked cross-agent update"
);
}
#[test]
fn test_update_builder_add_tag_respects_visibility() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-builder-tag-private", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let outcome = as_store(&db)
.update("kn-builder-tag-private")
.add_tag("sneaky")
.execute(&ctx_b)
.unwrap();
assert!(
!outcome.applied,
"agent-b must not be able to tag agent-a's private entry"
);
let tags = db.get_tags_for_entry("kn-builder-tag-private").unwrap();
assert!(
tags.is_empty(),
"no tag edge should exist after a blocked cross-agent add_tag, got {tags:?}"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let owner_outcome = as_store(&db)
.update("kn-builder-tag-private")
.add_tag("legit")
.execute(&ctx_a)
.unwrap();
assert!(owner_outcome.applied, "owner must be able to tag own entry");
let owner_tags = db.get_tags_for_entry("kn-builder-tag-private").unwrap();
assert_eq!(owner_tags, vec!["legit".to_string()]);
}
#[test]
fn test_update_builder_column_plus_tag_respects_visibility() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-builder-col-tag-private", 5, 0.0);
entry.visibility = "private".to_string();
entry.owner = Some("agent-a".to_string());
entry.summary = Some("original".to_string());
db.upsert_knowledge(&entry).unwrap();
let ctx_b = crate::store::AgentContext::for_agent("agent-b");
let outcome = as_store(&db)
.update("kn-builder-col-tag-private")
.summary("hijacked")
.add_tag("sneaky")
.execute(&ctx_b)
.unwrap();
assert!(
!outcome.applied,
"agent-b must not be able to update+tag agent-a's private entry"
);
let ctx_a = crate::store::AgentContext::for_agent("agent-a");
let after_block = db
.get("kn-builder-col-tag-private", &ctx_a)
.unwrap()
.unwrap();
assert_eq!(
after_block.summary.as_deref(),
Some("original"),
"blocked cross-agent update must not change the column"
);
let tags = db.get_tags_for_entry("kn-builder-col-tag-private").unwrap();
assert!(
tags.is_empty(),
"blocked cross-agent update must not create a tag edge, got {tags:?}"
);
let owner_outcome = as_store(&db)
.update("kn-builder-col-tag-private")
.summary("owner-set")
.add_tag("legit")
.execute(&ctx_a)
.unwrap();
assert!(
owner_outcome.applied,
"owner must be able to update+tag own entry"
);
let after_owner = db
.get("kn-builder-col-tag-private", &ctx_a)
.unwrap()
.unwrap();
assert_eq!(after_owner.summary.as_deref(), Some("owner-set"));
let owner_tags = db.get_tags_for_entry("kn-builder-col-tag-private").unwrap();
assert_eq!(owner_tags, vec!["legit".to_string()]);
}
#[test]
fn test_update_summary_still_works_via_builder_delegation() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-summary-delegation", 5, 0.0);
entry.summary = Some(r#"{"state":"open"}"#.to_string());
entry.resonance = 5;
db.upsert_knowledge(&entry).unwrap();
let applied = db
.update_summary("kn-summary-delegation", r#"{"state":"closed"}"#, &ctx)
.unwrap();
assert!(applied);
let updated = db.get("kn-summary-delegation", &ctx).unwrap().unwrap();
let summary: serde_json::Value =
serde_json::from_str(updated.summary.as_deref().unwrap()).unwrap();
assert_eq!(summary["state"], "closed");
assert_eq!(updated.resonance, 5);
}
#[test]
fn test_query_recent_facts_all_types_includes_foundational() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut foundational = make_test_entry("kn-all-types-foundational", 9, 0.0);
foundational.resonance_type = Some("foundational".to_string());
db.upsert_knowledge(&foundational).unwrap();
let mut ephemeral = make_test_entry("kn-all-types-ephemeral", 5, 0.0);
ephemeral.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&ephemeral).unwrap();
let ephemeral_results = db.query_recent_facts(30).unwrap();
assert!(
!ephemeral_results
.iter()
.any(|e| e.id == "kn-all-types-foundational"),
"Foundational entry should not appear in ephemeral-only query"
);
assert!(
ephemeral_results
.iter()
.any(|e| e.id == "kn-all-types-ephemeral"),
"Ephemeral entry should appear in ephemeral-only query"
);
let all_results = db.query_recent_facts_all_types(30).unwrap();
assert!(
all_results
.iter()
.any(|e| e.id == "kn-all-types-foundational"),
"Foundational entry should appear in all-types query"
);
assert!(
all_results.iter().any(|e| e.id == "kn-all-types-ephemeral"),
"Ephemeral entry should appear in all-types query"
);
}
#[test]
fn test_query_recent_facts_all_types_includes_transformative() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut transformative = make_test_entry("kn-all-types-transformative", 8, 0.0);
transformative.resonance_type = Some("transformative".to_string());
db.upsert_knowledge(&transformative).unwrap();
let all_results = db.query_recent_facts_all_types(30).unwrap();
assert!(
all_results
.iter()
.any(|e| e.id == "kn-all-types-transformative"),
"Transformative entry should appear in all-types query"
);
}
#[test]
fn test_query_recent_facts_all_types_respects_decay_threshold() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut high = make_test_entry("kn-all-types-high", 8, 0.0);
high.resonance_type = Some("ephemeral".to_string());
db.upsert_knowledge(&high).unwrap();
let results = db.query_recent_facts_all_types(30).unwrap();
assert!(
results.iter().any(|e| e.id == "kn-all-types-high"),
"High-resonance ephemeral entry should appear in all-types query"
);
}
fn make_tagged_entry(
id: &str,
category: &str,
tags: Vec<String>,
) -> crate::knowledge::KnowledgeEntry {
let mut entry = make_test_entry(id, 5, 0.0);
entry.category_id = category.to_string();
entry.tags = tags;
entry
}
#[test]
fn test_list_all_tags_returns_distinct_tags() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry1 = make_tagged_entry(
"kn-tag1",
"pattern",
vec!["rust".to_string(), "async".to_string()],
);
db.upsert_knowledge(&entry1).unwrap();
let entry2 = make_tagged_entry(
"kn-tag2",
"technique",
vec!["rust".to_string(), "error-handling".to_string()],
);
db.upsert_knowledge(&entry2).unwrap();
let tags = db.list_all_tags(None).unwrap();
assert_eq!(tags.len(), 3);
assert_eq!(tags, vec!["async", "error-handling", "rust"]);
}
#[test]
fn test_list_all_tags_with_category_filter() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry1 = make_tagged_entry(
"kn-tag3",
"pattern",
vec!["rust".to_string(), "async".to_string()],
);
db.upsert_knowledge(&entry1).unwrap();
let entry2 = make_tagged_entry(
"kn-tag4",
"technique",
vec!["rust".to_string(), "error-handling".to_string()],
);
db.upsert_knowledge(&entry2).unwrap();
let pattern_tags = db.list_all_tags(Some("pattern")).unwrap();
assert_eq!(pattern_tags.len(), 2);
assert_eq!(pattern_tags, vec!["async", "rust"]);
let technique_tags = db.list_all_tags(Some("technique")).unwrap();
assert_eq!(technique_tags.len(), 2);
assert_eq!(technique_tags, vec!["error-handling", "rust"]);
}
#[test]
fn test_list_all_tags_empty_database() {
let db = SurrealDatabase::open_in_memory().unwrap();
let tags = db.list_all_tags(None).unwrap();
assert!(tags.is_empty());
let tags = db.list_all_tags(Some("pattern")).unwrap();
assert!(tags.is_empty());
}
#[test]
fn test_detect_ghosts_finds_missing_anchors() {
let live_ids: HashSet<String> = ["aaa"].iter().map(|s| s.to_string()).collect();
let anchors = vec!["aaa".to_string(), "bbb".to_string()];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert_eq!(ghosts, vec!["bbb"]);
}
#[test]
fn test_detect_ghosts_no_false_positives() {
let live_ids: HashSet<String> = ["aaa", "bbb", "ccc"]
.iter()
.map(|s| s.to_string())
.collect();
let anchors = vec!["aaa".to_string(), "bbb".to_string()];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert!(
ghosts.is_empty(),
"No ghosts should be detected when all anchors exist"
);
}
#[test]
fn test_detect_ghosts_all_anchors_are_ghosts() {
let live_ids: HashSet<String> = HashSet::new();
let anchors = vec!["aaa".to_string(), "bbb".to_string(), "ccc".to_string()];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert_eq!(ghosts.len(), 3, "All anchors should be ghosts");
assert_eq!(ghosts, anchors);
}
#[test]
fn test_detect_ghosts_handles_kn_prefix() {
let live_ids: HashSet<String> = ["aaa"].iter().map(|s| s.to_string()).collect();
let anchors = vec!["kn-aaa".to_string(), "kn-bbb".to_string()];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert_eq!(
ghosts,
vec!["kn-bbb"],
"kn-aaa maps to live 'aaa', kn-bbb is a ghost"
);
}
#[test]
fn test_detect_ghosts_mixed_prefix_and_bare() {
let live_ids: HashSet<String> = ["aaa", "bbb"].iter().map(|s| s.to_string()).collect();
let anchors = vec![
"kn-aaa".to_string(), "bbb".to_string(), "ccc".to_string(), "kn-ddd".to_string(), ];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert_eq!(ghosts, vec!["ccc", "kn-ddd"]);
}
#[test]
fn test_detect_ghosts_empty_anchors() {
let live_ids: HashSet<String> = ["aaa"].iter().map(|s| s.to_string()).collect();
let anchors: Vec<String> = vec![];
let ghosts = queries::detect_ghosts(&anchors, &live_ids);
assert!(ghosts.is_empty());
}
#[test]
fn test_sweep_ghost_anchors_dry_run_does_not_modify() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry_a = make_test_entry("kn-sweep-a", 5, 0.0);
entry_a.anchors = vec!["kn-sweep-target".to_string(), "kn-sweep-ghost".to_string()];
db.upsert_knowledge(&entry_a).unwrap();
let entry_target = make_test_entry("kn-sweep-target", 3, 0.0);
db.upsert_knowledge(&entry_target).unwrap();
let result = db.sweep_ghost_anchors(true).unwrap();
assert_eq!(result.ghosts_found, 1, "Should detect one ghost anchor");
assert_eq!(
result.ghosts_removed, 0,
"Dry run should not remove anything"
);
assert!(result.dry_run);
assert_eq!(result.affected_entries.len(), 1);
assert_eq!(
result.affected_entries[0].ghost_anchors,
vec!["kn-sweep-ghost"]
);
let ctx = crate::store::AgentContext::public_only();
let unchanged = db.get("kn-sweep-a", &ctx).unwrap().unwrap();
assert_eq!(
unchanged.anchors.len(),
2,
"Dry run must not modify anchors"
);
}
#[test]
fn test_sweep_ghost_anchors_removes_ghosts() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry_a = make_test_entry("kn-sweep-rm-a", 5, 0.0);
entry_a.anchors = vec![
"kn-sweep-rm-live".to_string(),
"kn-sweep-rm-dead".to_string(),
];
db.upsert_knowledge(&entry_a).unwrap();
let live_entry = make_test_entry("kn-sweep-rm-live", 3, 0.0);
db.upsert_knowledge(&live_entry).unwrap();
let result = db.sweep_ghost_anchors(false).unwrap();
assert_eq!(result.ghosts_found, 1);
assert_eq!(result.ghosts_removed, 1);
assert!(!result.dry_run);
let ctx = crate::store::AgentContext::public_only();
let updated = db.get("kn-sweep-rm-a", &ctx).unwrap().unwrap();
assert_eq!(
updated.anchors.len(),
1,
"Ghost anchor should have been removed"
);
assert!(
updated.anchors.contains(&"kn-sweep-rm-live".to_string()),
"Live anchor should be preserved"
);
}
#[test]
fn test_sweep_ghost_anchors_all_ghosts_on_entry() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-sweep-all-ghost", 5, 0.0);
entry.anchors = vec!["kn-ghost-x".to_string(), "kn-ghost-y".to_string()];
db.upsert_knowledge(&entry).unwrap();
let result = db.sweep_ghost_anchors(false).unwrap();
assert_eq!(result.ghosts_found, 2, "Both anchors should be ghosts");
assert_eq!(result.ghosts_removed, 2);
assert_eq!(result.affected_entries.len(), 1);
let ctx = crate::store::AgentContext::public_only();
let updated = db.get("kn-sweep-all-ghost", &ctx).unwrap().unwrap();
assert!(
updated.anchors.is_empty(),
"All ghost anchors should be removed"
);
}
#[test]
fn test_sweep_ghost_anchors_clean_graph() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry_a = make_test_entry("kn-clean-a", 5, 0.0);
entry_a.anchors = vec!["kn-clean-b".to_string()];
db.upsert_knowledge(&entry_a).unwrap();
let entry_b = make_test_entry("kn-clean-b", 3, 0.0);
db.upsert_knowledge(&entry_b).unwrap();
let result = db.sweep_ghost_anchors(true).unwrap();
assert_eq!(result.ghosts_found, 0, "Clean graph should have no ghosts");
assert_eq!(result.entries_scanned, 1, "One entry has anchors");
assert!(result.affected_entries.is_empty());
}
#[test]
fn test_sweep_ghost_anchors_no_anchored_entries() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-no-anchors", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
let result = db.sweep_ghost_anchors(true).unwrap();
assert_eq!(result.entries_scanned, 0);
assert_eq!(result.ghosts_found, 0);
assert!(result.affected_entries.is_empty());
}
#[test]
fn test_relationship_add_reinforces_target() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry_a = make_test_entry("kn-rel-src", 5, 0.0);
let entry_b = make_test_entry("kn-rel-tgt", 3, 0.0);
db.upsert_knowledge(&entry_a).unwrap();
db.upsert_knowledge(&entry_b).unwrap();
db.add_relationship("kn-rel-src", "kn-rel-tgt", "related")
.unwrap();
let result = db
.reinforce("kn-rel-tgt", 1, Some(10), &ctx)
.unwrap()
.expect("reinforce should succeed on visible target");
assert_eq!(result.old_resonance, 3);
assert_eq!(result.new_resonance, 4);
assert_eq!(result.amount_added, 1);
assert!(!result.capped);
let updated = db.get("kn-rel-tgt", &ctx).unwrap().unwrap();
assert_eq!(updated.resonance, 4);
}
#[test]
fn test_relationship_add_no_reinforce_skips() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry_a_src = make_test_entry("kn-noreinf-a-src", 5, 0.0);
let entry_a_tgt = make_test_entry("kn-noreinf-a-tgt", 3, 0.0);
db.upsert_knowledge(&entry_a_src).unwrap();
db.upsert_knowledge(&entry_a_tgt).unwrap();
db.add_relationship("kn-noreinf-a-src", "kn-noreinf-a-tgt", "related")
.unwrap();
db.reinforce("kn-noreinf-a-tgt", 1, Some(10), &ctx)
.unwrap()
.expect("reinforce should succeed");
let reinforced = db.get("kn-noreinf-a-tgt", &ctx).unwrap().unwrap();
assert_eq!(
reinforced.resonance, 4,
"Target resonance should increase from 3 to 4 when reinforced"
);
let entry_b_src = make_test_entry("kn-noreinf-b-src", 5, 0.0);
let entry_b_tgt = make_test_entry("kn-noreinf-b-tgt", 3, 0.0);
db.upsert_knowledge(&entry_b_src).unwrap();
db.upsert_knowledge(&entry_b_tgt).unwrap();
db.add_relationship("kn-noreinf-b-src", "kn-noreinf-b-tgt", "related")
.unwrap();
let not_reinforced = db.get("kn-noreinf-b-tgt", &ctx).unwrap().unwrap();
assert_eq!(
not_reinforced.resonance, 3,
"Target resonance should stay at 3 when --no-reinforce is set"
);
assert_ne!(
reinforced.resonance, not_reinforced.resonance,
"Reinforced and non-reinforced targets should have different resonance"
);
}
#[test]
fn test_relationship_contradicts_supersedes_skip_reinforce() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let src_c = make_test_entry("kn-contra-src", 5, 0.0);
let tgt_c = make_test_entry("kn-contra-tgt", 3, 0.0);
db.upsert_knowledge(&src_c).unwrap();
db.upsert_knowledge(&tgt_c).unwrap();
db.add_relationship("kn-contra-src", "kn-contra-tgt", "contradicts")
.unwrap();
let contra_target = db.get("kn-contra-tgt", &ctx).unwrap().unwrap();
assert_eq!(
contra_target.resonance, 3,
"contradicts target should NOT be reinforced (resonance stays at 3)"
);
let src_s = make_test_entry("kn-super-src", 5, 0.0);
let tgt_s = make_test_entry("kn-super-tgt", 3, 0.0);
db.upsert_knowledge(&src_s).unwrap();
db.upsert_knowledge(&tgt_s).unwrap();
db.add_relationship("kn-super-src", "kn-super-tgt", "supersedes")
.unwrap();
let super_target = db.get("kn-super-tgt", &ctx).unwrap().unwrap();
assert_eq!(
super_target.resonance, 3,
"supersedes target should NOT be reinforced (resonance stays at 3)"
);
let src_r = make_test_entry("kn-related-src", 5, 0.0);
let tgt_r = make_test_entry("kn-related-tgt", 3, 0.0);
db.upsert_knowledge(&src_r).unwrap();
db.upsert_knowledge(&tgt_r).unwrap();
db.add_relationship("kn-related-src", "kn-related-tgt", "related")
.unwrap();
db.reinforce("kn-related-tgt", 1, Some(10), &ctx)
.unwrap()
.expect("reinforce should succeed for related type");
let related_target = db.get("kn-related-tgt", &ctx).unwrap().unwrap();
assert_eq!(
related_target.resonance, 4,
"related target SHOULD be reinforced (resonance goes from 3 to 4)"
);
assert_eq!(contra_target.resonance, 3);
assert_eq!(super_target.resonance, 3);
assert_eq!(related_target.resonance, 4);
}
#[test]
fn test_search_select_activates_results() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut entry = make_test_entry("kn-search-sel", 5, 0.0);
entry.title = "unique searchable widget".to_string();
entry.body = Some("unique searchable widget content".to_string());
entry.activation_count = 0;
entry.last_activated = None;
db.upsert_knowledge(&entry).unwrap();
let filter = crate::store::KnowledgeFilter::default();
let results = db.search("widget", &ctx, &filter).unwrap();
assert!(!results.is_empty(), "Search should find the entry");
let ids: Vec<String> = results.iter().map(|e| e.id.clone()).collect();
db.update_activations(&ids).unwrap();
let activated = db.get("kn-search-sel", &ctx).unwrap().unwrap();
assert_eq!(
activated.activation_count, 1,
"activation_count should increment after --select"
);
assert!(
activated.last_activated.is_some(),
"last_activated should be set after --select"
);
}
#[test]
fn test_search_exclude_tags_drops_archived_keyword_match() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut archived = make_test_entry("kn-search-archived", 5, 0.0);
archived.title = "widget gadget archived".to_string();
archived.body = Some("unique widget content, archived tier".to_string());
archived.tags = vec!["tier/archived".to_string()];
db.upsert_knowledge(&archived).unwrap();
let mut live = make_test_entry("kn-search-live", 5, 0.0);
live.title = "widget gadget live".to_string();
live.body = Some("unique widget content, still live".to_string());
db.upsert_knowledge(&live).unwrap();
let db_filter = crate::store::KnowledgeFilter::default();
let raw_results = db.search("widget", &ctx, &db_filter).unwrap();
let raw_ids: Vec<&str> = raw_results.iter().map(|e| e.id.as_str()).collect();
assert!(
raw_ids.contains(&"kn-search-archived") && raw_ids.contains(&"kn-search-live"),
"fixture sanity check failed: both entries should match 'widget' \
before exclusion is applied, got {raw_ids:?}"
);
let entry_filter = crate::cli::EntryFilter {
exclude_tags: Some("tier/".to_string()),
..Default::default()
};
let filtered = crate::helpers::apply_entry_filters(raw_results, &entry_filter);
let filtered_ids: Vec<&str> = filtered.iter().map(|e| e.id.as_str()).collect();
assert!(
!filtered_ids.contains(&"kn-search-archived"),
"tier/archived entry must be dropped from keyword search results; got {filtered_ids:?}"
);
assert!(
filtered_ids.contains(&"kn-search-live"),
"the untagged match must survive exclusion; got {filtered_ids:?}"
);
}
#[test]
fn keyword_search_exclude_survives_past_hypothetical_limit() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
const N_ARCHIVED: usize = 50;
for i in 0..N_ARCHIVED {
let mut e = make_test_entry(&format!("kn-landmine-archived-{i:03}"), 5, 0.0);
e.title = "widget landmine archived".to_string();
e.body = Some("unique widget landmine content, archived tier".to_string());
e.tags = vec!["tier/archived".to_string()];
db.upsert_knowledge(&e).unwrap();
}
let mut live = make_test_entry("kn-landmine-live", 5, 0.0);
live.title = "widget landmine live".to_string();
live.body = Some("unique widget landmine content, still live".to_string());
db.upsert_knowledge(&live).unwrap();
let db_filter = crate::store::KnowledgeFilter::default();
let raw_results = db.search("widget landmine", &ctx, &db_filter).unwrap();
assert_eq!(
raw_results.len(),
N_ARCHIVED + 1,
"search_knowledge_async must return every matching row uncapped -- a \
future LIMIT smaller than the seeded set would shrink this count \
and silently break the no-DB-LIMIT contract apply_entry_filters \
relies on for exact exclusion"
);
let entry_filter = crate::cli::EntryFilter {
exclude_tags: Some("tier/".to_string()),
limit: Some(1),
..Default::default()
};
let filtered = crate::helpers::apply_entry_filters(raw_results, &entry_filter);
let filtered_ids: Vec<&str> = filtered.iter().map(|e| e.id.as_str()).collect();
assert_eq!(
filtered_ids,
vec!["kn-landmine-live"],
"the live entry must survive exclusion + limit truncation even \
though it sorts last among the seeded rows; got {filtered_ids:?}"
);
}
#[test]
fn test_search_select_no_results_is_noop() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let filter = crate::store::KnowledgeFilter::default();
let results = db
.search("xyzzy_nonexistent_query_12345", &ctx, &filter)
.unwrap();
assert!(results.is_empty(), "Should find no results");
let empty_ids: Vec<String> = vec![];
let result = db.update_activations(&empty_ids);
assert!(
result.is_ok(),
"update_activations with empty IDs should not error"
);
}
#[test]
fn test_update_activations_single_empty_id_is_noop() {
let db = SurrealDatabase::open_in_memory().unwrap();
let result = db.update_activations(&["".to_string()]);
assert!(result.is_ok(), "single empty id must not error: {result:?}");
let result = db.update_activations(&["kn-".to_string()]);
assert!(
result.is_ok(),
"single 'kn-' id (strips to empty) must not error: {result:?}"
);
}
#[test]
fn test_update_activations_all_digit_id_single_and_multi_path() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let entry_a = make_test_entry("kn-12345678", 5, 0.0);
db.upsert_knowledge(&entry_a).unwrap();
let entry_b = make_test_entry("kn-87654321", 5, 0.0);
db.upsert_knowledge(&entry_b).unwrap();
db.update_activations(&["kn-12345678".to_string()])
.expect("single all-digit id must not error");
let got_a = db
.get("kn-12345678", &ctx)
.unwrap()
.expect("entry must still exist");
assert_eq!(
got_a.activation_count, 1,
"single-id fast path must activate the all-digit id"
);
db.update_activations(&["kn-12345678".to_string(), "kn-87654321".to_string()])
.expect("multi all-digit id must not error");
let got_a2 = db
.get("kn-12345678", &ctx)
.unwrap()
.expect("entry must still exist");
let got_b = db
.get("kn-87654321", &ctx)
.unwrap()
.expect("entry must still exist");
assert_eq!(
got_a2.activation_count, 2,
"multi-id path must activate both all-digit ids"
);
assert_eq!(
got_b.activation_count, 1,
"multi-id path must activate both all-digit ids"
);
let result = db.update_activations(&["kn-99999999".to_string()]);
assert!(
result.is_ok(),
"single all-digit id with no matching row must be Ok(()), not an error: {result:?}"
);
}
fn make_pizza_test_entry() -> String {
let prefix = "Software engineering is a discipline that encompasses the systematic \
design, development, testing, and maintenance of software applications. \
The field has evolved significantly since its inception in the 1960s, \
when the term was first coined at the NATO Software Engineering Conference. \
Early software development was characterized by ad-hoc approaches and a lack \
of formal methodologies. The waterfall model emerged as one of the first \
structured approaches, dividing the development process into distinct phases: \
requirements analysis, design, implementation, testing, and maintenance. \
However, the rigidity of this approach led to the development of more flexible \
methodologies. Agile development, introduced through the Agile Manifesto in 2001, \
emphasized iterative development, collaboration, and adaptability. \
"
.repeat(4);
let pizza = "The art of pizza making is a fascinating departure from our main topic. \
A proper Neapolitan pizza requires a dough made from type 00 flour with 60-65% \
hydration, fermented for at least 24 hours. The sauce should be made from San \
Marzano tomatoes, crushed by hand, with nothing more than salt and fresh basil. \
Mozzarella di bufala, made from water buffalo milk, provides the ideal cheese \
topping. The pizza must be baked in a wood-fired oven at 485 degrees Celsius \
for exactly 60 to 90 seconds. The cornicione, or outer crust, should be puffy \
and leopard-spotted with char marks. A pizzaiolo trains for years to master the \
art of stretching dough by hand without tearing, creating a perfectly thin center \
with an airy, risen edge. The Associazione Verace Pizza Napoletana certifies \
pizzerias worldwide that meet their strict standards for authentic preparation.";
let suffix = "Returning to software engineering, modern practices include continuous \
integration and continuous deployment, microservices architecture, and cloud-native \
development. The rise of DevOps has blurred the traditional boundaries between \
development and operations teams, fostering a culture of shared responsibility. \
Container technologies like Docker and orchestration platforms like Kubernetes \
have revolutionized how applications are packaged and deployed. \
"
.repeat(4);
format!("{}\n\n{}\n\n{}", prefix, pizza, suffix)
}
#[test]
fn test_chunked_search_finds_buried_content() {
use crate::chunking::{ChunkConfig, chunk_text};
use crate::embeddings::{EmbeddingProvider, TractProvider};
use crate::store::KnowledgeStore;
let provider = TractProvider::new().expect("TractProvider should initialize");
let db = SurrealDatabase::open_in_memory().expect("in-memory DB should open");
let long_body = make_pizza_test_entry();
{
let counting_tok =
crate::embeddings::load_tokenizer().expect("load_tokenizer should succeed");
let encoding = counting_tok
.encode(long_body.as_str(), false)
.expect("tokenizer.encode should succeed");
assert!(
encoding.get_ids().len() > 512,
"Test body must exceed 512 tokens to validate chunked search (got {})",
encoding.get_ids().len()
);
}
let mut entry = make_test_entry("kn-pizza-deep", 5, 0.0);
entry.title = "Software Engineering History".to_string();
entry.body = Some(long_body);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let embedding_text = entry.embedding_text();
let config = ChunkConfig::default();
let chunking_tokenizer =
crate::embeddings::load_tokenizer().expect("load_tokenizer should succeed");
let chunks = chunk_text(&embedding_text, &chunking_tokenizer, &config);
assert!(
chunks.len() > 1,
"Entry should produce multiple chunks (got {})",
chunks.len()
);
let mut chunk_embeddings = Vec::with_capacity(chunks.len());
for chunk in &chunks {
chunk_embeddings.push(provider.embed(&chunk.text).unwrap());
}
db.delete_embedding_chunks("kn-pizza-deep").unwrap();
for (chunk, embedding) in chunks.iter().zip(chunk_embeddings.iter()) {
db.insert_embedding_chunk(
"kn-pizza-deep",
chunk.chunk_index,
&chunk.text,
chunk.token_offset,
chunk.token_count,
embedding,
provider.model_id(),
)
.unwrap();
}
let dims = provider.dimensions();
let mut mean_vec = vec![0.0f32; dims];
for emb in &chunk_embeddings {
for (i, v) in emb.iter().enumerate() {
mean_vec[i] += v;
}
}
let n = chunk_embeddings.len() as f32;
for v in mean_vec.iter_mut() {
*v /= n;
}
let l2: f32 = mean_vec.iter().map(|x| x * x).sum::<f32>().sqrt();
if l2 > 0.0 {
for v in mean_vec.iter_mut() {
*v /= l2;
}
}
entry.embedding = Some(mean_vec);
entry.embedding_model = Some(provider.model_id().to_string());
entry.embedded_at = Some(chrono::Utc::now().to_rfc3339());
entry.chunk_count = chunks.len() as i32;
entry.updated_at = Some(chrono::Utc::now().to_rfc3339());
db.upsert_knowledge(&entry).unwrap();
let query = "pizza making techniques and oven temperature";
let query_embedding = provider
.embed(query)
.expect("query embedding should succeed");
let filter = crate::store::KnowledgeFilter::default();
let results = db
.semantic_search(&query_embedding, &ctx, &filter, 10)
.unwrap();
let found = results.iter().any(|e| e.id == "kn-pizza-deep");
assert!(
found,
"Chunked semantic search should find the entry with buried pizza content. \
Got {} results: {:?}",
results.len(),
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
}
const BACKFILL_CHUNK_COUNT_SQL: &str = "UPDATE knowledge SET chunk_count = array::len(\
SELECT id FROM embedding_chunk \
WHERE entry_id = string::concat('kn-', meta::id($parent.id))) \
WHERE chunk_count IS NONE";
fn strand_chunk_count_none(db: &SurrealDatabase, record: &str) {
db.test_exec("REMOVE FIELD IF EXISTS chunk_count ON knowledge")
.unwrap();
db.test_exec(&format!("UPDATE {} SET chunk_count = NONE", record))
.unwrap();
db.test_exec("DEFINE FIELD IF NOT EXISTS chunk_count ON knowledge TYPE int DEFAULT 0")
.unwrap();
}
fn insert_n_chunks(db: &SurrealDatabase, entry_id: &str, n: usize) {
for i in 0..n {
db.insert_embedding_chunk(
entry_id,
i,
&format!("chunk {} text", i),
i * 10,
10,
&[0.1f32, 0.2, 0.3],
"test-model",
)
.unwrap();
}
}
#[test]
fn test_backfill_chunk_count_nonempty() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-w1chunked", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
insert_n_chunks(&db, "kn-w1chunked", 3);
strand_chunk_count_none(&db, "knowledge:w1chunked");
assert_eq!(
db.test_raw_chunk_count("kn-w1chunked").unwrap(),
None,
"precondition: chunk_count should be NONE before backfill"
);
db.test_exec(BACKFILL_CHUNK_COUNT_SQL).unwrap();
assert_eq!(
db.test_raw_chunk_count("kn-w1chunked").unwrap(),
Some(3),
"backfill must count the 3 embedding_chunk rows"
);
}
#[test]
fn test_backfill_chunk_count_zero_case() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-w1zero", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
strand_chunk_count_none(&db, "knowledge:w1zero");
assert_eq!(db.test_raw_chunk_count("kn-w1zero").unwrap(), None);
db.test_exec(BACKFILL_CHUNK_COUNT_SQL).unwrap();
assert_eq!(
db.test_raw_chunk_count("kn-w1zero").unwrap(),
Some(0),
"entry with no chunks must backfill to 0"
);
}
#[test]
fn test_backfill_chunk_count_idempotent() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-w1idem", 5, 0.5);
db.upsert_knowledge(&entry).unwrap();
insert_n_chunks(&db, "kn-w1idem", 2);
strand_chunk_count_none(&db, "knowledge:w1idem");
db.test_exec(BACKFILL_CHUNK_COUNT_SQL).unwrap();
assert_eq!(db.test_raw_chunk_count("kn-w1idem").unwrap(), Some(2));
insert_n_chunks(&db, "kn-w1idem", 5); db.test_exec(BACKFILL_CHUNK_COUNT_SQL).unwrap();
assert_eq!(
db.test_raw_chunk_count("kn-w1idem").unwrap(),
Some(2),
"re-running backfill must not change an already-set chunk_count"
);
}
fn strand_cold_upgrade_fields(db: &SurrealDatabase, record: &str) {
let fields: [(&str, &str); 7] = [
(
"triggers",
"DEFINE FIELD triggers ON knowledge TYPE array<string> DEFAULT []",
),
(
"wake_phrases",
"DEFINE FIELD wake_phrases ON knowledge TYPE array<string> DEFAULT []",
),
(
"chunk_count",
"DEFINE FIELD chunk_count ON knowledge TYPE int DEFAULT 0",
),
(
"format",
"DEFINE FIELD format ON knowledge TYPE string DEFAULT 'markdown' \
ASSERT $value IN ['markdown', 'json', 'stele:markdown', 'stele:ascii', \
'stele:light', 'stele:full']",
),
(
"resonance",
"DEFINE FIELD resonance ON knowledge TYPE int DEFAULT 0",
),
(
"activation_count",
"DEFINE FIELD activation_count ON knowledge TYPE int DEFAULT 0",
),
(
"decay_rate",
"DEFINE FIELD decay_rate ON knowledge TYPE float DEFAULT 0.0",
),
];
for (name, _) in fields {
db.test_exec(&format!("REMOVE FIELD IF EXISTS {} ON knowledge", name))
.unwrap();
}
let set_clause = fields
.iter()
.map(|(name, _)| format!("{} = NONE", name))
.collect::<Vec<_>>()
.join(", ");
db.test_exec(&format!("UPDATE {} SET {}", record, set_clause))
.unwrap();
for (_, strict_define) in fields {
db.test_exec(strict_define).unwrap();
}
}
fn raw_field(db: &SurrealDatabase, record: &str, field: &str) -> serde_json::Value {
let sql = format!("SELECT {0} FROM {1}", field, record);
SurrealDatabase::runtime().block_on(async {
let mut response = with_db!(db, db, { db.query(&sql).await.unwrap() });
let rows: Vec<serde_json::Value> = response.take(0).unwrap();
rows.into_iter()
.next()
.and_then(|r| r.get(field).cloned())
.unwrap_or(serde_json::Value::Null)
})
}
#[test]
fn test_cold_upgrade_apply_schema_heals_stranded_legacy_rows() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-legacy360", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
let entry_b = make_test_entry("kn-legacy360b", 5, 0.0);
db.upsert_knowledge(&entry_b).unwrap();
strand_cold_upgrade_fields(&db, "knowledge:legacy360");
strand_cold_upgrade_fields(&db, "knowledge:legacy360b");
assert!(
raw_field(&db, "knowledge:legacy360", "triggers").is_null(),
"precondition: triggers must be NONE before re-apply"
);
assert!(
raw_field(&db, "knowledge:legacy360", "chunk_count").is_null(),
"precondition: chunk_count must be NONE before re-apply"
);
assert!(
raw_field(&db, "knowledge:legacy360", "resonance").is_null(),
"precondition: resonance must be NONE before re-apply"
);
assert!(
raw_field(&db, "knowledge:legacy360", "activation_count").is_null(),
"precondition: activation_count must be NONE before re-apply"
);
assert!(
raw_field(&db, "knowledge:legacy360", "decay_rate").is_null(),
"precondition: decay_rate must be NONE before re-apply"
);
db.apply_schema_explicit(false)
.expect("apply_schema must succeed on a populated pre-existing graph (Issue #360)");
assert_eq!(
raw_field(&db, "knowledge:legacy360", "triggers"),
serde_json::json!([]),
"triggers must be backfilled to []"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "chunk_count"),
serde_json::json!(0),
"chunk_count must be backfilled to 0 (no chunks for this entry)"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "format"),
serde_json::json!("markdown"),
"format must be backfilled to 'markdown'"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "resonance"),
serde_json::json!(0),
"resonance must be backfilled to 0"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "activation_count"),
serde_json::json!(0),
"activation_count must be backfilled to 0"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "decay_rate"),
serde_json::json!(0.0),
"decay_rate must be backfilled to 0.0"
);
db.update_activations(&["kn-legacy360".to_string()])
.expect("ordinary write to a healed legacy row must succeed (Issue #360)");
assert_eq!(
raw_field(&db, "knowledge:legacy360", "activation_count"),
serde_json::json!(1),
"single-id fast path must have actually activated the row, not just returned Ok"
);
db.update_activations(&["kn-legacy360".to_string(), "kn-legacy360b".to_string()])
.expect(
"ordinary multi-id write (WHERE id IN $ids) to healed legacy rows \
must succeed (Issue #360)",
);
assert_eq!(
raw_field(&db, "knowledge:legacy360", "activation_count"),
serde_json::json!(2),
"multi-id path must have activated legacy360 again, not just returned Ok"
);
assert_eq!(
raw_field(&db, "knowledge:legacy360b", "activation_count"),
serde_json::json!(1),
"multi-id path must have activated legacy360b, not just returned Ok"
);
}
#[test]
fn test_cold_upgrade_apply_schema_idempotent_on_healthy_graph() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-healthy360", 5, 0.0);
db.upsert_knowledge(&entry).unwrap();
db.apply_schema_explicit(false).unwrap();
db.apply_schema_explicit(false).unwrap();
assert_eq!(
raw_field(&db, "knowledge:healthy360", "chunk_count"),
serde_json::json!(0)
);
assert_eq!(
raw_field(&db, "knowledge:healthy360", "triggers"),
serde_json::json!([])
);
assert_eq!(
raw_field(&db, "knowledge:healthy360", "format"),
serde_json::json!("markdown")
);
assert_eq!(
raw_field(&db, "knowledge:healthy360", "resonance"),
serde_json::json!(5),
"idempotent re-apply must NOT reset a healthy resonance to its default"
);
assert_eq!(
raw_field(&db, "knowledge:healthy360", "activation_count"),
serde_json::json!(0)
);
assert_eq!(
raw_field(&db, "knowledge:healthy360", "decay_rate"),
serde_json::json!(0.0)
);
db.update_activations(&["kn-healthy360".to_string()])
.expect("ordinary write after idempotent re-apply must succeed");
}
#[test]
fn test_triggers_round_trip_normalized_and_deduped() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-trig1", 5, 0.0);
entry.triggers = vec![
"Brad".to_string(),
" blood sugar ".to_string(),
"BLOOD SUGAR".to_string(), "Glucose".to_string(),
"".to_string(), " ".to_string(), ];
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let got = db
.get("kn-trig1", &ctx)
.unwrap()
.expect("entry should exist");
assert_eq!(
got.triggers,
vec![
"brad".to_string(),
"blood sugar".to_string(),
"glucose".to_string(),
],
"triggers must be normalized, deduped, and empty-dropped on write"
);
}
#[test]
fn test_triggers_default_empty_when_absent() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = make_test_entry("kn-trig2", 5, 0.0); db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let got = db
.get("kn-trig2", &ctx)
.unwrap()
.expect("entry should exist");
assert_eq!(
got.triggers,
Vec::<String>::new(),
"an entry with no triggers must read back as an empty array"
);
}
fn entry_with_dim_embedding(id: &str, embedding: Vec<f32>) -> crate::knowledge::KnowledgeEntry {
let mut e = make_test_entry(id, 5, 0.0);
e.content_hash = Some(format!("hash-{id}"));
e.embedding = Some(embedding);
e.embedding_model = Some("test-model".to_string());
e.embedded_at = Some(chrono::Utc::now().to_rfc3339());
e
}
#[test]
fn off_dim_row_does_not_abort_cosine_scan() {
use crate::store::{AgentContext, KnowledgeStore};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
let mut good_a = vec![0.0f32; N];
good_a[0] = 1.0; let mut good_b = vec![0.0f32; N];
good_b[1] = 1.0; db.upsert_knowledge(&entry_with_dim_embedding("kn-good-a", good_a.clone()))
.unwrap();
db.upsert_knowledge(&entry_with_dim_embedding("kn-good-b", good_b))
.unwrap();
db.upsert_knowledge(&entry_with_dim_embedding(
"kn-poison-dim4",
vec![1.0, 0.0, 0.0, 0.0],
))
.unwrap();
let ctx = AgentContext::public_only();
let filter = crate::store::KnowledgeFilter::default();
let query = good_a;
let results = db
.semantic_search(&query, &ctx, &filter, 10)
.expect("semantic_search must skip the off-dim row, not abort the scan");
let ids: Vec<&str> = results.iter().map(|e| e.id.as_str()).collect();
assert!(
ids.contains(&"kn-good-a"),
"the matching good row must be returned; got {ids:?}"
);
assert!(
ids.contains(&"kn-good-b"),
"the second good row must be returned; got {ids:?}"
);
assert!(
!ids.contains(&"kn-poison-dim4"),
"the off-dim row must be skipped by the guard; got {ids:?}"
);
let scored = db
.semantic_search_entries_scored(&query, &ctx, 10)
.expect("entries-scored search must skip the off-dim row, not abort the scan");
let scored_ids: Vec<&str> = scored.iter().map(|(e, _)| e.id.as_str()).collect();
assert!(
scored_ids.contains(&"kn-good-a") && scored_ids.contains(&"kn-good-b"),
"entry-level scored search must return the good rows; got {scored_ids:?}"
);
assert!(
!scored_ids.contains(&"kn-poison-dim4"),
"entry-level scored search must skip the off-dim row; got {scored_ids:?}"
);
}
#[test]
fn test_get_applicability_for_entry_returns_written_targets() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-applies-read", 5, 0.01);
entry.applicability = vec!["backend".to_string()];
db.upsert_knowledge(&entry).unwrap();
let applicability = db
.get_applicability_for_entry("kn-applies-read")
.expect("applicability read must not fail");
assert_eq!(
applicability,
vec!["backend".to_string()],
"written applies_to edge must be read back"
);
}
fn dedup_candidate_entry(
id: &str,
session_id: &str,
owner: Option<&str>,
visibility: &str,
title: &str,
body: &str,
) -> crate::knowledge::KnowledgeEntry {
crate::knowledge::KnowledgeEntry {
session_id: Some(session_id.to_string()),
owner: owner.map(String::from),
visibility: visibility.to_string(),
title: title.to_string(),
body: Some(body.to_string()),
..make_test_entry(id, 3, 0.0)
}
}
fn dedup_candidate_entry_with_category(
id: &str,
session_id: &str,
owner: Option<&str>,
category_id: &str,
visibility: &str,
title: &str,
body: &str,
) -> crate::knowledge::KnowledgeEntry {
crate::knowledge::KnowledgeEntry {
category_id: category_id.to_string(),
..dedup_candidate_entry(id, session_id, owner, visibility, title, body)
}
}
#[test]
fn test_get_entries_for_session_field_scoped_not_edge_scoped() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = dedup_candidate_entry(
"kn-field-scoped",
"sess-1",
Some("soren"),
"public",
"A Title",
"Some body",
);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::for_agent("soren");
let candidates = db
.get_entries_for_session("sess-1", Some("soren"), "test", &ctx)
.unwrap();
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].id, "kn-field-scoped");
assert_eq!(candidates[0].title, "A Title");
assert_eq!(candidates[0].body, "Some body");
}
#[test]
fn test_get_entries_for_session_owner_scope_excludes_other_owners_public_entry() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry_b = dedup_candidate_entry(
"kn-owner-b-public",
"sess-1",
Some("agent-b"),
"public",
"Shared Title",
"Shared body",
);
db.upsert_knowledge(&entry_b).unwrap();
let ctx = crate::store::AgentContext::for_agent("agent-a");
let candidates = db
.get_entries_for_session("sess-1", Some("agent-a"), "test", &ctx)
.unwrap();
assert!(
candidates.is_empty(),
"agent-a's candidate set must never include agent-b's public entry; got {candidates:?}"
);
}
#[test]
fn test_search_knowledge_returns_applicability() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-applies-search", 5, 0.01);
entry.title = "Applicability Search Target".to_string();
entry.applicability = vec!["backend".to_string()];
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let filter = crate::store::KnowledgeFilter::default();
let results = db
.search_knowledge("Applicability Search Target", &ctx, &filter)
.expect("search must not fail");
let found = results
.iter()
.find(|e| e.id == "kn-applies-search")
.expect("upserted entry must be found by search");
assert_eq!(
found.applicability,
vec!["backend".to_string()],
"search results must carry the written applies_to edge"
);
}
#[test]
fn test_get_knowledge_returns_applicability() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = make_test_entry("kn-applies-get", 5, 0.01);
entry.applicability = vec!["backend".to_string()];
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::public_only();
let fetched = db
.get("kn-applies-get", &ctx)
.expect("get_knowledge must not fail when the entry has applicability")
.expect("entry must be found");
assert_eq!(
fetched.applicability,
vec!["backend".to_string()],
"get_knowledge must carry the written applies_to edge through"
);
}
#[test]
fn test_get_entries_for_session_same_owner_private_entry_is_included() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = dedup_candidate_entry(
"kn-owner-a-private",
"sess-1",
Some("agent-a"),
"private",
"Private Title",
"Private body",
);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::for_agent("agent-a");
let candidates = db
.get_entries_for_session("sess-1", Some("agent-a"), "test", &ctx)
.unwrap();
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].id, "kn-owner-a-private");
}
#[test]
fn test_get_entries_for_session_none_owner_matches_only_unowned_rows() {
let db = SurrealDatabase::open_in_memory().unwrap();
let unowned = dedup_candidate_entry(
"kn-unowned",
"sess-1",
None,
"public",
"Unowned Title",
"Unowned body",
);
let owned = dedup_candidate_entry(
"kn-owned",
"sess-1",
Some("agent-a"),
"public",
"Owned Title",
"Owned body",
);
db.upsert_knowledge(&unowned).unwrap();
db.upsert_knowledge(&owned).unwrap();
let ctx = crate::store::AgentContext::public_only();
let candidates = db
.get_entries_for_session("sess-1", None, "test", &ctx)
.unwrap();
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].id, "kn-unowned");
}
#[test]
fn test_get_entries_for_session_scoped_to_session_field_only() {
let db = SurrealDatabase::open_in_memory().unwrap();
let entry = dedup_candidate_entry(
"kn-other-session",
"sess-2",
Some("agent-a"),
"public",
"Title",
"Body",
);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::for_agent("agent-a");
let candidates = db
.get_entries_for_session("sess-1", Some("agent-a"), "test", &ctx)
.unwrap();
assert!(candidates.is_empty());
}
#[test]
fn test_get_entries_for_session_category_scope_excludes_other_categories() {
let db = SurrealDatabase::open_in_memory().unwrap();
let other_category = dedup_candidate_entry_with_category(
"kn-other-category",
"sess-1",
Some("agent-a"),
"discovery",
"public",
"Shared Title",
"Shared body",
);
db.upsert_knowledge(&other_category).unwrap();
let ctx = crate::store::AgentContext::for_agent("agent-a");
let candidates = db
.get_entries_for_session("sess-1", Some("agent-a"), "recipe", &ctx)
.unwrap();
assert!(
candidates.is_empty(),
"a 'recipe'-category query must never surface a 'discovery'-category row \
with identical title+body; got {candidates:?}"
);
let same_category = db
.get_entries_for_session("sess-1", Some("agent-a"), "discovery", &ctx)
.unwrap();
assert_eq!(same_category.len(), 1);
assert_eq!(same_category[0].id, "kn-other-category");
}
#[test]
fn test_get_entries_for_session_projection_has_no_embedding_field() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut entry = dedup_candidate_entry(
"kn-no-embed-leak",
"sess-1",
Some("agent-a"),
"public",
"Title",
"Body",
);
entry.embedding = Some(vec![0.1; 768]);
db.upsert_knowledge(&entry).unwrap();
let ctx = crate::store::AgentContext::for_agent("agent-a");
let candidates = db
.get_entries_for_session("sess-1", Some("agent-a"), "test", &ctx)
.unwrap();
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].id, "kn-no-embed-leak");
}
fn entry_with_embedding_and_tags(
id: &str,
embedding: Vec<f32>,
tags: &[&str],
) -> crate::knowledge::KnowledgeEntry {
let mut e = make_test_entry(id, 5, 0.0);
e.content_hash = Some(format!("hash-{id}"));
e.embedding = Some(embedding);
e.embedding_model = Some("test-model".to_string());
e.embedded_at = Some(chrono::Utc::now().to_rfc3339());
e.tags = tags.iter().map(|s| s.to_string()).collect();
e
}
#[test]
fn semantic_search_excludes_top_ranked_archived_neighbors_returns_full_limit() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
for id in ["kn-archived-1", "kn-archived-2"] {
db.upsert_knowledge(&entry_with_embedding_and_tags(
id,
query.clone(),
&["tier/archived"],
))
.unwrap();
}
let mut good_vecs = Vec::new();
for i in 0..3usize {
let mut v = vec![0.0f32; N];
v[0] = 0.9;
v[1 + i] = (1.0f32 - 0.81f32).sqrt(); good_vecs.push(v);
}
for (i, v) in good_vecs.into_iter().enumerate() {
db.upsert_knowledge(&entry_with_embedding_and_tags(
&format!("kn-good-{i}"),
v,
&[],
))
.unwrap();
}
let ctx = AgentContext::public_only();
let unfiltered_filter = KnowledgeFilter::default();
let unfiltered = db
.semantic_search(&query, &ctx, &unfiltered_filter, 2)
.unwrap();
let unfiltered_ids: Vec<&str> = unfiltered.iter().map(|e| e.id.as_str()).collect();
assert!(
unfiltered_ids.contains(&"kn-archived-1") && unfiltered_ids.contains(&"kn-archived-2"),
"fixture sanity check failed: archived entries should rank in the \
unfiltered top 2, got {unfiltered_ids:?}"
);
let exclude_filter = KnowledgeFilter {
exclude_tag_prefixes: vec!["tier/".to_string()],
..Default::default()
};
let results = db
.semantic_search(&query, &ctx, &exclude_filter, 2)
.unwrap();
assert_eq!(
results.len(),
2,
"full requested limit must survive exclusion even when the top-ranked \
neighbors are all archived; got {:?}",
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
for entry in &results {
assert!(
!entry.tags.iter().any(|t| t.starts_with("tier/")),
"an archived entry leaked into exclusion-filtered results: {}",
entry.id
);
}
}
fn seed_chunked_entry_with_tags(
db: &SurrealDatabase,
id: &str,
chunk_embedding: Vec<f32>,
tags: &[&str],
) {
let mut e = make_test_entry(id, 5, 0.0);
e.content_hash = Some(format!("hash-{id}"));
e.tags = tags.iter().map(|s| s.to_string()).collect();
e.chunk_count = 1;
db.upsert_knowledge(&e).unwrap();
db.insert_embedding_chunk(id, 0, "chunk text", 0, 10, &chunk_embedding, "test-model")
.unwrap();
}
#[test]
fn semantic_search_excludes_top_ranked_archived_chunk_neighbors_returns_full_limit() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
const LIMIT: usize = 2;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
for i in 0..8 {
seed_chunked_entry_with_tags(
&db,
&format!("kn-chunk-archived-{i}"),
query.clone(),
&["tier/archived"],
);
}
for i in 0..LIMIT {
let mut v = vec![0.0f32; N];
v[0] = 0.9;
v[1 + i] = (1.0f32 - 0.81f32).sqrt(); seed_chunked_entry_with_tags(&db, &format!("kn-chunk-good-{i}"), v, &[]);
}
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter {
exclude_tag_prefixes: vec!["tier/".to_string()],
..Default::default()
};
let results = db.semantic_search(&query, &ctx, &filter, LIMIT).unwrap();
assert_eq!(
results.len(),
LIMIT,
"full requested limit must survive exclusion in the embedding_chunk \
phase even when a fixed over-fetch window would have been entirely \
consumed by archived candidates; got {:?}",
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
for entry in &results {
assert!(
!entry.tags.iter().any(|t| t.starts_with("tier/")),
"an archived chunked entry leaked into exclusion-filtered results: {}",
entry.id
);
}
}
#[test]
fn semantic_search_exclude_tags_prefix_match_drops_tier_archived_only() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-archived",
query.clone(),
&["tier/archived"],
))
.unwrap();
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-project",
query.clone(),
&["project/x"],
))
.unwrap();
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter {
exclude_tag_prefixes: vec!["tier/".to_string()],
..Default::default()
};
let results = db.semantic_search(&query, &ctx, &filter, 10).unwrap();
let ids: Vec<&str> = results.iter().map(|e| e.id.as_str()).collect();
assert!(
!ids.contains(&"kn-archived"),
"tier/archived must be dropped by --exclude-tags 'tier/'; got {ids:?}"
);
assert!(
ids.contains(&"kn-project"),
"an entry tagged project/x must survive a 'tier/' exclusion; got {ids:?}"
);
}
#[test]
fn semantic_search_multi_prefix_exclusion_drops_both_namespaces_at_db_level() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-tier-archived",
query.clone(),
&["tier/archived"],
))
.unwrap();
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-scratch-x",
query.clone(),
&["scratch/x"],
))
.unwrap();
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-unrelated",
query.clone(),
&["project/y"],
))
.unwrap();
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter {
exclude_tag_prefixes: vec!["tier/".to_string(), "scratch/".to_string()],
..Default::default()
};
let results = db.semantic_search(&query, &ctx, &filter, 10).unwrap();
let ids: Vec<&str> = results.iter().map(|e| e.id.as_str()).collect();
assert!(
!ids.contains(&"kn-tier-archived"),
"tier/archived must be dropped by a multi-prefix exclusion including 'tier/'; got {ids:?}"
);
assert!(
!ids.contains(&"kn-scratch-x"),
"scratch/x must be dropped by a multi-prefix exclusion including 'scratch/'; got {ids:?}"
);
assert!(
ids.contains(&"kn-unrelated"),
"an entry tagged project/y must survive a ['tier/', 'scratch/'] exclusion; got {ids:?}"
);
}
#[test]
fn semantic_search_no_exclude_tags_behavior_unchanged() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
db.upsert_knowledge(&entry_with_embedding_and_tags(
"kn-archived",
query.clone(),
&["tier/archived"],
))
.unwrap();
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter::default();
let results = db.semantic_search(&query, &ctx, &filter, 10).unwrap();
let ids: Vec<&str> = results.iter().map(|e| e.id.as_str()).collect();
assert!(
ids.contains(&"kn-archived"),
"with no --exclude-tags, an archived entry must still be returned; got {ids:?}"
);
}
#[test]
fn semantic_search_no_flag_category_thinned_chunk_query_fills_to_limit() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
const N: usize = 8;
const LIMIT: usize = 2;
let mut query = vec![0.0f32; N];
query[0] = 1.0;
for i in 0..8 {
let mut e = make_test_entry(&format!("kn-noise-{i}"), 5, 0.0);
e.content_hash = Some(format!("hash-kn-noise-{i}"));
e.category_id = "noise".to_string();
e.chunk_count = 1;
db.upsert_knowledge(&e).unwrap();
db.insert_embedding_chunk(&e.id, 0, "chunk text", 0, 10, &query, "test-model")
.unwrap();
}
for i in 0..LIMIT {
let mut v = vec![0.0f32; N];
v[0] = 0.9;
v[1 + i] = (1.0f32 - 0.81f32).sqrt(); let mut e = make_test_entry(&format!("kn-good-{i}"), 5, 0.0);
e.content_hash = Some(format!("hash-kn-good-{i}"));
e.category_id = "good".to_string();
e.chunk_count = 1;
db.upsert_knowledge(&e).unwrap();
db.insert_embedding_chunk(&e.id, 0, "chunk text", 0, 10, &v, "test-model")
.unwrap();
}
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter {
categories: Some(vec!["good".to_string()]),
..Default::default()
};
let results = db.semantic_search(&query, &ctx, &filter, LIMIT).unwrap();
assert_eq!(
results.len(),
LIMIT,
"full requested limit must be reached via category filtering alone \
(no --exclude-tags) even when a fixed over-fetch window would have \
been entirely consumed by out-of-category candidates; got {:?}",
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
for entry in &results {
assert_eq!(
entry.category_id, "good",
"an out-of-category entry leaked into category-filtered results: {}",
entry.id
);
}
}
#[test]
fn batch_tag_hydration_agrees_with_the_single_entry_path() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut e = make_test_entry("kn-batch-tags", 5, 0.0);
e.content_hash = Some("hash-kn-batch-tags".to_string());
e.tags = vec!["tier/archived".to_string(), "project/x".to_string()];
db.upsert_knowledge(&e).unwrap();
let mut single = db.get_tags_for_entry("kn-batch-tags").unwrap();
single.sort();
assert_eq!(
single,
vec!["project/x".to_string(), "tier/archived".to_string()],
"precondition: the single-entry path must see the tags that were just \
written, otherwise this test proves nothing about the batch path"
);
let batched = SurrealDatabase::runtime()
.block_on(db.get_tags_for_entries_async(&["kn-batch-tags".to_string()]))
.unwrap();
let mut batch_tags = batched.get("kn-batch-tags").cloned().unwrap_or_default();
batch_tags.sort();
assert_eq!(
batch_tags, single,
"the batch tag query must return what the single-entry query returns; \
an empty result here means the WHERE clause matched nothing and every \
caller will read the entry as untagged"
);
}
#[test]
fn batch_applicability_hydration_returns_the_edge_that_was_written() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut e = make_test_entry("kn-batch-app", 5, 0.0);
e.content_hash = Some("hash-kn-batch-app".to_string());
e.applicability = vec!["backend".to_string()];
db.upsert_knowledge(&e).unwrap();
let batched = SurrealDatabase::runtime()
.block_on(db.get_applicability_for_entries_async(&["kn-batch-app".to_string()]))
.unwrap();
assert_eq!(
batched.get("kn-batch-app").cloned().unwrap_or_default(),
vec!["backend".to_string()],
"the batch applicability query must return the applies_to edge that \
upsert_knowledge wrote; it shares the broken WHERE shape with the tag \
query and returns nothing for every entry"
);
}
#[test]
fn semantic_search_exclude_tags_drops_a_chunked_entry_with_no_eligible_neighbors() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
let mut query = vec![0.0f32; 8];
query[0] = 1.0;
seed_chunked_entry_with_tags(&db, "kn-only-archived", query.clone(), &["tier/archived"]);
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter {
exclude_tag_prefixes: vec!["tier/".to_string()],
..Default::default()
};
let results = db.semantic_search(&query, &ctx, &filter, 5).unwrap();
assert!(
results.is_empty(),
"--exclude-tags 'tier/' must drop a chunked entry tagged tier/archived; \
got {:?}",
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
}
#[test]
fn semantic_search_returns_chunked_entries_with_their_tags_hydrated() {
use crate::store::{AgentContext, KnowledgeFilter};
let db = SurrealDatabase::open_in_memory().unwrap();
let mut query = vec![0.0f32; 8];
query[0] = 1.0;
seed_chunked_entry_with_tags(&db, "kn-tagged-chunk", query.clone(), &["project/x"]);
let ctx = AgentContext::public_only();
let filter = KnowledgeFilter::default();
let results = db.semantic_search(&query, &ctx, &filter, 5).unwrap();
assert_eq!(
results.len(),
1,
"precondition: the chunked entry must be found at all; got {:?}",
results.iter().map(|e| e.id.as_str()).collect::<Vec<_>>()
);
assert_eq!(
results[0].tags,
vec!["project/x".to_string()],
"a chunked entry must come back with its tags; empty tags here mean the \
batch hydration dropped them and every tag assertion downstream is \
vacuous"
);
}
fn core_layer_entry(id: &str, tags: &[&str]) -> crate::knowledge::KnowledgeEntry {
let mut e = make_test_entry(id, 9, 0.0);
e.resonance_type = Some("foundational".to_string());
e.last_activated = Some((chrono::Utc::now() - chrono::Duration::days(60)).to_rfc3339());
e.tags = tags.iter().map(|t| t.to_string()).collect();
e
}
fn recent_layer_entry(id: &str, tags: &[&str]) -> crate::knowledge::KnowledgeEntry {
let mut e = make_test_entry(id, 3, 0.0);
e.resonance_type = Some("operational".to_string());
e.last_activated = Some(chrono::Utc::now().to_rfc3339());
e.tags = tags.iter().map(|t| t.to_string()).collect();
e
}
fn bridge_layer_entry(id: &str, anchor: &str, tags: &[&str]) -> crate::knowledge::KnowledgeEntry {
let mut e = make_test_entry(id, 6, 0.0);
e.resonance_type = Some("relational".to_string());
e.last_activated = Some((chrono::Utc::now() - chrono::Duration::days(60)).to_rfc3339());
e.anchors = vec![anchor.to_string()];
e.tags = tags.iter().map(|t| t.to_string()).collect();
e
}
fn cascade_ids(cascade: &crate::store::WakeCascade) -> Vec<String> {
cascade.all_ids()
}
#[test]
fn wake_cascade_excludes_tagged_entries_from_the_core_layer() {
for tag in crate::store::WAKE_EXCLUDED_TAGS {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-keep", &[]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-drop", &[tag]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
let ids = cascade_ids(&cascade);
assert!(ids.contains(&"kn-keep".to_string()), "tag {tag}: {ids:?}");
assert!(!ids.contains(&"kn-drop".to_string()), "tag {tag}: {ids:?}");
assert_eq!(cascade.excluded.get(tag), Some(&1), "tag {tag}");
let cascade = db.wake_cascade(&ctx, 50, None, 7, true).unwrap();
let ids = cascade_ids(&cascade);
assert!(ids.contains(&"kn-drop".to_string()), "tag {tag}: {ids:?}");
assert!(cascade.excluded.is_empty(), "tag {tag}");
}
}
#[test]
fn wake_cascade_excludes_tagged_entries_from_the_recent_layer() {
for tag in crate::store::WAKE_EXCLUDED_TAGS {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-core", &[]))
.unwrap();
db.upsert_knowledge(&recent_layer_entry("kn-recent-keep", &[]))
.unwrap();
db.upsert_knowledge(&recent_layer_entry("kn-recent-drop", &[tag]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
let recent: Vec<&str> = cascade.recent.iter().map(|e| e.id.as_str()).collect();
assert!(recent.contains(&"kn-recent-keep"), "tag {tag}: {recent:?}");
assert!(!recent.contains(&"kn-recent-drop"), "tag {tag}: {recent:?}");
assert_eq!(cascade.excluded.get(tag), Some(&1), "tag {tag}");
let cascade = db.wake_cascade(&ctx, 50, None, 7, true).unwrap();
let recent: Vec<&str> = cascade.recent.iter().map(|e| e.id.as_str()).collect();
assert!(recent.contains(&"kn-recent-drop"), "tag {tag}: {recent:?}");
}
}
#[test]
fn wake_cascade_excludes_tagged_entries_from_the_bridge_layer() {
for tag in crate::store::WAKE_EXCLUDED_TAGS {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-anchor", &[]))
.unwrap();
db.upsert_knowledge(&bridge_layer_entry("kn-bridge-keep", "kn-anchor", &[]))
.unwrap();
db.upsert_knowledge(&bridge_layer_entry("kn-bridge-drop", "kn-anchor", &[tag]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
let bridges: Vec<&str> = cascade.bridges.iter().map(|e| e.id.as_str()).collect();
assert!(
bridges.contains(&"kn-bridge-keep"),
"precondition: the bridge layer must fire at all; tag {tag}: {bridges:?}"
);
assert!(
!bridges.contains(&"kn-bridge-drop"),
"tag {tag}: {bridges:?}"
);
assert_eq!(cascade.excluded.get(tag), Some(&1), "tag {tag}");
let cascade = db.wake_cascade(&ctx, 50, None, 7, true).unwrap();
let bridges: Vec<&str> = cascade.bridges.iter().map(|e| e.id.as_str()).collect();
assert!(
bridges.contains(&"kn-bridge-drop"),
"tag {tag}: {bridges:?}"
);
}
}
#[test]
fn wake_cascade_excludes_tagged_entries_on_the_min_resonance_path() {
for tag in crate::store::WAKE_EXCLUDED_TAGS {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-keep", &[]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-drop", &[tag]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, Some(9), 7, false).unwrap();
let ids = cascade_ids(&cascade);
assert_eq!(ids, vec!["kn-keep".to_string()], "tag {tag}");
assert_eq!(cascade.excluded.get(tag), Some(&1), "tag {tag}");
let cascade = db.wake_cascade(&ctx, 50, Some(9), 7, true).unwrap();
assert_eq!(cascade_ids(&cascade).len(), 2, "tag {tag}");
}
}
#[test]
fn wake_cascade_matches_excluded_tags_exactly_not_by_prefix() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-archived", &["archived"]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-archive-slash", &["archive/2026"]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-wake-exclude-ish", &["wake-excluded"]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-exact", &["archive"]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
let ids = cascade_ids(&cascade);
for near_miss in ["kn-archived", "kn-archive-slash", "kn-wake-exclude-ish"] {
assert!(
ids.contains(&near_miss.to_string()),
"{near_miss} is not tagged with an excluded tag and must stay: {ids:?}"
);
}
assert!(!ids.contains(&"kn-exact".to_string()), "{ids:?}");
assert_eq!(cascade.excluded.get("archive"), Some(&1));
}
#[test]
fn wake_cascade_counts_a_doubly_qualifying_excluded_entry_once() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-core", &[]))
.unwrap();
let mut both = core_layer_entry("kn-both", &["archive"]);
both.last_activated = Some(chrono::Utc::now().to_rfc3339());
db.upsert_knowledge(&both).unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
assert_eq!(cascade.excluded.get("archive"), Some(&1));
}
#[test]
fn the_core_query_widens_its_window_repeatedly_until_the_limit_is_filled() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let layout = [
("kn-x0", true),
("kn-x1", true),
("kn-k2", false),
("kn-x3", true),
("kn-x4", true),
("kn-k5", false),
];
for (order, (id, excluded)) in layout.iter().enumerate() {
let tags: &[&str] = if *excluded { &["archive"] } else { &[] };
let mut entry = core_layer_entry(id, tags);
entry.wake_order = Some(order as i32);
db.upsert_knowledge(&entry).unwrap();
}
let cascade = db.wake_cascade(&ctx, 2, None, 7, false).unwrap();
assert_eq!(
cascade_ids(&cascade),
vec!["kn-k2".to_string(), "kn-k5".to_string()],
"an excluded entry must not cost a kept one its slot, however many \
passes that takes"
);
assert_eq!(cascade.excluded.get("archive"), Some(&4));
}
#[test]
fn the_excluded_count_ignores_entries_ranked_below_the_wake_set() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let layout = [
("kn-k0", false),
("kn-x1", true),
("kn-k2", false),
("kn-x3", true),
("kn-x4", true),
];
for (order, (id, excluded)) in layout.iter().enumerate() {
let tags: &[&str] = if *excluded { &["archive"] } else { &[] };
let mut entry = core_layer_entry(id, tags);
entry.wake_order = Some(order as i32);
db.upsert_knowledge(&entry).unwrap();
}
let cascade = db.wake_cascade(&ctx, 2, None, 7, false).unwrap();
assert_eq!(
cascade_ids(&cascade),
vec!["kn-k0".to_string(), "kn-k2".to_string()]
);
assert_eq!(
cascade.excluded.get("archive"),
Some(&1),
"only the excluded entry that displaced a slot counts: {:?}",
cascade.excluded
);
}
#[test]
fn the_excluded_count_is_an_upper_bound_when_exclusions_interleave() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let layout = [
("kn-x0", true),
("kn-k1", false),
("kn-x2", true),
("kn-k3", false),
];
for (order, (id, excluded)) in layout.iter().enumerate() {
let tags: &[&str] = if *excluded { &["archive"] } else { &[] };
let mut entry = core_layer_entry(id, tags);
entry.wake_order = Some(order as i32);
db.upsert_knowledge(&entry).unwrap();
}
let cascade = db.wake_cascade(&ctx, 2, None, 7, false).unwrap();
assert_eq!(
cascade_ids(&cascade),
vec!["kn-k1".to_string(), "kn-k3".to_string()],
"the wake set itself is exact: the two kept entries, in order"
);
assert_eq!(
cascade.excluded.get("archive"),
Some(&2),
"both excluded entries were walked past while filling two slots"
);
let untagged = db.wake_cascade(&ctx, 2, None, 7, true).unwrap();
assert_eq!(
cascade_ids(&untagged),
vec!["kn-x0".to_string(), "kn-k1".to_string()],
"only one of the two would have made the cut untagged, which is why \
the reported count is an upper bound and is described as one"
);
}
#[test]
fn a_limit_of_zero_returns_an_empty_cascade_without_spinning() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-keep", &[]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-drop", &["archive"]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 0, None, 7, false).unwrap();
assert!(cascade_ids(&cascade).is_empty(), "nothing was asked for");
assert!(
cascade.excluded.is_empty(),
"no slot was contested, so nothing was kept out of anything: {:?}",
cascade.excluded
);
}
#[test]
fn wake_cascade_counts_an_entry_carrying_both_excluded_tags_once() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-keep", &[]))
.unwrap();
db.upsert_knowledge(&core_layer_entry(
"kn-both-tags",
&["archive", "wake-exclude"],
))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
assert_eq!(cascade_ids(&cascade), vec!["kn-keep".to_string()]);
assert_eq!(
cascade.excluded.len(),
1,
"one entry is one drop, not one per tag: {:?}",
cascade.excluded
);
assert_eq!(cascade.excluded.get("archive"), Some(&1));
assert_eq!(cascade.excluded.get("wake-exclude"), None);
}
#[test]
fn wake_cascade_entries_carry_their_tags_in_every_layer() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-core", &["core-tag"]))
.unwrap();
db.upsert_knowledge(&recent_layer_entry("kn-recent", &["recent-tag"]))
.unwrap();
db.upsert_knowledge(&bridge_layer_entry("kn-bridge", "kn-core", &["bridge-tag"]))
.unwrap();
let cascade = db.wake_cascade(&ctx, 50, None, 7, false).unwrap();
let tags_of = |layer: &[crate::knowledge::KnowledgeEntry], id: &str| -> Vec<String> {
layer
.iter()
.find(|e| e.id == id)
.unwrap_or_else(|| panic!("{id} missing from its layer"))
.tags
.clone()
};
assert_eq!(tags_of(&cascade.core, "kn-core"), vec!["core-tag"]);
assert_eq!(tags_of(&cascade.recent, "kn-recent"), vec!["recent-tag"]);
assert_eq!(tags_of(&cascade.bridges, "kn-bridge"), vec!["bridge-tag"]);
let by_resonance = db.wake_cascade(&ctx, 50, Some(9), 7, false).unwrap();
assert_eq!(tags_of(&by_resonance.core, "kn-core"), vec!["core-tag"]);
}
#[test]
fn a_wake_order_of_zero_round_trips_as_zero() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
let mut zero = core_layer_entry("kn-zero", &[]);
zero.wake_order = Some(0);
db.upsert_knowledge(&zero).unwrap();
let mut unset = core_layer_entry("kn-unset", &[]);
unset.wake_order = None;
db.upsert_knowledge(&unset).unwrap();
assert_eq!(
db.get("kn-zero", &ctx).unwrap().unwrap().wake_order,
Some(0),
"a stored wake_order of 0 must read back as 0, not as unset"
);
assert_eq!(db.get("kn-unset", &ctx).unwrap().unwrap().wake_order, None);
}
#[test]
fn an_entry_with_wake_order_zero_opens_the_ritual() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-aaa", &[]))
.unwrap();
let mut opener = core_layer_entry("kn-zzz", &[]);
opener.wake_order = Some(0);
db.upsert_knowledge(&opener).unwrap();
let mut later = core_layer_entry("kn-mmm", &[]);
later.wake_order = Some(5);
db.upsert_knowledge(&later).unwrap();
for min_resonance in [None, Some(9)] {
let ordered = cascade_ids(&db.wake_cascade(&ctx, 50, min_resonance, 7, false).unwrap());
assert_eq!(
ordered.first().map(String::as_str),
Some("kn-zzz"),
"wake_order 0 must sort first (min_resonance {min_resonance:?}): {ordered:?}"
);
assert_eq!(
ordered.get(1).map(String::as_str),
Some("kn-mmm"),
"wake_order 5 sorts after 0 and before the unordered entries: {ordered:?}"
);
}
}
#[test]
fn min_resonance_order_is_stable_and_wake_order_opens_the_ritual() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
for id in ["kn-mid", "kn-last", "kn-first"] {
db.upsert_knowledge(&core_layer_entry(id, &[])).unwrap();
}
let first_run: Vec<String> =
cascade_ids(&db.wake_cascade(&ctx, 50, Some(9), 7, false).unwrap());
let second_run: Vec<String> =
cascade_ids(&db.wake_cascade(&ctx, 50, Some(9), 7, false).unwrap());
assert_eq!(
first_run, second_run,
"two begins over the same data must produce the same sequence"
);
assert_eq!(
first_run,
vec![
"kn-first".to_string(),
"kn-last".to_string(),
"kn-mid".to_string()
],
"equal resonance falls back to the id tiebreak"
);
let mut opener = core_layer_entry("kn-mid", &[]);
opener.wake_order = Some(1);
db.upsert_knowledge(&opener).unwrap();
let ordered = cascade_ids(&db.wake_cascade(&ctx, 50, Some(9), 7, false).unwrap());
assert_eq!(
ordered.first().map(String::as_str),
Some("kn-mid"),
"wake_order decides the opening bloom: {ordered:?}"
);
}
#[test]
fn core_layer_order_is_stable_across_runs() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
for id in ["kn-c", "kn-a", "kn-b"] {
db.upsert_knowledge(&core_layer_entry(id, &[])).unwrap();
}
let run = |db: &SurrealDatabase| -> Vec<String> {
db.wake_cascade(&ctx, 50, None, 7, false)
.unwrap()
.core
.iter()
.map(|e| e.id.clone())
.collect()
};
assert_eq!(run(&db), run(&db));
assert_eq!(
run(&db),
vec!["kn-a".to_string(), "kn-b".to_string(), "kn-c".to_string()]
);
}
#[test]
fn core_layer_limit_counts_kept_entries_not_excluded_ones() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::public_only();
db.upsert_knowledge(&core_layer_entry("kn-a", &["archive"]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-b", &["archive"]))
.unwrap();
db.upsert_knowledge(&core_layer_entry("kn-c", &[])).unwrap();
db.upsert_knowledge(&core_layer_entry("kn-d", &[])).unwrap();
let cascade = db.wake_cascade(&ctx, 2, None, 7, false).unwrap();
let core: Vec<&str> = cascade.core.iter().map(|e| e.id.as_str()).collect();
assert_eq!(core, vec!["kn-c", "kn-d"], "{core:?}");
assert_eq!(cascade.excluded.get("archive"), Some(&2));
}
const WAKE_GUESS_SELECT: &str = "SELECT agent, wake, session_id, <string>ts AS ts, bloom_id,
chunk_index, chunk_total, position, bloom_position, bloom_total, title_shown, guess,
model_id, phrase_source, phrases, match_kind, match_index, bucket, content_hash,
embedding, embedding_model, sim_phrase, sim_content, sim_title, sim_prior,
sim_prior_null, prior_n, scored_at
FROM wake_guess";
fn wake_session_at(
db: &SurrealDatabase,
session_id: &str,
step: u32,
) -> crate::wake_token::WakeSession {
let cascade = crate::store::WakeCascade {
core: vec![
make_test_entry("kn-a", 9, 0.0),
make_test_entry("kn-b", 9, 0.0),
],
..Default::default()
};
let mut session =
crate::wake_token::WakeSession::new(&cascade, "test-agent".to_string(), Some(7), None);
session.session_id = session_id.to_string();
session.step = step;
db.create_wake_session(&session).unwrap();
session
}
fn log_guess(db: &SurrealDatabase, row: &crate::wake_guess::WakeGuessRow) {
let mut session = wake_session_at(db, &row.session_id, row.position);
session.step += 1;
db.record_wake_guess(row, &session, row.position).unwrap();
}
fn sample_guess_row(session_id: &str, position: u32) -> crate::wake_guess::WakeGuessRow {
crate::wake_guess::WakeGuessRow {
agent: "test-agent".to_string(),
wake: Some(7),
session_id: session_id.to_string(),
bloom_id: "kn-a".to_string(),
chunk_index: 0,
chunk_total: 1,
position,
bloom_position: 1,
bloom_total: 2,
title_shown: "Sample Title".to_string(),
guess: "a synthetic guess".to_string(),
model_id: None,
phrase_source: "authored".to_string(),
phrases: vec!["first phrase".to_string()],
match_kind: "none".to_string(),
match_index: None,
bucket: "revealed".to_string(),
content_hash: crate::wake_guess::content_hash("chunk text"),
}
}
fn stored_step(db: &SurrealDatabase, session_id: &str) -> u32 {
db.get_wake_session(session_id).unwrap().unwrap().step
}
#[test]
fn wake_guess_row_round_trips_through_the_schema() {
let db = SurrealDatabase::open_in_memory().unwrap();
let row = crate::wake_guess::WakeGuessRow {
agent: "test-agent".to_string(),
wake: Some(7),
session_id: "session-1".to_string(),
bloom_id: "kn-sample".to_string(),
chunk_index: 1,
chunk_total: 3,
position: 7,
bloom_position: 4,
bloom_total: 12,
title_shown: "Sample Title (Part 2/3)".to_string(),
guess: "a synthetic guess".to_string(),
model_id: Some("test-model".to_string()),
phrase_source: "authored".to_string(),
phrases: vec!["first phrase".to_string(), "second phrase".to_string()],
match_kind: "close".to_string(),
match_index: Some(1),
bucket: "unhinted".to_string(),
content_hash: crate::wake_guess::content_hash("chunk text"),
};
log_guess(&db, &row);
let stored = db.query_json_for_test(WAKE_GUESS_SELECT).unwrap();
assert_eq!(stored.len(), 1, "exactly one row was written");
let got = &stored[0];
assert_eq!(got["agent"], "test-agent");
assert_eq!(got["wake"], 7);
assert_eq!(got["session_id"], "session-1");
assert_eq!(got["bloom_id"], "kn-sample");
assert_eq!(got["chunk_index"], 1);
assert_eq!(got["chunk_total"], 3);
assert_eq!(got["position"], 7);
assert_eq!(got["bloom_position"], 4);
assert_eq!(got["bloom_total"], 12);
assert_eq!(got["title_shown"], "Sample Title (Part 2/3)");
assert_eq!(got["guess"], "a synthetic guess");
assert_eq!(got["model_id"], "test-model");
assert_eq!(got["phrase_source"], "authored");
assert_eq!(got["phrases"][1], "second phrase");
assert_eq!(got["match_kind"], "close");
assert_eq!(got["match_index"], 1);
assert_eq!(got["bucket"], "unhinted");
assert_eq!(got["content_hash"], row.content_hash);
assert!(!got["ts"].is_null(), "ts defaults to write time");
for pending in [
"embedding",
"embedding_model",
"sim_phrase",
"sim_content",
"sim_title",
"sim_prior",
"sim_prior_null",
"prior_n",
"scored_at",
] {
assert!(
got[pending].is_null(),
"{pending} must be null on a freshly written row, got {:?}",
got[pending]
);
}
}
#[test]
fn wake_guess_rows_accept_an_absent_wake_number_and_model() {
let db = SurrealDatabase::open_in_memory().unwrap();
let row = crate::wake_guess::WakeGuessRow {
agent: "test-agent".to_string(),
wake: None,
session_id: "session-1".to_string(),
bloom_id: "kn-sample".to_string(),
chunk_index: 0,
chunk_total: 1,
position: 0,
bloom_position: 1,
bloom_total: 1,
title_shown: "Sample Title".to_string(),
guess: "a synthetic guess".to_string(),
model_id: None,
phrase_source: "auto".to_string(),
phrases: vec!["generated phrase".to_string()],
match_kind: "none".to_string(),
match_index: None,
bucket: "revealed".to_string(),
content_hash: crate::wake_guess::content_hash("chunk text"),
};
log_guess(&db, &row);
let stored = db.query_json_for_test(WAKE_GUESS_SELECT).unwrap();
assert!(stored[0]["wake"].is_null());
assert!(stored[0]["model_id"].is_null());
assert!(stored[0]["match_index"].is_null());
}
#[test]
fn a_wake_session_round_trips_its_agent_wake_and_model() {
let db = SurrealDatabase::open_in_memory().unwrap();
let cascade = crate::store::WakeCascade {
core: vec![
make_test_entry("kn-a", 9, 0.0),
make_test_entry("kn-b", 9, 0.0),
],
..Default::default()
};
let mut session = crate::wake_token::WakeSession::new(
&cascade,
"test-agent".to_string(),
Some(7),
Some("test-model".to_string()),
);
let session_id = db.create_wake_session(&session).unwrap();
let loaded = db.get_wake_session(&session_id).unwrap().unwrap();
assert_eq!(loaded.agent, "test-agent");
assert_eq!(loaded.wake, Some(7));
assert_eq!(loaded.model_id.as_deref(), Some("test-model"));
assert_eq!(loaded.bloom_ids.len(), 2);
session.advance(
1,
crate::wake_guess::Bucket::Unhinted,
crate::wake_token::PhraseSource::Authored,
);
db.update_wake_session(&session, 0).unwrap();
let loaded = db.get_wake_session(&session_id).unwrap().unwrap();
assert_eq!(loaded.step, 1);
assert_eq!(loaded.unhinted_count, 1);
assert_eq!(loaded.revealed_count, 0);
assert_eq!(loaded.bucket_totals().unhinted.authored, 1);
}
#[test]
fn a_session_row_without_an_agent_field_is_refused_by_the_loader() {
let db = SurrealDatabase::open_in_memory().unwrap();
db.query_json_for_test("REMOVE FIELD agent ON wake_session")
.unwrap();
db.query_json_for_test(
"CREATE type::thing('wake_session', 'legacy-session') SET
bloom_ids = ['kn-a'],
current_index = 0,
current_chunk_index = 0,
step = 0,
created_at = time::now(),
bloom_chunk_meta = []
RETURN NONE",
)
.unwrap();
let err = db
.get_wake_session("legacy-session")
.expect_err("an agent-less session row must not load");
let msg = err.to_string();
assert!(
msg.contains("older version") && msg.contains("--begin"),
"the refusal must name the cause and the way forward, got: {msg}"
);
}
#[test]
fn a_session_row_with_an_agent_still_loads() {
let db = SurrealDatabase::open_in_memory().unwrap();
let cascade = crate::store::WakeCascade {
core: vec![make_test_entry("kn-a", 9, 0.0)],
..Default::default()
};
let session =
crate::wake_token::WakeSession::new(&cascade, "test-agent".to_string(), None, None);
let id = db.create_wake_session(&session).unwrap();
let loaded = db.get_wake_session(&id).unwrap().unwrap();
assert_eq!(loaded.agent, "test-agent");
}
#[test]
fn a_wake_session_round_trips_its_prompt_record() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut session = wake_session_at(&db, "session-1", 0);
let prompt_json =
|prompt: &Option<crate::wake_token::BloomPrompt>| serde_json::to_value(prompt).unwrap();
assert!(
db.get_wake_session("session-1")
.unwrap()
.unwrap()
.prompt
.is_none()
);
session.prompt = Some(crate::wake_token::BloomPrompt {
id: "kn-a".to_string(),
title: "Sample Title (Part 2/3)".to_string(),
phrase_source: "derived".to_string(),
chunk: Some(crate::wake_token::ChunkRef {
index: 2,
total: 3,
oversized: Some(true),
}),
});
db.update_wake_session(&session, 0).unwrap();
let loaded = db.get_wake_session("session-1").unwrap().unwrap();
assert_eq!(prompt_json(&loaded.prompt), prompt_json(&session.prompt));
let prompt = loaded.prompt.unwrap();
assert_eq!(prompt.chunk_index(), 1);
assert_eq!(prompt.chunk_total(), 3);
session.step = 1;
session.prompt = Some(crate::wake_token::BloomPrompt {
id: "kn-b".to_string(),
title: "Plain".to_string(),
phrase_source: "authored".to_string(),
chunk: None,
});
db.record_wake_guess(&sample_guess_row("session-1", 0), &session, 0)
.unwrap();
let loaded = db.get_wake_session("session-1").unwrap().unwrap();
assert_eq!(prompt_json(&loaded.prompt), prompt_json(&session.prompt));
session.current_index = 2;
session.step = 2;
session.prompt = None;
db.update_wake_session(&session, 1).unwrap();
let loaded = db.get_wake_session("session-1").unwrap().unwrap();
assert!(loaded.prompt.is_none());
}
#[test]
fn a_title_edited_mid_ritual_is_logged_as_prompted_in_the_real_store() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::for_agent("test-agent");
let mut entry = make_test_entry("kn-only", 9, 0.0);
entry.title = "Before".to_string();
entry.wake_phrases = vec!["alpha cue".to_string()];
db.upsert_knowledge(&entry).unwrap();
let cascade = crate::store::WakeCascade {
core: vec![entry.clone()],
..Default::default()
};
let begin: serde_json::Value = serde_json::from_str(
&crate::wake_ritual::begin_ritual(
&db,
&cascade,
crate::wake_ritual::RitualMeta {
agent: "test-agent".to_string(),
wake: Some(7),
model_id: None,
},
false,
)
.unwrap(),
)
.unwrap();
assert_eq!(begin["prompt"]["title"], "Before");
entry.title = "After".to_string();
db.upsert_knowledge(&entry).unwrap();
let token = begin["session"].as_str().unwrap();
crate::wake_ritual::respond_ritual(&db, &ctx, "kn-only", "alpha cue", token).unwrap();
let rows = db.query_json_for_test(WAKE_GUESS_SELECT).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["title_shown"], "Before");
}
#[test]
fn a_guess_and_its_session_advance_land_together() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut session = wake_session_at(&db, "session-1", 0);
session.step = 1;
db.record_wake_guess(&sample_guess_row("session-1", 0), &session, 0)
.unwrap();
assert_eq!(stored_step(&db, "session-1"), 1);
let ids = db
.query_json_for_test("SELECT <string>id AS id FROM wake_guess")
.unwrap();
assert_eq!(ids.len(), 1);
assert!(
ids[0]["id"].as_str().unwrap().contains("session-1"),
"the row id is keyed by (session_id, position): {ids:?}"
);
}
#[test]
fn a_session_that_has_moved_rolls_the_row_back() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut session = wake_session_at(&db, "session-1", 0);
session.step = 6;
db.record_wake_guess(&sample_guess_row("session-1", 5), &session, 5)
.expect_err("a session no longer at the expected step must refuse the write");
assert_eq!(stored_step(&db, "session-1"), 0, "the session did not move");
assert!(
db.query_json_for_test(WAKE_GUESS_SELECT)
.unwrap()
.is_empty(),
"the row must roll back with the refused session write"
);
}
#[test]
fn a_second_row_at_one_step_is_refused_and_the_session_stays_put() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut session = wake_session_at(&db, "session-1", 0);
session.step = 1;
db.record_wake_guess(&sample_guess_row("session-1", 0), &session, 0)
.unwrap();
let mut again = session.clone();
again.step = 2;
db.record_wake_guess(&sample_guess_row("session-1", 0), &again, 1)
.expect_err("a second row at the same session step must be rejected");
assert_eq!(stored_step(&db, "session-1"), 1, "the session did not move");
let stored = db.query_json_for_test(WAKE_GUESS_SELECT).unwrap();
assert_eq!(stored.len(), 1);
log_guess(&db, &sample_guess_row("session-2", 0));
assert_eq!(db.query_json_for_test(WAKE_GUESS_SELECT).unwrap().len(), 2);
}
#[test]
fn a_row_an_older_binary_wrote_under_a_random_id_is_found_and_still_blocks() {
let db = SurrealDatabase::open_in_memory().unwrap();
let session = wake_session_at(&db, "session-1", 0);
db.query_json_for_test(
"CREATE wake_guess SET agent = 'test-agent', wake = 7, session_id = 'session-1',
bloom_id = 'kn-a', chunk_index = 0, chunk_total = 1, position = 0,
bloom_position = 1, bloom_total = 2, title_shown = 'Sample Title',
guess = 'the logged guess', model_id = NONE, phrase_source = 'authored',
phrases = ['first phrase'], match_kind = 'none', match_index = NONE,
bucket = 'revealed', content_hash = 'abc' RETURN NONE",
)
.unwrap();
let logged = db
.get_wake_guess("session-1", 0)
.unwrap()
.expect("a legacy row is found by (session_id, position)");
assert_eq!(logged.guess, "the logged guess");
assert_eq!(logged.bucket, "revealed");
assert_eq!(logged.match_index, None);
assert!(db.get_wake_guess("session-1", 1).unwrap().is_none());
let mut advanced = session.clone();
advanced.step = 1;
db.record_wake_guess(&sample_guess_row("session-1", 0), &advanced, 0)
.expect_err("the unique index refuses a second row at a logged step");
assert_eq!(stored_step(&db, "session-1"), 0);
}
#[test]
fn a_session_update_is_a_compare_and_swap_and_stamps_completion() {
let db = SurrealDatabase::open_in_memory().unwrap();
let mut session = wake_session_at(&db, "session-1", 0);
session.advance(
1,
crate::wake_guess::Bucket::Revealed,
crate::wake_token::PhraseSource::Authored,
);
db.update_wake_session(&session, 3)
.expect_err("a stale expected step is refused");
assert_eq!(stored_step(&db, "session-1"), 0);
assert_eq!(
db.get_wake_session("session-1").unwrap().unwrap().prev_step,
None,
"nothing written yet"
);
session.last_status = Some("bloom_missing".to_string());
db.update_wake_session(&session, 0).unwrap();
let loaded = db.get_wake_session("session-1").unwrap().unwrap();
assert_eq!(loaded.step, 1);
assert_eq!(loaded.completed_at, None, "one of two blooms walked");
assert_eq!(loaded.prev_step, Some(0), "the step the write started from");
assert_eq!(loaded.last_status.as_deref(), Some("bloom_missing"));
session.advance(
1,
crate::wake_guess::Bucket::Unhinted,
crate::wake_token::PhraseSource::Authored,
);
assert!(session.is_complete());
db.update_wake_session(&session, 1).unwrap();
let loaded = db
.get_wake_session("session-1")
.unwrap()
.expect("a completed session is kept, not deleted");
assert!(
loaded.completed_at.is_some(),
"the write that walks the last step stamps completed_at"
);
}
#[test]
fn wake_history_reports_sessions_at_a_wake_and_the_highest_wake() {
let db = SurrealDatabase::open_in_memory().unwrap();
let row = |session: &str, position: u32, agent: &str, wake: i64| {
let mut r = sample_guess_row(session, position);
r.agent = agent.to_string();
r.wake = Some(wake);
r
};
log_guess(&db, &row("s1", 0, "test-agent", 7));
log_guess(&db, &row("s1b", 0, "test-agent", 7));
log_guess(&db, &row("s3", 0, "test-agent", 9));
log_guess(&db, &row("s4", 0, "other-agent", 12));
let mut unnumbered = sample_guess_row("s5", 0);
unnumbered.wake = None;
log_guess(&db, &unnumbered);
let mut history = db.wake_history("test-agent", 7).unwrap();
history.sessions_at_wake.sort();
assert_eq!(history.sessions_at_wake, vec!["s1", "s1b"]);
assert_eq!(
history.highest_wake,
Some(9),
"another agent's wake 12 is not ours"
);
let history = db.wake_history("test-agent", 8).unwrap();
assert!(history.sessions_at_wake.is_empty());
let history = db.wake_history("new-agent", 1).unwrap();
assert_eq!(history, crate::wake_guess::WakeHistory::default());
}
#[test]
fn a_lost_response_resumes_however_far_the_call_moved_step() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::for_agent("test-agent");
let mut entries = Vec::new();
for (id, phrase) in [
("kn-1", "one"),
("kn-2", "two"),
("kn-3", "three"),
("kn-4", "four"),
] {
let mut e = make_test_entry(id, 9, 0.0);
e.wake_phrases = vec![phrase.to_string()];
db.upsert_knowledge(&e).unwrap();
entries.push(e);
}
let cascade = crate::store::WakeCascade {
core: entries,
..Default::default()
};
let begin: serde_json::Value = serde_json::from_str(
&crate::wake_ritual::begin_ritual(
&db,
&cascade,
crate::wake_ritual::RitualMeta {
agent: "test-agent".to_string(),
wake: Some(7),
model_id: None,
},
false,
)
.unwrap(),
)
.unwrap();
let respond = |id: &str, guess: &str, token: &str| -> serde_json::Value {
let out = crate::wake_ritual::respond_ritual(&db, &ctx, id, guess, token)
.unwrap_or_else(|e| panic!("respond {id} failed: {e}"));
serde_json::from_str(&out).unwrap()
};
let token0 = begin["session"].as_str().unwrap().to_string();
let first_id = begin["prompt"]["id"].as_str().unwrap().to_string();
let order: Vec<String> = cascade.core.iter().map(|e| e.id.clone()).collect();
let pos = order.iter().position(|id| *id == first_id).unwrap();
let (second_id, third_id) = (order[pos + 1].clone(), order[pos + 2].clone());
db.delete(&second_id, &ctx).unwrap();
let phrase = |id: &str| match id {
"kn-1" => "one",
"kn-2" => "two",
"kn-3" => "three",
_ => "four",
};
let first = respond(&first_id, phrase(&first_id), &token0);
assert_eq!(first["next"]["id"], third_id.as_str());
let resumed = respond(&first_id, "retyped", &token0);
assert_eq!(resumed["replayed"], true);
assert_eq!(resumed["guess"], phrase(&first_id));
assert_eq!(resumed["session"], first["session"]);
db.delete(&third_id, &ctx).unwrap();
let token1 = first["session"].as_str().unwrap().to_string();
let missing = respond(&third_id, "anything", &token1);
assert_eq!(missing["status"], "bloom_missing");
let resumed = respond(&third_id, "anything", &token1);
assert_eq!(resumed["status"], "bloom_missing");
assert_eq!(resumed["replayed"], true);
assert_eq!(resumed["session"], missing["session"]);
assert_eq!(resumed["next"], missing["next"]);
assert_eq!(db.query_json_for_test(WAKE_GUESS_SELECT).unwrap().len(), 1);
}
#[test]
fn a_retry_after_a_half_written_step_replays_the_logged_guess() {
let db = SurrealDatabase::open_in_memory().unwrap();
let ctx = crate::store::AgentContext::for_agent("test-agent");
let mut first = make_test_entry("kn-first", 9, 0.0);
first.wake_phrases = vec!["alpha cue".to_string()];
let mut second = make_test_entry("kn-second", 9, 0.0);
second.wake_phrases = vec!["bravo cue".to_string()];
db.upsert_knowledge(&first).unwrap();
db.upsert_knowledge(&second).unwrap();
let cascade = crate::store::WakeCascade {
core: vec![first, second],
..Default::default()
};
let begin: serde_json::Value = serde_json::from_str(
&crate::wake_ritual::begin_ritual(
&db,
&cascade,
crate::wake_ritual::RitualMeta {
agent: "test-agent".to_string(),
wake: Some(7),
model_id: None,
},
false,
)
.unwrap(),
)
.unwrap();
let token = begin["session"].as_str().unwrap().to_string();
let session_id = token.split('.').next().unwrap().to_string();
db.query_json_for_test(&format!(
"CREATE wake_guess SET agent = 'test-agent', wake = 7, session_id = '{session_id}',
bloom_id = 'kn-first', chunk_index = 0, chunk_total = 1, position = 0,
bloom_position = 1, bloom_total = 2, title_shown = 'Test Entry kn-first',
guess = 'the first guess', model_id = NONE, phrase_source = 'authored',
phrases = ['alpha cue'], match_kind = 'exact', match_index = 0,
bucket = 'unhinted', content_hash = 'abc' RETURN NONE"
))
.unwrap();
assert_eq!(stored_step(&db, &session_id), 0);
let out = crate::wake_ritual::respond_ritual(&db, &ctx, "kn-first", "a retyped miss", &token)
.expect("the retry must be answered, not refused by the index");
let resp: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(resp["replayed"], true);
assert_eq!(resp["guess"], "the first guess");
assert_eq!(resp["bucket"], "unhinted");
assert_eq!(resp["match"]["kind"], "exact");
assert_eq!(resp["next"]["id"], "kn-second");
assert_eq!(resp["wake"], 7);
assert_eq!(stored_step(&db, &session_id), 1, "the session advanced");
let rows = db.query_json_for_test(WAKE_GUESS_SELECT).unwrap();
assert_eq!(rows.len(), 1, "still exactly one row");
let loaded = db.get_wake_session(&session_id).unwrap().unwrap();
assert_eq!(loaded.bucket_totals().unhinted.authored, 1);
let next_token = resp["session"].as_str().unwrap();
let out = crate::wake_ritual::respond_ritual(&db, &ctx, "kn-second", "bravo cue", next_token)
.unwrap();
let resp: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(resp["status"], "shown");
assert!(resp.get("replayed").is_none());
assert_eq!(resp["summary"]["buckets"]["unhinted"]["authored"], 2);
let loaded = db
.get_wake_session(&session_id)
.unwrap()
.expect("a completed session is kept");
assert!(loaded.completed_at.is_some());
assert_eq!(db.query_json_for_test(WAKE_GUESS_SELECT).unwrap().len(), 2);
}