stemedb/crates/stemedb-query/tests/battery/helpers.rs
jordan 137a588ed0 feat: Concept hierarchy (Phase 5D) - ConceptPath, source schemes, AliasStore
Implements hierarchical subject identifiers with scheme-based source tier inference:

- ConceptPath type with parse/wire_format, leaf/parent, prefix matching
- SourceScheme registry mapping schemes to default SourceClass tiers:
  - rfc://, fda://, ietf:// → Regulatory (Tier 0)
  - peer://, pubmed:// → PeerReviewed (Tier 1)
  - code://, wiki:// → Expert (Tier 3)
  - blog://, anon:// → Anecdotal (Tier 5)
- AliasStore for cross-scheme entity resolution (bidirectional indexing)
- API endpoints for concept operations
- Battery tests 8, 9 & 10 for concepts, aliases, and advanced signatures
- Go SDK updates for concept types and signing

Completes Phase 5, advancing to Phase 6 (Distributed Writes).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:44:54 -07:00

134 lines
4.5 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 (v1), source class, confidence,
/// and arbitrary ObjectValue (text/bool, not just number).
///
/// The signature signs the message `"{subject}:{predicate}"` which matches
/// IngestWorker's v1 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,
version: 1,
}])
.build()
}
/// Create a signed assertion with Ed25519 signature (v2 enterprise), source class,
/// confidence, and arbitrary ObjectValue.
///
/// v2 signatures sign the BLAKE3 content hash of the full serialized assertion,
/// protecting ALL fields from tampering (confidence, object, timestamp, etc.).
pub fn create_signed_assertion_v2(
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();
// Build assertion WITHOUT signatures first (for hash computation)
let mut assertion = AssertionBuilder::new()
.subject(subject)
.predicate(predicate)
.object(object)
.source_class(source_class)
.confidence(confidence)
.lifecycle(LifecycleStage::Proposed)
.timestamp(timestamp)
.signatures(vec![])
.build();
// Serialize to get content hash
let bytes = serialize(&assertion).expect("serialize assertion for v2 signing");
let content_hash = blake3::hash(&bytes);
// Sign the content hash (v2 enterprise format)
let signature = signing_key.sign(content_hash.as_bytes());
// Add signature with version 2
assertion.signatures = vec![SignatureEntry {
agent_id: verifying_key.to_bytes(),
signature: signature.to_bytes(),
timestamp,
version: 2,
}];
assertion
}
/// 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");
}