stemedb/crates/stemedb-query/tests/battery/helpers.rs
jordan 42d4e09508 feat: Index persistence (Phase 5C) - vector hot/cold, visual checkpoint
Phase 5C (Index Persistence) implementation:
- PersistentVectorIndex with hot/cold architecture
  - Hot: in-memory HNSW for recent vectors
  - Cold: memory-mapped HNSW loaded from disk
  - Background builder for WAL replay and atomic swap
  - BLAKE3 integrity verification
- PersistentVisualIndex with checkpoint persistence
  - BkTreeSnapshot with rkyv serialization
  - CRC32C corruption detection
  - Atomic write pattern (temp → fsync → rename)
- Key codec additions for vector index metadata
- Split large files into modules (<500 lines each)
  - battery_pre_sentinel.rs → battery/ directory
  - visual_index.rs → visual_index/ directory
  - persistent.rs → persistent/ directory
- Refactored ingest worker tests for clarity
- Updated roadmap to mark Phase 5 complete

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:43:18 -07:00

86 lines
3.0 KiB
Rust

//! Shared test utilities for battery tests.
#![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages
// Re-export commonly used items for tests
pub use ed25519_dalek::{Signer, SigningKey};
pub use rand::rngs::OsRng;
pub use std::sync::Arc;
pub use stemedb_core::serde::serialize;
pub use stemedb_core::testing::AssertionBuilder;
pub use stemedb_core::types::{
Assertion, EscalationLevel, EscalationPolicy, LifecycleStage, ObjectValue, ResolutionStatus,
SignatureEntry, SourceClass,
};
pub use stemedb_ingest::worker::{serialize_assertion, IngestWorker};
pub use stemedb_lens::{
AsyncLens, LayeredConsensusLens, RecencyLens, SyncLensWrapper, TrustAwareAuthorityLens,
};
pub use stemedb_query::{
apply_source_class_decay, Materializer, Query, QueryEngine, SkepticResolver,
};
pub use stemedb_storage::{
key_codec, EscalationStore, GenericEscalationStore, GenericIndexStore, GenericTrustRankStore,
GenericVoteStore, HybridStore, IndexStore, KVStore,
};
pub use stemedb_wal::Journal;
pub use tempfile::tempdir;
pub use tokio::sync::Mutex;
/// Create a signed assertion with Ed25519 signature, source class, confidence,
/// and arbitrary ObjectValue (text/bool, not just number).
///
/// The signature signs the message `"{subject}:{predicate}"` which matches
/// IngestWorker's verification logic.
pub fn create_signed_assertion_with_source(
subject: &str,
predicate: &str,
object: ObjectValue,
source_class: SourceClass,
confidence: f32,
timestamp: u64,
) -> Assertion {
let mut csprng = OsRng;
let signing_key = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
let message = format!("{}:{}", subject, predicate);
let signature = signing_key.sign(message.as_bytes());
AssertionBuilder::new()
.subject(subject)
.predicate(predicate)
.object(object)
.source_class(source_class)
.confidence(confidence)
.lifecycle(LifecycleStage::Proposed)
.timestamp(timestamp)
.signatures(vec![SignatureEntry {
agent_id: verifying_key.to_bytes(),
signature: signature.to_bytes(),
timestamp,
}])
.build()
}
/// Store an assertion directly into H: and SP: keys (bypassing WAL/Ingest).
///
/// Used for unit-style tests that don't need the full pipeline.
pub async fn store_assertion_direct(
store: &Arc<HybridStore>,
index_store: &GenericIndexStore<Arc<HybridStore>>,
assertion: &Assertion,
) {
let bytes = serialize(assertion).expect("serialize assertion");
let hash = blake3::hash(&bytes);
let hash_hex = hash.to_hex().to_string();
let key = key_codec::assertion_key(&assertion.subject, &hash_hex);
store.put(&key, &bytes).await.expect("put assertion");
let assertion_hash: [u8; 32] = *hash.as_bytes();
index_store
.add_to_indexes(&assertion.subject, &assertion.predicate, &assertion_hash)
.await
.expect("add to indexes");
}