//! 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, 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, 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 ); }