Enterprise Features: - Hosted mode with remote sync for team pattern aggregation - Community sharing with privacy-preserving anonymization - LLM-based semantic claim extraction with Gemini integration - Pattern learning with promotion to declarative extractors - High-entropy secrets extractor with configurable thresholds - Auth bypass and insecure cookies extractors Module Refactoring: - Split oversized files to comply with 500-line limit - Config split: types/core.rs, types/extractors.rs, types/hosted.rs, etc. - Handlers split: scan.rs, policy.rs, report.rs modules - Extractors split: declarative/, high_entropy_secrets/, insecure_cookies/ - Learning split: store modules with metrics and persistence SDK & Ontology: - stemedb-ontology SDK with fluent builders and StemeDB client - Pharma domain extractors for FDA Orange Book data - Consumer health UAT test infrastructure Code Quality: - Fixed clippy warnings (needless_borrows_for_generic_args) - Added KVStore trait imports where needed - Fixed utoipa path re-exports for OpenAPI docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
269 lines
9.5 KiB
Rust
269 lines
9.5 KiB
Rust
//! Core logic and types for Episteme (StemeDB).
|
|
//!
|
|
//! This crate defines the fundamental data structures like `Assertion`,
|
|
//! `SignatureEntry`, and the core traits for the knowledge graph.
|
|
|
|
/// System-wide limits and defaults.
|
|
pub mod limits;
|
|
/// Zero-copy serialization utilities.
|
|
pub mod serde;
|
|
/// Cryptographic signing utilities for assertions.
|
|
pub mod signing;
|
|
/// Shared test helpers (builders, factories) for the workspace.
|
|
pub mod testing;
|
|
/// Core data types for StemeDB assertions and signatures.
|
|
pub mod types;
|
|
|
|
/// A simple hello world function for testing the core crate.
|
|
pub fn hello_world() -> String {
|
|
"Hello from Episteme Core!".to_string()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::types::{
|
|
Assertion, Epoch, HlcTimestamp, LifecycleStage, ObjectValue, SignatureEntry, SourceClass,
|
|
Supersession, SupersessionType, Vote,
|
|
};
|
|
use rkyv::check_archived_root;
|
|
use rkyv::ser::serializers::AllocSerializer;
|
|
use rkyv::ser::Serializer;
|
|
use rkyv::Deserialize;
|
|
|
|
#[test]
|
|
fn test_hello_world() {
|
|
assert_eq!(hello_world(), "Hello from Episteme Core!");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assertion_serialization_roundtrip() {
|
|
let assertion = Assertion {
|
|
subject: "Tesla_Inc".to_string(),
|
|
predicate: "has_revenue".to_string(),
|
|
object: ObjectValue::Number(96.7),
|
|
parent_hash: None,
|
|
source_hash: [0u8; 32],
|
|
source_class: SourceClass::Clinical,
|
|
visual_hash: Some([1u8; 8]),
|
|
epoch: Some([2u8; 32]),
|
|
source_metadata: None,
|
|
lifecycle: LifecycleStage::Approved,
|
|
signatures: vec![SignatureEntry {
|
|
agent_id: [2u8; 32],
|
|
signature: [3u8; 64],
|
|
timestamp: 123456789,
|
|
version: 1,
|
|
}],
|
|
confidence: 0.95,
|
|
timestamp: 123456789,
|
|
hlc_timestamp: HlcTimestamp::default(),
|
|
vector: Some(vec![0.1, 0.2, 0.3]),
|
|
};
|
|
|
|
// Serialize
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&assertion).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
// Validate
|
|
let archived = check_archived_root::<Assertion>(&bytes)
|
|
.expect("Failed to validate archived assertion");
|
|
|
|
// Deserialize
|
|
let deserialized: Assertion =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(assertion, deserialized);
|
|
assert_eq!(deserialized.subject, "Tesla_Inc");
|
|
assert_eq!(deserialized.visual_hash, Some([1u8; 8]));
|
|
assert_eq!(deserialized.epoch, Some([2u8; 32]));
|
|
assert_eq!(deserialized.lifecycle, LifecycleStage::Approved);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lifecycle_stage_serialization_roundtrip() {
|
|
// Test all lifecycle stages survive serialization
|
|
let stages = [
|
|
LifecycleStage::Proposed,
|
|
LifecycleStage::UnderReview,
|
|
LifecycleStage::Approved,
|
|
LifecycleStage::Deprecated,
|
|
LifecycleStage::Rejected,
|
|
];
|
|
|
|
for stage in stages {
|
|
let assertion = Assertion {
|
|
subject: "test".to_string(),
|
|
predicate: "lifecycle_test".to_string(),
|
|
object: ObjectValue::Text(format!("{:?}", stage)),
|
|
parent_hash: None,
|
|
source_hash: [0u8; 32],
|
|
source_class: SourceClass::Expert,
|
|
visual_hash: None,
|
|
epoch: None,
|
|
source_metadata: None,
|
|
lifecycle: stage,
|
|
signatures: vec![],
|
|
confidence: 1.0,
|
|
timestamp: 0,
|
|
hlc_timestamp: HlcTimestamp::default(),
|
|
vector: None,
|
|
};
|
|
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&assertion).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
let archived = check_archived_root::<Assertion>(&bytes)
|
|
.expect("Failed to validate archived assertion");
|
|
let deserialized: Assertion =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(
|
|
deserialized.lifecycle, stage,
|
|
"Lifecycle stage {:?} should survive round-trip",
|
|
stage
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_lifecycle_stage_default() {
|
|
// Verify that Default gives us Proposed (the safe default)
|
|
assert_eq!(LifecycleStage::default(), LifecycleStage::Proposed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_epoch_serialization_roundtrip() {
|
|
let epoch = Epoch {
|
|
id: [1u8; 32],
|
|
name: "Newtonian Physics".to_string(),
|
|
supersedes: Some([0u8; 32]),
|
|
supersession_type: Some(SupersessionType::Refinement),
|
|
start_timestamp: 1000,
|
|
end_timestamp: None,
|
|
};
|
|
|
|
// Serialize
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&epoch).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
// Validate
|
|
let archived =
|
|
check_archived_root::<Epoch>(&bytes).expect("Failed to validate archived epoch");
|
|
|
|
// Deserialize
|
|
let deserialized: Epoch =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(epoch, deserialized);
|
|
assert_eq!(deserialized.name, "Newtonian Physics");
|
|
assert_eq!(deserialized.supersession_type, Some(SupersessionType::Refinement));
|
|
}
|
|
|
|
#[test]
|
|
fn test_supersession_serialization_roundtrip() {
|
|
let supersession = Supersession {
|
|
target_hash: [1u8; 32],
|
|
supersession_type: SupersessionType::Invalidate,
|
|
reason: "Proposal treated as approved. See incident INC-2024-001".to_string(),
|
|
new_hash: Some([2u8; 32]),
|
|
timestamp: 1704067200,
|
|
hlc_timestamp: None, // Legacy: no HLC for backward compat test
|
|
agent_id: [3u8; 32],
|
|
signature: [4u8; 64],
|
|
};
|
|
|
|
// Serialize
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&supersession).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
// Validate
|
|
let archived = check_archived_root::<Supersession>(&bytes)
|
|
.expect("Failed to validate archived supersession");
|
|
|
|
// Deserialize
|
|
let deserialized: Supersession =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(supersession, deserialized);
|
|
assert_eq!(deserialized.target_hash, [1u8; 32]);
|
|
assert_eq!(deserialized.supersession_type, SupersessionType::Invalidate);
|
|
assert_eq!(deserialized.reason, "Proposal treated as approved. See incident INC-2024-001");
|
|
assert_eq!(deserialized.new_hash, Some([2u8; 32]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_supersession_type_all_variants() {
|
|
// Test all supersession types survive serialization
|
|
let types = [
|
|
SupersessionType::Invalidate,
|
|
SupersessionType::Temporal,
|
|
SupersessionType::Refinement,
|
|
SupersessionType::RequiresReview,
|
|
SupersessionType::Additive,
|
|
];
|
|
|
|
for stype in types {
|
|
let supersession = Supersession {
|
|
target_hash: [0u8; 32],
|
|
supersession_type: stype,
|
|
reason: format!("{:?} test", stype),
|
|
new_hash: None,
|
|
timestamp: 0,
|
|
hlc_timestamp: None,
|
|
agent_id: [0u8; 32],
|
|
signature: [0u8; 64],
|
|
};
|
|
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&supersession).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
let archived = check_archived_root::<Supersession>(&bytes)
|
|
.expect("Failed to validate archived supersession");
|
|
let deserialized: Supersession =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(
|
|
deserialized.supersession_type, stype,
|
|
"SupersessionType {:?} should survive round-trip",
|
|
stype
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_vote_serialization_roundtrip() {
|
|
let vote = Vote {
|
|
assertion_hash: [1u8; 32],
|
|
agent_id: [2u8; 32],
|
|
weight: 0.8,
|
|
signature: [3u8; 64],
|
|
timestamp: 123456789,
|
|
source_url: None,
|
|
observed_context: None,
|
|
};
|
|
|
|
// Serialize
|
|
let mut serializer = AllocSerializer::<4096>::default();
|
|
serializer.serialize_value(&vote).expect("Failed to serialize");
|
|
let bytes = serializer.into_serializer().into_inner();
|
|
|
|
// Validate
|
|
let archived =
|
|
check_archived_root::<Vote>(&bytes).expect("Failed to validate archived vote");
|
|
|
|
// Deserialize
|
|
let deserialized: Vote =
|
|
archived.deserialize(&mut rkyv::Infallible).expect("Failed to deserialize");
|
|
|
|
assert_eq!(vote, deserialized);
|
|
assert_eq!(deserialized.assertion_hash, [1u8; 32]);
|
|
assert_eq!(deserialized.weight, 0.8);
|
|
}
|
|
}
|