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 super::connection::SurrealConfig;
use tempfile::tempdir;
let temp_dir = tempdir().unwrap();
let db_path = temp_dir.path().join("test.surreal");
let config = SurrealConfig::default(); let _db = SurrealDatabase::connect(&db_path, &config).unwrap();
assert!(db_path.exists());
assert!(db_path.is_dir());
}
#[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![],
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 result = db.get("kn-", &ctx);
assert!(result.is_ok(), "Empty suffix should not panic");
}
#[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).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);
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_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_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"
);
}
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"
);
}