stemedb/crates/stemedb-query/tests/battery/battery2_jwt_conflict.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

377 lines
14 KiB
Rust

//! Battery 2: JWT Conflict Scenario.
//!
//! Tests escalation mechanisms and layered consensus with cross-tier disagreement.
//!
//! # Test Coverage
//!
//! | Test | Feature | Validates |
//! |------|---------|-----------|
//! | `test_jwt_conflict_escalation_fires` | Escalation | Conflict score threshold triggers event |
//! | `test_jwt_escalation_predicate_filter` | Escalation | Predicate pattern filtering |
//! | `test_jwt_layered_lens_tier_agreement` | Layered lens | Tier-by-tier resolution |
#![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages
use super::helpers::*;
/// Test 2.1: Escalation event fires when conflict score exceeds threshold.
///
/// Setup:
/// - RFC 7519 (Tier 0, confidence 1.0): aud_validation = Boolean(true)
/// - Approved runbook (Tier 2, confidence 0.95): aud_validation = Boolean(true)
/// - Internal wiki (Tier 3, confidence 0.8): aud_validation = Boolean(false)
/// - Stack Overflow (Tier 5, confidence 0.6): aud_validation = Boolean(false)
///
/// Escalation policy: min_conflict_score=0.5, level=High, predicate_pattern=None
///
/// Proves the escalation mechanism correctly detects cross-tier disagreement
/// and records an event for external review.
#[tokio::test]
async fn test_jwt_conflict_escalation_fires() {
let dir = tempdir().expect("create temp dir");
let wal_dir = dir.path().join("wal");
let db_dir = dir.path().join("db");
let base_ts: u64 = 1_000_000;
// === Setup: Create 4 conflicting assertions ===
// RFC 7519 (Tier 0, confidence 1.0): aud_validation = Boolean(true)
let rfc_7519 = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(true),
SourceClass::Regulatory,
1.0,
base_ts,
);
// Approved runbook (Tier 2, confidence 0.95): aud_validation = Boolean(true)
let approved_runbook = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(true),
SourceClass::Observational,
0.95,
base_ts + 1,
);
// Internal wiki (Tier 3, confidence 0.8): aud_validation = Boolean(false)
let internal_wiki = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(false),
SourceClass::Expert,
0.8,
base_ts + 2,
);
// Stack Overflow (Tier 5, confidence 0.6): aud_validation = Boolean(false)
let stack_overflow = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(false),
SourceClass::Anecdotal,
0.6,
base_ts + 3,
);
// === Step 1: Write all 4 to WAL ===
let mut journal = Journal::open(&wal_dir).expect("open journal");
journal.append(serialize_assertion(&rfc_7519).expect("ser")).expect("append rfc");
journal.append(serialize_assertion(&approved_runbook).expect("ser")).expect("append runbook");
journal.append(serialize_assertion(&internal_wiki).expect("ser")).expect("append wiki");
journal
.append(serialize_assertion(&stack_overflow).expect("ser"))
.expect("append stackoverflow");
// === Step 2: Ingest all 4 via IngestWorker ===
let journal = Arc::new(Mutex::new(journal));
let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));
let mut worker =
IngestWorker::new(journal.clone(), store.clone()).await.expect("create worker");
for _ in 0..4 {
let bytes = worker.step().await.expect("ingest step");
assert!(bytes > 0, "should process data from WAL");
}
// === Step 3: Configure escalation policy and materialize ===
let policy = EscalationPolicy {
name: "security-config".to_string(),
min_conflict_score: 0.5,
level: EscalationLevel::High,
predicate_pattern: None,
};
let escalation_store = Arc::new(GenericEscalationStore::new(store.clone()));
let lens = SyncLensWrapper(LayeredConsensusLens::new());
let materializer = Materializer::new(store.clone(), Box::new(lens))
.with_escalation(escalation_store.clone() as Arc<dyn EscalationStore>, vec![policy]);
let report = materializer.step().await.expect("materialize");
assert_eq!(report.views_updated, 1, "should update one view");
assert_eq!(report.escalations_triggered, 1, "should trigger one escalation");
// === Step 4: Verify escalation event ===
let pending = escalation_store.get_pending_escalations().await.expect("get pending");
assert_eq!(pending.len(), 1, "should have one pending escalation");
let event = &pending[0];
assert_eq!(event.subject, "JWT_aud_validation");
assert_eq!(event.predicate, "aud_validation");
assert_eq!(event.level, EscalationLevel::High);
assert!(
event.conflict_score >= 0.5,
"conflict_score should be >= 0.5, got {}",
event.conflict_score
);
assert!(!event.resolved, "escalation should not be resolved");
}
/// Test 2.2: Escalation predicate pattern filtering works correctly.
///
/// Two policies:
/// - Policy A: predicate_pattern=Some("aud"), triggers on "aud_validation"
/// - Policy B: predicate_pattern=Some("revenue"), does NOT trigger on "aud_validation"
///
/// Only Policy A should fire, creating a Critical-level escalation.
#[tokio::test]
async fn test_jwt_escalation_predicate_filter() {
let dir = tempdir().expect("create temp dir");
let wal_dir = dir.path().join("wal");
let db_dir = dir.path().join("db");
let base_ts: u64 = 1_000_000;
// Same four assertions as 2.1
let rfc_7519 = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(true),
SourceClass::Regulatory,
1.0,
base_ts,
);
let approved_runbook = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(true),
SourceClass::Observational,
0.95,
base_ts + 1,
);
let internal_wiki = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(false),
SourceClass::Expert,
0.8,
base_ts + 2,
);
let stack_overflow = create_signed_assertion_with_source(
"JWT_aud_validation",
"aud_validation",
ObjectValue::Boolean(false),
SourceClass::Anecdotal,
0.6,
base_ts + 3,
);
// Write to WAL and ingest
let mut journal = Journal::open(&wal_dir).expect("open journal");
journal.append(serialize_assertion(&rfc_7519).expect("ser")).expect("append");
journal.append(serialize_assertion(&approved_runbook).expect("ser")).expect("append");
journal.append(serialize_assertion(&internal_wiki).expect("ser")).expect("append");
journal.append(serialize_assertion(&stack_overflow).expect("ser")).expect("append");
let journal = Arc::new(Mutex::new(journal));
let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));
let mut worker =
IngestWorker::new(journal.clone(), store.clone()).await.expect("create worker");
for _ in 0..4 {
worker.step().await.expect("ingest step");
}
// === Configure two policies ===
let policy_a = EscalationPolicy {
name: "policy-aud".to_string(),
min_conflict_score: 0.3,
level: EscalationLevel::Critical,
predicate_pattern: Some("aud".to_string()),
};
let policy_b = EscalationPolicy {
name: "policy-revenue".to_string(),
min_conflict_score: 0.3,
level: EscalationLevel::Medium,
predicate_pattern: Some("revenue".to_string()),
};
let escalation_store = Arc::new(GenericEscalationStore::new(store.clone()));
let lens = SyncLensWrapper(LayeredConsensusLens::new());
let materializer = Materializer::new(store.clone(), Box::new(lens)).with_escalation(
escalation_store.clone() as Arc<dyn EscalationStore>,
vec![policy_a, policy_b],
);
let report = materializer.step().await.expect("materialize");
assert_eq!(report.escalations_triggered, 1, "should trigger exactly one escalation");
// === Verify only Policy A fired ===
let pending = escalation_store.get_pending_escalations().await.expect("get pending");
assert_eq!(pending.len(), 1, "should have exactly one pending escalation");
let event = &pending[0];
assert_eq!(event.level, EscalationLevel::Critical, "should be Critical (Policy A)");
assert_eq!(event.predicate, "aud_validation");
assert!(event.reason.contains("policy-aud"), "reason should reference Policy A");
}
/// Test 2.3: Layered Consensus Lens shows tier-by-tier resolution.
///
/// With the JWT assertions:
/// - Tier 0 (Regulatory): Boolean(true) wins
/// - Tier 2 (Observational): Boolean(true) wins
/// - Tier 3 (Expert): Boolean(false) wins
/// - Tier 5 (Anecdotal): Boolean(false) wins
///
/// Cross-tier conflict should be high (tiers 0/2 vs 3/5 disagree).
/// Overall winner should come from Tier 0 (highest authority).
#[tokio::test]
async fn test_jwt_layered_lens_tier_agreement() {
use stemedb_lens::{LayeredConsensusLens, LayeredLens};
let store = Arc::new(HybridStore::open_temp().expect("store"));
let index_store = GenericIndexStore::new(store.clone());
let base_ts: u64 = 1_000_000;
// RFC 7519 (Tier 0): Boolean(true)
let rfc_7519 = AssertionBuilder::new()
.subject("JWT_aud_validation")
.predicate("aud_validation")
.object(ObjectValue::Boolean(true))
.source_class(SourceClass::Regulatory)
.confidence(1.0)
.agent_id([1u8; 32])
.timestamp(base_ts)
.build();
// Approved runbook (Tier 2): Boolean(true)
let approved_runbook = AssertionBuilder::new()
.subject("JWT_aud_validation")
.predicate("aud_validation")
.object(ObjectValue::Boolean(true))
.source_class(SourceClass::Observational)
.confidence(0.95)
.agent_id([2u8; 32])
.timestamp(base_ts + 1)
.build();
// Internal wiki (Tier 3): Boolean(false)
let internal_wiki = AssertionBuilder::new()
.subject("JWT_aud_validation")
.predicate("aud_validation")
.object(ObjectValue::Boolean(false))
.source_class(SourceClass::Expert)
.confidence(0.8)
.agent_id([3u8; 32])
.timestamp(base_ts + 2)
.build();
// Stack Overflow (Tier 5): Boolean(false)
let stack_overflow = AssertionBuilder::new()
.subject("JWT_aud_validation")
.predicate("aud_validation")
.object(ObjectValue::Boolean(false))
.source_class(SourceClass::Anecdotal)
.confidence(0.6)
.agent_id([4u8; 32])
.timestamp(base_ts + 3)
.build();
store_assertion_direct(&store, &index_store, &rfc_7519).await;
store_assertion_direct(&store, &index_store, &approved_runbook).await;
store_assertion_direct(&store, &index_store, &internal_wiki).await;
store_assertion_direct(&store, &index_store, &stack_overflow).await;
// === Resolve with LayeredConsensusLens ===
let lens = LayeredConsensusLens::new();
let assertions = vec![rfc_7519, approved_runbook, internal_wiki, stack_overflow];
let result = lens.resolve_layered(&assertions);
// === Assert tier-specific results ===
// Should have 4 tiers (0, 2, 3, 5)
assert_eq!(result.tiers.len(), 4, "should have 4 tiers");
// Tier 0 (Regulatory): Boolean(true)
let tier_0 = result.tiers.iter().find(|t| t.tier == 0).expect("tier 0 should exist");
assert_eq!(tier_0.candidates_count, 1);
assert!(tier_0.winner.is_some(), "tier 0 should have a winner");
assert_eq!(
tier_0.winner.as_ref().expect("tier 0 winner").object,
ObjectValue::Boolean(true),
"Tier 0 should say Boolean(true)"
);
// Tier 2 (Observational): Boolean(true)
let tier_2 = result.tiers.iter().find(|t| t.tier == 2).expect("tier 2 should exist");
assert_eq!(tier_2.candidates_count, 1);
assert!(tier_2.winner.is_some(), "tier 2 should have a winner");
assert_eq!(
tier_2.winner.as_ref().expect("tier 2 winner").object,
ObjectValue::Boolean(true),
"Tier 2 should say Boolean(true)"
);
// Tier 3 (Expert): Boolean(false)
let tier_3 = result.tiers.iter().find(|t| t.tier == 3).expect("tier 3 should exist");
assert_eq!(tier_3.candidates_count, 1);
assert!(tier_3.winner.is_some(), "tier 3 should have a winner");
assert_eq!(
tier_3.winner.as_ref().expect("tier 3 winner").object,
ObjectValue::Boolean(false),
"Tier 3 should say Boolean(false)"
);
// Tier 5 (Anecdotal): Boolean(false)
let tier_5 = result.tiers.iter().find(|t| t.tier == 5).expect("tier 5 should exist");
assert_eq!(tier_5.candidates_count, 1);
assert!(tier_5.winner.is_some(), "tier 5 should have a winner");
assert_eq!(
tier_5.winner.as_ref().expect("tier 5 winner").object,
ObjectValue::Boolean(false),
"Tier 5 should say Boolean(false)"
);
// === Assert overall results ===
// Overall winner should be from Tier 0 (highest authority)
assert!(result.overall_winner.is_some(), "should have overall winner");
assert_eq!(
result.overall_winner.as_ref().expect("overall winner").object,
ObjectValue::Boolean(true),
"Overall winner should be Boolean(true) from Tier 0"
);
assert_eq!(
result.overall_winner.as_ref().expect("overall winner").source_class,
SourceClass::Regulatory,
"Overall winner should be from Tier 0 (Regulatory)"
);
// Cross-tier conflict should be high (tiers 0/2 vs 3/5 disagree)
assert!(
result.overall_conflict_score > 0.5,
"overall_conflict_score should be > 0.5 (cross-tier disagreement), got {}",
result.overall_conflict_score
);
}