- Add Layered() method to Go SDK for per-source-class consensus queries - Add LayeredQueryParams, LayeredResult, TierResolution types to Go SDK - Create conflict example demonstrating Skeptic and Layered endpoints - Update quickstart.md with sections 6 (conflict detection) and 7 (authority tiers) - Remove tracked Go binary and add data/ to .gitignore The new quickstart sections demonstrate Episteme's differentiating features: - Skeptic endpoint shows "Trust but Verify" conflict analysis - Layered endpoint shows per-tier resolution (Clinical vs Anecdotal) Note: Pre-existing large files flagged by pre-commit hook (technical debt from prior sessions) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
358 lines
13 KiB
Rust
358 lines
13 KiB
Rust
//! Smoke tests for the StemeDB simulation.
|
|
//!
|
|
//! These tests validate that the simulation infrastructure works correctly
|
|
//! and can be run in CI. They exercise the core simulation loop and verify
|
|
//! that the Spine (WAL + Ingestor + KV Store), Cortex (Query + Lenses),
|
|
//! and Ballot Box (Votes + VoteAwareConsensusLens) correctly process data.
|
|
|
|
use stemedb_sim::{run_simulation, ErrorKind, SimulationConfig, SimulationResult};
|
|
|
|
/// Verify the default simulation configuration succeeds.
|
|
///
|
|
/// This is the primary smoke test - if this passes, Spine, Cortex, and Ballot Box are working.
|
|
#[tokio::test]
|
|
async fn smoke_default_simulation_succeeds() {
|
|
let config = SimulationConfig::default();
|
|
let result = run_simulation(config).await.expect("Simulation setup should not fail");
|
|
|
|
assert!(result.is_success(), "Default simulation should succeed. Errors: {:?}", result.errors);
|
|
// All Arena 1 tests should pass
|
|
assert!(result.recency_test_passed, "Recency lens test should pass");
|
|
assert!(result.lifecycle_test_passed, "Lifecycle filtering test should pass");
|
|
assert!(result.audit_test_passed, "Query audit test should pass");
|
|
// All Arena 2 tests should pass
|
|
assert!(result.vote_consensus_test_passed, "Vote consensus test should pass");
|
|
assert!(result.troll_resistance_test_passed, "Troll resistance test should pass");
|
|
assert!(result.votes_written >= 6, "Should have written at least 6 votes");
|
|
// All Arena 3 tests should pass
|
|
assert!(result.mv_integration_test_passed, "MV integration test should pass");
|
|
assert!(result.fast_path_test_passed, "Fast-path test should pass");
|
|
assert!(result.mv_freshness_test_passed, "MV freshness test should pass");
|
|
assert!(result.views_materialized >= 1, "Should have materialized at least 1 view");
|
|
}
|
|
|
|
/// Verify simulation works with increased load.
|
|
#[tokio::test]
|
|
async fn smoke_high_volume_simulation() {
|
|
let config = SimulationConfig {
|
|
agent_count: 10,
|
|
tick_count: 50,
|
|
ingestion_wait_ms: 1000, // More time for larger workload
|
|
};
|
|
|
|
let result = run_simulation(config).await.expect("Simulation setup should not fail");
|
|
|
|
assert!(
|
|
result.is_success(),
|
|
"High-volume simulation should succeed. Errors: {:?}",
|
|
result.errors
|
|
);
|
|
// 50 base + 4 (Arena 2) + 12 (Arena 3) = 66
|
|
assert_eq!(result.assertions_written, 66);
|
|
assert_eq!(result.assertions_verified, 50);
|
|
// All Arena 1 + 2 + 3 tests should pass
|
|
assert!(result.recency_test_passed);
|
|
assert!(result.lifecycle_test_passed);
|
|
assert!(result.audit_test_passed);
|
|
assert!(result.vote_consensus_test_passed);
|
|
assert!(result.troll_resistance_test_passed);
|
|
assert!(result.mv_integration_test_passed);
|
|
assert!(result.fast_path_test_passed);
|
|
assert!(result.mv_freshness_test_passed);
|
|
}
|
|
|
|
/// Verify simulation result accessors work correctly.
|
|
#[test]
|
|
fn test_simulation_result_is_success() {
|
|
// Success case: all assertions verified, no errors, all tests pass
|
|
let success = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(success.is_success());
|
|
|
|
// Note: mismatched assertions_written != assertions_verified is allowed
|
|
// because Arena 2 tests write additional test assertions.
|
|
let with_mismatch = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 8,
|
|
queries_executed: 10,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
// This is now allowed - mismatched counts don't fail
|
|
assert!(with_mismatch.is_success());
|
|
|
|
// Failure case: has errors
|
|
let with_errors = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![stemedb_sim::SimulationError {
|
|
tick: 5,
|
|
kind: ErrorKind::VerificationFailure,
|
|
message: "Test error".to_string(),
|
|
}],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!with_errors.is_success());
|
|
|
|
// Failure case: recency test failed
|
|
let recency_failed = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: false,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!recency_failed.is_success());
|
|
|
|
// Failure case: lifecycle test failed
|
|
let lifecycle_failed = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: false,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!lifecycle_failed.is_success());
|
|
|
|
// Failure case: audit test failed
|
|
let audit_failed = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: false,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!audit_failed.is_success());
|
|
|
|
// Failure case: vote consensus test failed
|
|
let vote_consensus_failed = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: false,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!vote_consensus_failed.is_success());
|
|
|
|
// Failure case: troll resistance test failed
|
|
let troll_resistance_failed = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: false,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
assert!(!troll_resistance_failed.is_success());
|
|
}
|
|
|
|
/// Verify summary formatting.
|
|
#[test]
|
|
fn test_simulation_result_summary_format() {
|
|
let success = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 10,
|
|
queries_executed: 12,
|
|
votes_written: 6,
|
|
recency_test_passed: true,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: true,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: true,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 3,
|
|
errors: vec![],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
let summary = success.summary();
|
|
assert!(summary.contains("✅"), "Success should show checkmark");
|
|
assert!(summary.contains("10"), "Should show assertion count");
|
|
assert!(summary.contains("6"), "Should show vote count");
|
|
assert!(summary.contains("recency=✓"), "Should show recency passed");
|
|
assert!(summary.contains("lifecycle=✓"), "Should show lifecycle passed");
|
|
assert!(summary.contains("audit=✓"), "Should show audit passed");
|
|
assert!(summary.contains("vote_consensus=✓"), "Should show vote consensus passed");
|
|
assert!(summary.contains("troll_resist=✓"), "Should show troll resistance passed");
|
|
assert!(summary.contains("mv_integ=✓"), "Should show MV integration passed");
|
|
assert!(summary.contains("fast_path=✓"), "Should show fast-path passed");
|
|
assert!(summary.contains("mv_fresh=✓"), "Should show MV freshness passed");
|
|
|
|
let failure = SimulationResult {
|
|
assertions_written: 10,
|
|
assertions_verified: 8,
|
|
queries_executed: 10,
|
|
votes_written: 6,
|
|
recency_test_passed: false,
|
|
lifecycle_test_passed: true,
|
|
audit_test_passed: true,
|
|
vote_consensus_test_passed: true,
|
|
troll_resistance_test_passed: false,
|
|
mv_integration_test_passed: true,
|
|
fast_path_test_passed: false,
|
|
mv_freshness_test_passed: true,
|
|
views_materialized: 2,
|
|
errors: vec![stemedb_sim::SimulationError {
|
|
tick: 5,
|
|
kind: ErrorKind::VerificationFailure,
|
|
message: "Test".to_string(),
|
|
}],
|
|
agent_count: 3,
|
|
tick_count: 10,
|
|
};
|
|
let summary = failure.summary();
|
|
assert!(summary.contains("❌"), "Failure should show X");
|
|
assert!(summary.contains("1 error"), "Should show error count");
|
|
assert!(summary.contains("recency=✗"), "Should show recency failed");
|
|
assert!(summary.contains("troll_resist=✗"), "Should show troll resistance failed");
|
|
assert!(summary.contains("fast_path=✗"), "Should show fast-path failed");
|
|
}
|
|
|
|
/// Verify all error kinds are representable.
|
|
#[test]
|
|
fn test_error_kinds_are_complete() {
|
|
// This test documents all error kinds - if a new one is added,
|
|
// this test should be updated to cover it.
|
|
let kinds = [
|
|
ErrorKind::WriteFailure,
|
|
ErrorKind::VerificationFailure,
|
|
ErrorKind::SignatureInvalid,
|
|
ErrorKind::StorageCorruption,
|
|
ErrorKind::SerializationFailure,
|
|
ErrorKind::QueryFailure,
|
|
ErrorKind::LensResolutionFailure,
|
|
ErrorKind::AuditFailure,
|
|
ErrorKind::VoteWriteFailure,
|
|
ErrorKind::VoteConsensusFailure,
|
|
ErrorKind::MaterializerFailure,
|
|
];
|
|
|
|
for kind in kinds {
|
|
let error =
|
|
stemedb_sim::SimulationError { tick: 0, kind, message: format!("{:?} test", kind) };
|
|
// Verify Debug trait works
|
|
let debug_str = format!("{:?}", error);
|
|
assert!(!debug_str.is_empty());
|
|
}
|
|
}
|
|
|
|
/// Verify minimum viable configuration works.
|
|
/// Note: Minimal simulation requires at least 3 agents for voting tests.
|
|
#[tokio::test]
|
|
async fn smoke_minimal_simulation() {
|
|
let config = SimulationConfig { agent_count: 3, tick_count: 1, ingestion_wait_ms: 300 };
|
|
|
|
let result = run_simulation(config).await.expect("Simulation setup should not fail");
|
|
|
|
assert!(result.is_success(), "Minimal simulation should succeed: {:?}", result.errors);
|
|
// 1 base + 4 (Arena 2) + 12 (Arena 3) = 17
|
|
assert_eq!(result.assertions_written, 17);
|
|
assert_eq!(result.assertions_verified, 1);
|
|
assert_eq!(result.agent_count, 3);
|
|
assert_eq!(result.tick_count, 1);
|
|
// All Arena 1 + 2 + 3 tests should pass
|
|
assert!(result.recency_test_passed);
|
|
assert!(result.lifecycle_test_passed);
|
|
assert!(result.audit_test_passed);
|
|
assert!(result.vote_consensus_test_passed);
|
|
assert!(result.troll_resistance_test_passed);
|
|
assert!(result.mv_integration_test_passed);
|
|
assert!(result.fast_path_test_passed);
|
|
assert!(result.mv_freshness_test_passed);
|
|
}
|