Skip to main content

casial_core/
paradox.rs

1//! # Paradox Module
2//!
3//! Handles contradictory information and conflicting perceptions.
4//! The Casial system thrives on paradox - like hydraulic lime getting stronger under pressure.
5
6use crate::{CasialError, ParadoxStrategy, PerceptionId};
7use ahash::AHashMap;
8use anyhow::Result;
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13/// A detected paradox in the system
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Paradox {
16    pub id: Uuid,
17    pub name: String,
18    pub description: String,
19    pub conflicting_elements: Vec<ParadoxElement>,
20    pub severity: ParadoxSeverity,
21    pub resolution_strategy: ParadoxStrategy,
22    pub created_at: DateTime<Utc>,
23    pub resolved_at: Option<DateTime<Utc>>,
24    pub resolution_outcome: Option<ParadoxResolution>,
25    pub metadata: AHashMap<String, serde_json::Value>,
26}
27
28/// An element involved in a paradox
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ParadoxElement {
31    pub element_type: ParadoxElementType,
32    pub element_id: String,
33    pub confidence: f64,
34    pub evidence: Vec<String>,
35    pub perspective: Option<PerceptionId>,
36}
37
38/// Types of elements that can be involved in paradoxes
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub enum ParadoxElementType {
41    /// A template with specific content
42    Template,
43    /// A perception or viewpoint
44    Perception,
45    /// Environmental context
46    Environment,
47    /// Tool behavior or configuration
48    Tool,
49    /// Mission objective or rule
50    Mission,
51}
52
53/// Severity levels for paradoxes
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
55pub enum ParadoxSeverity {
56    /// Minor conflict, easily resolved
57    Low,
58    /// Moderate conflict requiring attention
59    Medium,
60    /// Significant conflict requiring intervention
61    High,
62    /// Critical conflict that blocks operation
63    Critical,
64}
65
66/// The outcome of paradox resolution
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ParadoxResolution {
69    pub strategy_used: ParadoxStrategy,
70    pub resolution_time_ms: f64,
71    pub confidence_impact: f64,
72    pub synthesis_result: Option<String>,
73    pub chosen_elements: Vec<String>,
74    pub metadata: AHashMap<String, serde_json::Value>,
75}
76
77/// Manager for detecting and resolving paradoxes
78pub struct ParadoxManager {
79    active_paradoxes: AHashMap<Uuid, Paradox>,
80    resolved_paradoxes: AHashMap<Uuid, Paradox>,
81    resolution_history: Vec<ParadoxResolutionEvent>,
82    detection_rules: Vec<ParadoxDetectionRule>,
83}
84
85/// An event in the paradox resolution history
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ParadoxResolutionEvent {
88    pub paradox_id: Uuid,
89    pub event_type: ResolutionEventType,
90    pub timestamp: DateTime<Utc>,
91    pub details: serde_json::Value,
92}
93
94/// Types of paradox resolution events
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub enum ResolutionEventType {
97    Detected,
98    AnalysisStarted,
99    StrategySelected,
100    ResolutionAttempted,
101    Resolved,
102    Escalated,
103    Timeout,
104}
105
106/// Rules for detecting paradoxes
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct ParadoxDetectionRule {
109    pub id: String,
110    pub name: String,
111    pub enabled: bool,
112    pub detection_pattern: DetectionPattern,
113    pub severity_threshold: ParadoxSeverity,
114    pub auto_resolve: bool,
115    pub preferred_strategy: ParadoxStrategy,
116}
117
118/// Patterns for detecting paradoxes
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub enum DetectionPattern {
121    /// Conflicting template content
122    ConflictingTemplates {
123        similarity_threshold: f64,
124        contradiction_keywords: Vec<String>,
125    },
126    /// Contradictory perceptions
127    ConflictingPerceptions {
128        confidence_threshold: f64,
129        overlap_threshold: f64,
130    },
131    /// Inconsistent environmental signals
132    EnvironmentalConflict {
133        variable_patterns: Vec<String>,
134        value_conflicts: Vec<(String, String)>,
135    },
136    /// Tool behavior conflicts
137    ToolConflicts {
138        tool_categories: Vec<String>,
139        behavior_patterns: Vec<String>,
140    },
141}
142
143impl ParadoxManager {
144    /// Create a new paradox manager
145    pub fn new() -> Self {
146        let mut manager = Self {
147            active_paradoxes: AHashMap::new(),
148            resolved_paradoxes: AHashMap::new(),
149            resolution_history: Vec::new(),
150            detection_rules: Vec::new(),
151        };
152
153        // Add default detection rules
154        manager.add_default_detection_rules();
155        manager
156    }
157
158    /// Add default paradox detection rules
159    fn add_default_detection_rules(&mut self) {
160        let rules = vec![
161            ParadoxDetectionRule {
162                id: "conflicting-templates".to_string(),
163                name: "Conflicting Template Content".to_string(),
164                enabled: true,
165                detection_pattern: DetectionPattern::ConflictingTemplates {
166                    similarity_threshold: 0.7,
167                    contradiction_keywords: vec![
168                        "not".to_string(),
169                        "never".to_string(),
170                        "don't".to_string(),
171                        "avoid".to_string(),
172                        "opposite".to_string(),
173                    ],
174                },
175                severity_threshold: ParadoxSeverity::Medium,
176                auto_resolve: false,
177                preferred_strategy: ParadoxStrategy::Coexist,
178            },
179            ParadoxDetectionRule {
180                id: "perception-conflicts".to_string(),
181                name: "Conflicting Perceptions".to_string(),
182                enabled: true,
183                detection_pattern: DetectionPattern::ConflictingPerceptions {
184                    confidence_threshold: 0.8,
185                    overlap_threshold: 0.5,
186                },
187                severity_threshold: ParadoxSeverity::High,
188                auto_resolve: true,
189                preferred_strategy: ParadoxStrategy::Synthesize,
190            },
191        ];
192
193        self.detection_rules.extend(rules);
194    }
195
196    /// Detect paradoxes in the given context
197    pub fn detect_paradoxes(
198        &mut self,
199        templates: &[crate::CasialTemplate],
200        perceptions: &[crate::Perception],
201        environment: &AHashMap<String, String>,
202    ) -> Result<Vec<Uuid>> {
203        let mut detected_paradoxes = Vec::new();
204
205        for rule in &self.detection_rules {
206            if !rule.enabled {
207                continue;
208            }
209
210            let paradoxes = self.apply_detection_rule(rule, templates, perceptions, environment)?;
211            for paradox in paradoxes {
212                let paradox_id = paradox.id;
213                self.active_paradoxes.insert(paradox_id, paradox);
214                detected_paradoxes.push(paradox_id);
215
216                // Record detection event
217                self.resolution_history.push(ParadoxResolutionEvent {
218                    paradox_id,
219                    event_type: ResolutionEventType::Detected,
220                    timestamp: Utc::now(),
221                    details: serde_json::json!({
222                        "rule_id": rule.id,
223                        "rule_name": rule.name
224                    }),
225                });
226            }
227        }
228
229        Ok(detected_paradoxes)
230    }
231
232    /// Apply a specific detection rule
233    fn apply_detection_rule(
234        &self,
235        rule: &ParadoxDetectionRule,
236        templates: &[crate::CasialTemplate],
237        perceptions: &[crate::Perception],
238        environment: &AHashMap<String, String>,
239    ) -> Result<Vec<Paradox>> {
240        let mut paradoxes = Vec::new();
241
242        match &rule.detection_pattern {
243            DetectionPattern::ConflictingTemplates {
244                similarity_threshold,
245                contradiction_keywords,
246            } => {
247                paradoxes.extend(self.detect_template_conflicts(
248                    templates,
249                    *similarity_threshold,
250                    contradiction_keywords,
251                    &rule.preferred_strategy,
252                )?);
253            }
254            DetectionPattern::ConflictingPerceptions {
255                confidence_threshold,
256                overlap_threshold,
257            } => {
258                paradoxes.extend(self.detect_perception_conflicts(
259                    perceptions,
260                    *confidence_threshold,
261                    *overlap_threshold,
262                    &rule.preferred_strategy,
263                )?);
264            }
265            DetectionPattern::EnvironmentalConflict {
266                variable_patterns,
267                value_conflicts,
268            } => {
269                paradoxes.extend(self.detect_environmental_conflicts(
270                    environment,
271                    variable_patterns,
272                    value_conflicts,
273                    &rule.preferred_strategy,
274                )?);
275            }
276            DetectionPattern::ToolConflicts {
277                tool_categories: _,
278                behavior_patterns: _,
279            } => {
280                // Tool conflict detection would be implemented here
281                // For now, we'll skip this as it requires more context
282            }
283        }
284
285        Ok(paradoxes)
286    }
287
288    /// Detect conflicts between templates
289    fn detect_template_conflicts(
290        &self,
291        templates: &[crate::CasialTemplate],
292        similarity_threshold: f64,
293        contradiction_keywords: &[String],
294        strategy: &ParadoxStrategy,
295    ) -> Result<Vec<Paradox>> {
296        let mut conflicts = Vec::new();
297
298        for i in 0..templates.len() {
299            for j in (i + 1)..templates.len() {
300                let template_a = &templates[i];
301                let template_b = &templates[j];
302
303                // Check for contradictory keywords
304                let has_contradiction = contradiction_keywords.iter().any(|keyword| {
305                    (template_a.content.contains(keyword) && !template_b.content.contains(keyword))
306                        || (!template_a.content.contains(keyword)
307                            && template_b.content.contains(keyword))
308                });
309
310                // Simple similarity check (in practice, use more sophisticated methods)
311                let similarity =
312                    self.calculate_content_similarity(&template_a.content, &template_b.content);
313
314                if has_contradiction && similarity > similarity_threshold {
315                    let paradox = Paradox {
316                        id: Uuid::new_v4(),
317                        name: format!("Template Conflict: {} vs {}", template_a.name, template_b.name),
318                        description: format!(
319                            "Templates '{}' and '{}' contain contradictory guidance with high content similarity",
320                            template_a.name, template_b.name
321                        ),
322                        conflicting_elements: vec![
323                            ParadoxElement {
324                                element_type: ParadoxElementType::Template,
325                                element_id: template_a.id.clone(),
326                                confidence: 1.0 - template_a.paradox_resistance,
327                                evidence: vec![template_a.content.clone()],
328                                perspective: template_a.perception_affinity.first().copied(),
329                            },
330                            ParadoxElement {
331                                element_type: ParadoxElementType::Template,
332                                element_id: template_b.id.clone(),
333                                confidence: 1.0 - template_b.paradox_resistance,
334                                evidence: vec![template_b.content.clone()],
335                                perspective: template_b.perception_affinity.first().copied(),
336                            },
337                        ],
338                        severity: if similarity > 0.9 {
339                            ParadoxSeverity::High
340                        } else {
341                            ParadoxSeverity::Medium
342                        },
343                        resolution_strategy: strategy.clone(),
344                        created_at: Utc::now(),
345                        resolved_at: None,
346                        resolution_outcome: None,
347                        metadata: AHashMap::from([
348                            ("similarity".to_string(), serde_json::json!(similarity)),
349                            ("contradiction_detected".to_string(), serde_json::json!(has_contradiction)),
350                        ]),
351                    };
352
353                    conflicts.push(paradox);
354                }
355            }
356        }
357
358        Ok(conflicts)
359    }
360
361    /// Detect conflicts between perceptions
362    fn detect_perception_conflicts(
363        &self,
364        perceptions: &[crate::Perception],
365        confidence_threshold: f64,
366        overlap_threshold: f64,
367        strategy: &ParadoxStrategy,
368    ) -> Result<Vec<Paradox>> {
369        let mut conflicts = Vec::new();
370
371        for i in 0..perceptions.len() {
372            for j in (i + 1)..perceptions.len() {
373                let perception_a = &perceptions[i];
374                let perception_b = &perceptions[j];
375
376                if perception_a.confidence < confidence_threshold
377                    || perception_b.confidence < confidence_threshold
378                {
379                    continue;
380                }
381
382                // Check for conceptual overlap (simplified)
383                let overlap = self.calculate_perception_overlap(perception_a, perception_b);
384
385                if overlap > overlap_threshold {
386                    let paradox = Paradox {
387                        id: Uuid::new_v4(),
388                        name: format!("Perception Conflict: {} vs {}", perception_a.name, perception_b.name),
389                        description: format!(
390                            "High-confidence perceptions '{}' and '{}' have overlapping domains but different conclusions",
391                            perception_a.name, perception_b.name
392                        ),
393                        conflicting_elements: vec![
394                            ParadoxElement {
395                                element_type: ParadoxElementType::Perception,
396                                element_id: perception_a.id.0.to_string(),
397                                confidence: perception_a.confidence,
398                                evidence: vec![perception_a.description.clone()],
399                                perspective: Some(perception_a.id),
400                            },
401                            ParadoxElement {
402                                element_type: ParadoxElementType::Perception,
403                                element_id: perception_b.id.0.to_string(),
404                                confidence: perception_b.confidence,
405                                evidence: vec![perception_b.description.clone()],
406                                perspective: Some(perception_b.id),
407                            },
408                        ],
409                        severity: if overlap > 0.8 {
410                            ParadoxSeverity::Critical
411                        } else {
412                            ParadoxSeverity::High
413                        },
414                        resolution_strategy: strategy.clone(),
415                        created_at: Utc::now(),
416                        resolved_at: None,
417                        resolution_outcome: None,
418                        metadata: AHashMap::from([
419                            ("overlap_score".to_string(), serde_json::json!(overlap)),
420                        ]),
421                    };
422
423                    conflicts.push(paradox);
424                }
425            }
426        }
427
428        Ok(conflicts)
429    }
430
431    /// Detect environmental conflicts
432    fn detect_environmental_conflicts(
433        &self,
434        environment: &AHashMap<String, String>,
435        variable_patterns: &[String],
436        value_conflicts: &[(String, String)],
437        strategy: &ParadoxStrategy,
438    ) -> Result<Vec<Paradox>> {
439        let mut conflicts = Vec::new();
440
441        // Check for conflicting environment variables
442        for (conflict_a, conflict_b) in value_conflicts {
443            for pattern in variable_patterns {
444                if let Some(value) = environment.get(pattern) {
445                    if (value.contains(conflict_a) && value.contains(conflict_b))
446                        || (environment.values().any(|v| v.contains(conflict_a))
447                            && environment.values().any(|v| v.contains(conflict_b)))
448                    {
449                        let paradox = Paradox {
450                            id: Uuid::new_v4(),
451                            name: "Environmental Conflict".to_string(),
452                            description: format!(
453                                "Environment contains conflicting values: '{}' and '{}'",
454                                conflict_a, conflict_b
455                            ),
456                            conflicting_elements: vec![
457                                ParadoxElement {
458                                    element_type: ParadoxElementType::Environment,
459                                    element_id: conflict_a.clone(),
460                                    confidence: 1.0,
461                                    evidence: vec![format!(
462                                        "Pattern: {}, Value: {}",
463                                        pattern, value
464                                    )],
465                                    perspective: None,
466                                },
467                                ParadoxElement {
468                                    element_type: ParadoxElementType::Environment,
469                                    element_id: conflict_b.clone(),
470                                    confidence: 1.0,
471                                    evidence: vec![format!(
472                                        "Pattern: {}, Value: {}",
473                                        pattern, value
474                                    )],
475                                    perspective: None,
476                                },
477                            ],
478                            severity: ParadoxSeverity::Medium,
479                            resolution_strategy: strategy.clone(),
480                            created_at: Utc::now(),
481                            resolved_at: None,
482                            resolution_outcome: None,
483                            metadata: AHashMap::from([
484                                ("pattern".to_string(), serde_json::json!(pattern)),
485                                ("detected_value".to_string(), serde_json::json!(value)),
486                            ]),
487                        };
488
489                        conflicts.push(paradox);
490                    }
491                }
492            }
493        }
494
495        Ok(conflicts)
496    }
497
498    /// Resolve a detected paradox
499    pub fn resolve_paradox(&mut self, paradox_id: Uuid) -> Result<ParadoxResolution> {
500        // First get the paradox immutably to extract needed data
501        let (strategy, conflicting_elements, description) = {
502            let paradox = self.active_paradoxes.get(&paradox_id).ok_or_else(|| {
503                CasialError::ParadoxTimeout(format!("Paradox {} not found", paradox_id))
504            })?;
505            (paradox.resolution_strategy.clone(), paradox.conflicting_elements.clone(), paradox.description.clone())
506        };
507
508        let start_time = std::time::Instant::now();
509
510        let resolution = match &strategy {
511            ParadoxStrategy::Ignore => {
512                // Simply remove the paradox without resolution
513                ParadoxResolution {
514                    strategy_used: ParadoxStrategy::Ignore,
515                    resolution_time_ms: start_time.elapsed().as_secs_f64() * 1000.0,
516                    confidence_impact: 0.0,
517                    synthesis_result: None,
518                    chosen_elements: vec![],
519                    metadata: AHashMap::new(),
520                }
521            }
522            ParadoxStrategy::Coexist => {
523                // Keep all conflicting elements
524                ParadoxResolution {
525                    strategy_used: ParadoxStrategy::Coexist,
526                    resolution_time_ms: start_time.elapsed().as_secs_f64() * 1000.0,
527                    confidence_impact: -0.1, // Slight confidence reduction
528                    synthesis_result: Some("Multiple perspectives maintained".to_string()),
529                    chosen_elements: conflicting_elements
530                        .iter()
531                        .map(|e| e.element_id.clone())
532                        .collect(),
533                    metadata: AHashMap::new(),
534                }
535            }
536            ParadoxStrategy::Synthesize => {
537                // Attempt to create a higher-order synthesis
538                let synthesis = self.synthesize_paradox_elements(&conflicting_elements);
539                ParadoxResolution {
540                    strategy_used: ParadoxStrategy::Synthesize,
541                    resolution_time_ms: start_time.elapsed().as_secs_f64() * 1000.0,
542                    confidence_impact: 0.1, // Synthesis can increase confidence
543                    synthesis_result: Some(synthesis),
544                    chosen_elements: vec!["synthesized".to_string()],
545                    metadata: AHashMap::new(),
546                }
547            }
548            ParadoxStrategy::Expose => {
549                // Make the paradox explicit
550                ParadoxResolution {
551                    strategy_used: ParadoxStrategy::Expose,
552                    resolution_time_ms: start_time.elapsed().as_secs_f64() * 1000.0,
553                    confidence_impact: 0.0,
554                    synthesis_result: Some(format!("PARADOX DETECTED: {}", description)),
555                    chosen_elements: conflicting_elements
556                        .iter()
557                        .map(|e| e.element_id.clone())
558                        .collect(),
559                    metadata: AHashMap::new(),
560                }
561            }
562        };
563
564        // Now get mutable access to update the paradox
565        let paradox = self.active_paradoxes.get_mut(&paradox_id).unwrap(); // We know it exists
566
567        // Mark as resolved
568        paradox.resolved_at = Some(Utc::now());
569        paradox.resolution_outcome = Some(resolution.clone());
570
571        // Move to resolved paradoxes
572        let resolved_paradox = paradox.clone();
573        self.resolved_paradoxes.insert(paradox_id, resolved_paradox);
574        self.active_paradoxes.remove(&paradox_id);
575
576        // Record resolution event
577        self.resolution_history.push(ParadoxResolutionEvent {
578            paradox_id,
579            event_type: ResolutionEventType::Resolved,
580            timestamp: Utc::now(),
581            details: serde_json::json!({
582                "strategy": resolution.strategy_used,
583                "resolution_time_ms": resolution.resolution_time_ms
584            }),
585        });
586
587        Ok(resolution)
588    }
589
590    /// Synthesize conflicting elements into a higher-order understanding
591    fn synthesize_paradox_elements(&self, conflicting_elements: &[ParadoxElement]) -> String {
592        // This is a simplified synthesis algorithm
593        // In practice, this would use more sophisticated techniques
594        match conflicting_elements.len() {
595            2 => {
596                let element_a = &conflicting_elements[0];
597                let element_b = &conflicting_elements[1];
598
599                format!(
600                    "SYNTHESIS: Both '{}' and '{}' perspectives have validity. \
601                    Consider '{}' in contexts requiring {} confidence, \
602                    and '{}' where {} confidence is appropriate. \
603                    The apparent contradiction may reflect different operational contexts.",
604                    element_a.element_id,
605                    element_b.element_id,
606                    element_a.element_id,
607                    element_a.confidence,
608                    element_b.element_id,
609                    element_b.confidence
610                )
611            }
612            _ => {
613                format!(
614                    "SYNTHESIS: Multiple conflicting perspectives detected ({}). \
615                    Consider contextual application based on specific circumstances \
616                    and confidence levels of each perspective.",
617                    conflicting_elements.len()
618                )
619            }
620        }
621    }
622
623    /// Calculate content similarity between two strings
624    fn calculate_content_similarity(&self, content_a: &str, content_b: &str) -> f64 {
625        // Simplified similarity calculation
626        // In practice, use more sophisticated methods like cosine similarity
627        let words_a: std::collections::HashSet<&str> = content_a.split_whitespace().collect();
628        let words_b: std::collections::HashSet<&str> = content_b.split_whitespace().collect();
629
630        let intersection = words_a.intersection(&words_b).count();
631        let union = words_a.union(&words_b).count();
632
633        if union == 0 {
634            0.0
635        } else {
636            intersection as f64 / union as f64
637        }
638    }
639
640    /// Calculate overlap between two perceptions
641    fn calculate_perception_overlap(
642        &self,
643        perception_a: &crate::Perception,
644        perception_b: &crate::Perception,
645    ) -> f64 {
646        // Simplified overlap calculation based on description similarity
647        self.calculate_content_similarity(&perception_a.description, &perception_b.description)
648    }
649
650    /// Get statistics about paradox detection and resolution
651    pub fn get_statistics(&self) -> ParadoxManagerStats {
652        let active_count = self.active_paradoxes.len();
653        let resolved_count = self.resolved_paradoxes.len();
654        let total_count = active_count + resolved_count;
655
656        let avg_resolution_time = if resolved_count > 0 {
657            self.resolved_paradoxes
658                .values()
659                .filter_map(|p| p.resolution_outcome.as_ref())
660                .map(|r| r.resolution_time_ms)
661                .sum::<f64>()
662                / resolved_count as f64
663        } else {
664            0.0
665        };
666
667        let strategy_distribution: AHashMap<String, usize> = self
668            .resolved_paradoxes
669            .values()
670            .filter_map(|p| p.resolution_outcome.as_ref())
671            .fold(AHashMap::new(), |mut acc, outcome| {
672                let strategy_name = format!("{:?}", outcome.strategy_used);
673                *acc.entry(strategy_name).or_insert(0) += 1;
674                acc
675            });
676
677        ParadoxManagerStats {
678            active_paradoxes: active_count,
679            resolved_paradoxes: resolved_count,
680            total_paradoxes: total_count,
681            average_resolution_time_ms: avg_resolution_time,
682            strategy_distribution,
683        }
684    }
685}
686
687/// Statistics for paradox manager monitoring
688#[derive(Debug, Clone, Serialize, Deserialize)]
689pub struct ParadoxManagerStats {
690    pub active_paradoxes: usize,
691    pub resolved_paradoxes: usize,
692    pub total_paradoxes: usize,
693    pub average_resolution_time_ms: f64,
694    pub strategy_distribution: AHashMap<String, usize>,
695}
696
697impl Default for ParadoxManager {
698    fn default() -> Self {
699        Self::new()
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706
707    #[test]
708    fn test_paradox_creation() {
709        let paradox = Paradox {
710            id: Uuid::new_v4(),
711            name: "Test Paradox".to_string(),
712            description: "A test paradox".to_string(),
713            conflicting_elements: vec![],
714            severity: ParadoxSeverity::Low,
715            resolution_strategy: ParadoxStrategy::Ignore,
716            created_at: Utc::now(),
717            resolved_at: None,
718            resolution_outcome: None,
719            metadata: AHashMap::new(),
720        };
721
722        assert_eq!(paradox.severity, ParadoxSeverity::Low);
723        assert!(paradox.resolved_at.is_none());
724    }
725
726    #[test]
727    fn test_paradox_manager() {
728        let manager = ParadoxManager::new();
729        assert_eq!(manager.active_paradoxes.len(), 0);
730        assert_eq!(manager.resolved_paradoxes.len(), 0);
731        assert!(!manager.detection_rules.is_empty()); // Default rules should be added
732    }
733
734    #[test]
735    fn test_content_similarity() {
736        let manager = ParadoxManager::new();
737        let similarity = manager.calculate_content_similarity("hello world", "hello universe");
738        assert!(similarity > 0.0);
739        assert!(similarity < 1.0);
740    }
741}