//! Integration tests for Gap 1 and Gap 5 fixes. //! //! Gap 1: Observations should use confidence-based tiers (4 or 5), not Tier 3 //! Gap 5: Superseding claims should auto-deprecate old claims, warn on duplicates use aphoria::{AuthoredClaim, AuthoredValue, ClaimStatus, ComparisonMode}; use aphoria::claims_file::ClaimsFile; use stemedb_core::types::SourceClass; use tempfile::TempDir; /// Test Gap 1: Observations use confidence-based tiers (not Tier 3 Expert) #[test] fn test_gap1_observation_tiers() { // High confidence observation should be Tier 4 (Community) let high_confidence_tier = aphoria::bridge::observation_to_tier(0.95); assert_eq!(high_confidence_tier, SourceClass::Community); assert_eq!(high_confidence_tier.tier(), 4); assert!((high_confidence_tier.authority_weight() - 0.3).abs() < f32::EPSILON); // Low confidence observation should be Tier 5 (Anecdotal) let low_confidence_tier = aphoria::bridge::observation_to_tier(0.7); assert_eq!(low_confidence_tier, SourceClass::Anecdotal); assert_eq!(low_confidence_tier.tier(), 5); assert!((low_confidence_tier.authority_weight() - 0.1).abs() < f32::EPSILON); // Boundary case: exactly 0.9 should be Tier 4 let boundary_tier = aphoria::bridge::observation_to_tier(0.9); assert_eq!(boundary_tier, SourceClass::Community); assert_eq!(boundary_tier.tier(), 4); } /// Test Gap 5: Supersede auto-deprecates old claims #[test] fn test_gap5_supersede_auto_deprecates() { let temp_dir = TempDir::new().expect("create temp dir"); let claims_path = temp_dir.path().join("claims.toml"); let mut claims_file = ClaimsFile::new(); // Create initial claim let claim_v1 = AuthoredClaim { id: "test-001".to_string(), concept_path: "test/feature/enabled".to_string(), predicate: "value".to_string(), value: AuthoredValue::Bool(true), comparison: ComparisonMode::Equals, provenance: "Initial implementation".to_string(), invariant: "Feature should be enabled".to_string(), consequence: "Feature disabled".to_string(), authority_tier: "expert".to_string(), evidence: vec![], category: "feature".to_string(), status: ClaimStatus::Active, supersedes: None, created_by: "dev".to_string(), created_at: "2026-02-08T10:00:00Z".to_string(), updated_at: None, }; claims_file.add(claim_v1); assert_eq!(claims_file.len(), 1); assert_eq!( claims_file.find_by_id("test-001").map(|c| &c.status), Some(&ClaimStatus::Active) ); // Supersede with v2 let claim_v2 = AuthoredClaim { id: "test-002".to_string(), concept_path: "test/feature/enabled".to_string(), predicate: "value".to_string(), value: AuthoredValue::Bool(false), comparison: ComparisonMode::Equals, provenance: "Updated after review".to_string(), invariant: "Feature should be disabled".to_string(), consequence: "Feature enabled".to_string(), authority_tier: "expert".to_string(), evidence: vec!["Review notes".to_string()], category: "feature".to_string(), status: ClaimStatus::Active, supersedes: Some("test-001".to_string()), created_by: "lead".to_string(), created_at: "2026-02-08T11:00:00Z".to_string(), updated_at: None, }; claims_file.supersede("test-001", claim_v2).expect("supersede"); // Verify old claim is superseded assert_eq!( claims_file.find_by_id("test-001").map(|c| &c.status), Some(&ClaimStatus::Superseded) ); // Verify new claim is active assert_eq!( claims_file.find_by_id("test-002").map(|c| &c.status), Some(&ClaimStatus::Active) ); // Verify lineage link assert_eq!( claims_file.find_by_id("test-002").and_then(|c| c.supersedes.as_deref()), Some("test-001") ); // Verify persistence claims_file.save(&claims_path).expect("save"); let loaded = ClaimsFile::load(&claims_path).expect("load"); assert_eq!(loaded.len(), 2); assert_eq!( loaded.find_by_id("test-001").map(|c| &c.status), Some(&ClaimStatus::Superseded) ); } /// Test Gap 5: Duplicate validation warns when creating duplicate active claims #[test] fn test_gap5_duplicate_validation_warning() { let mut claims_file = ClaimsFile::new(); // Create first claim let claim1 = AuthoredClaim { id: "dup-001".to_string(), concept_path: "test/config/timeout".to_string(), predicate: "value".to_string(), value: AuthoredValue::Number(30.0), comparison: ComparisonMode::Equals, provenance: "Initial config".to_string(), invariant: "Timeout must be 30s".to_string(), consequence: "Requests timeout too fast".to_string(), authority_tier: "team_policy".to_string(), evidence: vec![], category: "config".to_string(), status: ClaimStatus::Active, supersedes: None, created_by: "dev1".to_string(), created_at: "2026-02-08T10:00:00Z".to_string(), updated_at: None, }; claims_file.add(claim1); // Create duplicate (same concept_path + predicate, different ID) let claim2 = AuthoredClaim { id: "dup-002".to_string(), concept_path: "test/config/timeout".to_string(), // Same predicate: "value".to_string(), // Same value: AuthoredValue::Number(60.0), // Different value comparison: ComparisonMode::Equals, provenance: "Updated config".to_string(), invariant: "Timeout must be 60s".to_string(), consequence: "Requests timeout too slow".to_string(), authority_tier: "team_policy".to_string(), evidence: vec![], category: "config".to_string(), status: ClaimStatus::Active, supersedes: None, created_by: "dev2".to_string(), created_at: "2026-02-08T11:00:00Z".to_string(), updated_at: None, }; // This should print a warning (captured in test output) // but still add the claim claims_file.add(claim2); assert_eq!(claims_file.len(), 2); assert_eq!(claims_file.find_by_status(&ClaimStatus::Active).len(), 2); } /// Test Gap 5: No warning when duplicate is deprecated #[test] fn test_gap5_no_warning_for_deprecated_duplicate() { let mut claims_file = ClaimsFile::new(); // Create and deprecate first claim let claim1 = AuthoredClaim { id: "old-001".to_string(), concept_path: "test/feature/mode".to_string(), predicate: "value".to_string(), value: AuthoredValue::Text("legacy".to_string()), comparison: ComparisonMode::Equals, provenance: "Old implementation".to_string(), invariant: "Mode should be legacy".to_string(), consequence: "Mode incorrect".to_string(), authority_tier: "expert".to_string(), evidence: vec![], category: "feature".to_string(), status: ClaimStatus::Active, supersedes: None, created_by: "dev".to_string(), created_at: "2026-02-08T10:00:00Z".to_string(), updated_at: None, }; claims_file.add(claim1); claims_file.deprecate("old-001", "2026-02-08T11:00:00Z").expect("deprecate"); // Now add new claim with same concept_path/predicate // Should NOT warn because the first is deprecated let claim2 = AuthoredClaim { id: "new-001".to_string(), concept_path: "test/feature/mode".to_string(), // Same predicate: "value".to_string(), // Same value: AuthoredValue::Text("modern".to_string()), comparison: ComparisonMode::Equals, provenance: "New implementation".to_string(), invariant: "Mode should be modern".to_string(), consequence: "Mode incorrect".to_string(), authority_tier: "expert".to_string(), evidence: vec![], category: "feature".to_string(), status: ClaimStatus::Active, supersedes: None, created_by: "dev".to_string(), created_at: "2026-02-08T12:00:00Z".to_string(), updated_at: None, }; // Should NOT print warning claims_file.add(claim2); assert_eq!(claims_file.len(), 2); assert_eq!(claims_file.find_by_status(&ClaimStatus::Active).len(), 1); assert_eq!(claims_file.find_by_status(&ClaimStatus::Deprecated).len(), 1); }