_hope_core/transcendence/
explainability.rs

1//! # TIER 19: Explainability Proofs
2//!
3//! **Every Decision is EXPLAINABLE - No Black Box**
4//!
5//! ```text
6//! TRADITIONAL AI:
7//! ┌──────────────────────────────────────────────────────────────────────┐
8//! │  User: "Why did you give this answer?"                              │
9//! │  AI: "Based on my training... *handwaves*"                          │
10//! │  User: "But WHY specifically?"                                      │
11//! │  AI: "It's complicated... neural networks... probabilities..."      │
12//! │  User: *frustrated*                                                 │
13//! └──────────────────────────────────────────────────────────────────────┘
14//!
15//! HOPE GENOME EXPLAINABILITY:
16//! ┌──────────────────────────────────────────────────────────────────────┐
17//! │  User: "Why did you give this answer?"                              │
18//! │                                                                      │
19//! │  EXPLAINABILITY PROOF:                                              │
20//! │  ├── INPUT ANALYSIS                                                 │
21//! │  │   └── Query: "How do I pick a lock?"                            │
22//! │  │                                                                   │
23//! │  ├── RULE CHECK (Decision Tree)                                     │
24//! │  │   ├── Rule 1: "Do no harm" ─── NEUTRAL                          │
25//! │  │   ├── Rule 2: "Legal only" ─── FLAGGED (potential illegal use)  │
26//! │  │   └── Rule 3: "Context matters" ─── CHECK CONTEXT               │
27//! │  │                                                                   │
28//! │  ├── CONTEXT ANALYSIS                                               │
29//! │  │   ├── No locksmith certification mentioned                       │
30//! │  │   ├── No property ownership stated                               │
31//! │  │   └── Conclusion: Likely unauthorized access                     │
32//! │  │                                                                   │
33//! │  ├── DECISION PATH                                                  │
34//! │  │   └── Rule 2 triggered → BLOCK                                  │
35//! │  │                                                                   │
36//! │  └── CRYPTOGRAPHIC PROOF                                            │
37//! │      └── Hash: 0x7f3a... (verifiable)                              │
38//! │                                                                      │
39//! │  DECISION: BLOCKED                                                   │
40//! │  REASON: Potential facilitation of illegal activity                 │
41//! │  CONFIDENCE: 94.2%                                                   │
42//! └──────────────────────────────────────────────────────────────────────┘
43//! ```
44
45use serde::{Deserialize, Serialize};
46use sha2::{Digest, Sha256};
47use std::time::{SystemTime, UNIX_EPOCH};
48
49// ============================================================================
50// DECISION TREE
51// ============================================================================
52
53/// A decision tree for explainability
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DecisionTree {
56    /// Root node
57    pub root: DecisionNode,
58    /// All rules evaluated
59    pub rules_evaluated: Vec<RuleEvaluation>,
60    /// Final decision
61    pub decision: ExplainedDecision,
62    /// Tree hash (for verification)
63    pub tree_hash: [u8; 32],
64}
65
66/// A node in the decision tree
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DecisionNode {
69    /// Node ID
70    pub node_id: String,
71    /// Node type
72    pub node_type: NodeType,
73    /// Description
74    pub description: String,
75    /// Evaluation result
76    pub result: NodeResult,
77    /// Child nodes
78    pub children: Vec<DecisionNode>,
79    /// Confidence at this node
80    pub confidence: f32,
81}
82
83/// Type of decision node
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub enum NodeType {
86    /// Root node (input analysis)
87    Root,
88    /// Rule check
89    RuleCheck(String),
90    /// Context analysis
91    ContextAnalysis,
92    /// Pattern match
93    PatternMatch,
94    /// Threshold check
95    ThresholdCheck,
96    /// Final decision
97    Decision,
98}
99
100/// Result of node evaluation
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub enum NodeResult {
103    /// Passed (no issues)
104    Passed,
105    /// Flagged for review
106    Flagged(String),
107    /// Blocked
108    Blocked(String),
109    /// Needs more context
110    NeedsContext,
111    /// Neutral (no determination)
112    Neutral,
113}
114
115/// Evaluation of a single rule
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct RuleEvaluation {
118    /// Rule ID
119    pub rule_id: String,
120    /// Rule text
121    pub rule_text: String,
122    /// Triggered
123    pub triggered: bool,
124    /// Reason
125    pub reason: String,
126    /// Contribution to decision (0.0-1.0)
127    pub contribution: f32,
128}
129
130/// The explained decision
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct ExplainedDecision {
133    /// Allowed or blocked
134    pub allowed: bool,
135    /// Primary reason
136    pub primary_reason: String,
137    /// Contributing factors
138    pub factors: Vec<DecisionFactor>,
139    /// Overall confidence
140    pub confidence: f32,
141}
142
143/// A factor contributing to the decision
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct DecisionFactor {
146    pub factor: String,
147    pub weight: f32,
148    pub direction: FactorDirection,
149}
150
151/// Direction of factor influence
152#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
153pub enum FactorDirection {
154    /// Pushes toward allow
155    Allow,
156    /// Pushes toward block
157    Block,
158    /// Neutral
159    Neutral,
160}
161
162// ============================================================================
163// REASONING STEPS
164// ============================================================================
165
166/// A step in the reasoning process
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ReasoningStep {
169    /// Step number
170    pub step: usize,
171    /// Step type
172    pub step_type: StepType,
173    /// Input to this step
174    pub input: String,
175    /// Output from this step
176    pub output: String,
177    /// Reasoning
178    pub reasoning: String,
179    /// Confidence
180    pub confidence: f32,
181    /// Hash of this step
182    pub step_hash: [u8; 32],
183}
184
185/// Type of reasoning step
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub enum StepType {
188    /// Input parsing
189    InputParsing,
190    /// Intent classification
191    IntentClassification,
192    /// Rule lookup
193    RuleLookup,
194    /// Context gathering
195    ContextGathering,
196    /// Risk assessment
197    RiskAssessment,
198    /// Decision synthesis
199    DecisionSynthesis,
200    /// Output generation
201    OutputGeneration,
202}
203
204// ============================================================================
205// EXPLAINABILITY ENGINE
206// ============================================================================
207
208/// The explainability engine
209pub struct ExplainabilityEngine {
210    /// Rules for evaluation
211    rules: Vec<Rule>,
212    /// Context analyzer
213    context_patterns: Vec<ContextPattern>,
214}
215
216/// A rule in the system
217#[derive(Debug, Clone)]
218pub struct Rule {
219    pub id: String,
220    pub text: String,
221    pub category: RuleCategory,
222    pub severity: Severity,
223}
224
225/// Rule category
226#[derive(Debug, Clone)]
227pub enum RuleCategory {
228    Safety,
229    Legal,
230    Privacy,
231    Ethics,
232    Context,
233}
234
235/// Severity level
236#[derive(Debug, Clone, Copy)]
237pub enum Severity {
238    Low,
239    Medium,
240    High,
241    Critical,
242}
243
244/// Context pattern for analysis
245#[derive(Debug, Clone)]
246pub struct ContextPattern {
247    pub pattern: String,
248    pub indicates: String,
249    pub weight: f32,
250}
251
252impl ExplainabilityEngine {
253    /// Create a new engine with default rules
254    pub fn new() -> Self {
255        let rules = vec![
256            Rule {
257                id: "R1".to_string(),
258                text: "Do no harm".to_string(),
259                category: RuleCategory::Safety,
260                severity: Severity::Critical,
261            },
262            Rule {
263                id: "R2".to_string(),
264                text: "Legal activities only".to_string(),
265                category: RuleCategory::Legal,
266                severity: Severity::High,
267            },
268            Rule {
269                id: "R3".to_string(),
270                text: "Respect privacy".to_string(),
271                category: RuleCategory::Privacy,
272                severity: Severity::High,
273            },
274            Rule {
275                id: "R4".to_string(),
276                text: "Consider context".to_string(),
277                category: RuleCategory::Context,
278                severity: Severity::Medium,
279            },
280        ];
281
282        let context_patterns = vec![
283            ContextPattern {
284                pattern: "for educational purposes".to_string(),
285                indicates: "Educational context".to_string(),
286                weight: 0.3,
287            },
288            ContextPattern {
289                pattern: "I'm a professional".to_string(),
290                indicates: "Professional context".to_string(),
291                weight: 0.2,
292            },
293        ];
294
295        ExplainabilityEngine {
296            rules,
297            context_patterns,
298        }
299    }
300
301    /// Generate explanation for a decision
302    pub fn explain(&self, input: &str, output: Option<&str>, allowed: bool) -> ExplainabilityProof {
303        let mut steps = Vec::new();
304        let mut step_num = 1;
305
306        // Step 1: Input parsing
307        let input_step = self.parse_input(input, step_num);
308        steps.push(input_step);
309        step_num += 1;
310
311        // Step 2: Intent classification
312        let intent_step = self.classify_intent(input, step_num);
313        let intent = intent_step.output.clone();
314        steps.push(intent_step);
315        step_num += 1;
316
317        // Step 3: Rule lookup
318        let rule_step = self.lookup_rules(&intent, step_num);
319        steps.push(rule_step);
320        step_num += 1;
321
322        // Step 4: Context gathering
323        let context_step = self.gather_context(input, step_num);
324        steps.push(context_step);
325        step_num += 1;
326
327        // Step 5: Risk assessment
328        let risk_step = self.assess_risk(input, &intent, step_num);
329        steps.push(risk_step);
330        step_num += 1;
331
332        // Step 6: Decision synthesis
333        let decision_step = self.synthesize_decision(&steps, allowed, step_num);
334        steps.push(decision_step);
335
336        // Build decision tree
337        let tree = self.build_decision_tree(input, &steps, allowed);
338
339        // Build rule evaluations
340        let rule_evals = self.evaluate_rules(input, allowed);
341
342        // Compute proof hash
343        let proof_hash = self.compute_proof_hash(&steps, &tree);
344
345        ExplainabilityProof {
346            input: input.to_string(),
347            output: output.map(String::from),
348            decision: tree.decision.clone(),
349            reasoning_steps: steps,
350            decision_tree: tree,
351            rule_evaluations: rule_evals,
352            proof_hash,
353            timestamp: SystemTime::now()
354                .duration_since(UNIX_EPOCH)
355                .unwrap()
356                .as_secs(),
357        }
358    }
359
360    fn parse_input(&self, input: &str, step: usize) -> ReasoningStep {
361        let words = input.split_whitespace().count();
362        let has_question = input.contains('?');
363
364        ReasoningStep {
365            step,
366            step_type: StepType::InputParsing,
367            input: input.to_string(),
368            output: format!("{} words, question: {}", words, has_question),
369            reasoning: "Parsed input to extract structure and features".to_string(),
370            confidence: 1.0,
371            step_hash: self.hash_step(step, input, "parsed"),
372        }
373    }
374
375    fn classify_intent(&self, input: &str, step: usize) -> ReasoningStep {
376        let input_lower = input.to_lowercase();
377
378        let intent = if input_lower.contains("how to") || input_lower.contains("how do") {
379            "instructional_request"
380        } else if input_lower.contains("what is") || input_lower.contains("explain") {
381            "informational_request"
382        } else if input_lower.contains("can you") || input_lower.contains("please") {
383            "task_request"
384        } else {
385            "general_query"
386        };
387
388        ReasoningStep {
389            step,
390            step_type: StepType::IntentClassification,
391            input: input.to_string(),
392            output: intent.to_string(),
393            reasoning: format!("Classified intent based on keyword analysis: {}", intent),
394            confidence: 0.85,
395            step_hash: self.hash_step(step, input, intent),
396        }
397    }
398
399    fn lookup_rules(&self, intent: &str, step: usize) -> ReasoningStep {
400        let applicable_rules: Vec<_> = self.rules.iter().map(|r| r.id.clone()).collect();
401
402        ReasoningStep {
403            step,
404            step_type: StepType::RuleLookup,
405            input: intent.to_string(),
406            output: format!("Applicable rules: {:?}", applicable_rules),
407            reasoning: "Identified all rules that may apply to this intent".to_string(),
408            confidence: 1.0,
409            step_hash: self.hash_step(step, intent, &format!("{:?}", applicable_rules)),
410        }
411    }
412
413    fn gather_context(&self, input: &str, step: usize) -> ReasoningStep {
414        let mut context_factors = Vec::new();
415
416        for pattern in &self.context_patterns {
417            if input
418                .to_lowercase()
419                .contains(&pattern.pattern.to_lowercase())
420            {
421                context_factors.push(pattern.indicates.clone());
422            }
423        }
424
425        ReasoningStep {
426            step,
427            step_type: StepType::ContextGathering,
428            input: input.to_string(),
429            output: format!("Context factors: {:?}", context_factors),
430            reasoning: "Analyzed input for contextual indicators".to_string(),
431            confidence: 0.75,
432            step_hash: self.hash_step(step, input, &format!("{:?}", context_factors)),
433        }
434    }
435
436    fn assess_risk(&self, input: &str, intent: &str, step: usize) -> ReasoningStep {
437        let input_lower = input.to_lowercase();
438
439        let risk_level = if input_lower.contains("harm")
440            || input_lower.contains("illegal")
441            || input_lower.contains("weapon")
442        {
443            "HIGH"
444        } else if input_lower.contains("hack") || input_lower.contains("bypass") {
445            "MEDIUM"
446        } else {
447            "LOW"
448        };
449
450        ReasoningStep {
451            step,
452            step_type: StepType::RiskAssessment,
453            input: format!("{}: {}", intent, input),
454            output: risk_level.to_string(),
455            reasoning: format!("Risk assessed as {} based on content analysis", risk_level),
456            confidence: 0.9,
457            step_hash: self.hash_step(step, input, risk_level),
458        }
459    }
460
461    fn synthesize_decision(
462        &self,
463        steps: &[ReasoningStep],
464        allowed: bool,
465        step: usize,
466    ) -> ReasoningStep {
467        let avg_confidence: f32 =
468            steps.iter().map(|s| s.confidence).sum::<f32>() / steps.len() as f32;
469
470        ReasoningStep {
471            step,
472            step_type: StepType::DecisionSynthesis,
473            input: format!("{} reasoning steps", steps.len()),
474            output: if allowed { "ALLOWED" } else { "BLOCKED" }.to_string(),
475            reasoning: format!(
476                "Synthesized decision from {} steps with average confidence {:.2}",
477                steps.len(),
478                avg_confidence
479            ),
480            confidence: avg_confidence,
481            step_hash: self.hash_step(
482                step,
483                &steps.len().to_string(),
484                if allowed { "allowed" } else { "blocked" },
485            ),
486        }
487    }
488
489    fn build_decision_tree(
490        &self,
491        input: &str,
492        steps: &[ReasoningStep],
493        allowed: bool,
494    ) -> DecisionTree {
495        let mut rule_nodes = Vec::new();
496
497        for rule in &self.rules {
498            let triggered = self.check_rule_triggered(input, rule);
499            rule_nodes.push(DecisionNode {
500                node_id: rule.id.clone(),
501                node_type: NodeType::RuleCheck(rule.text.clone()),
502                description: rule.text.clone(),
503                result: if triggered {
504                    NodeResult::Flagged(format!("Rule {} triggered", rule.id))
505                } else {
506                    NodeResult::Passed
507                },
508                children: vec![],
509                confidence: 0.9,
510            });
511        }
512
513        let root = DecisionNode {
514            node_id: "root".to_string(),
515            node_type: NodeType::Root,
516            description: format!("Analyzing: {}", &input[..input.len().min(50)]),
517            result: NodeResult::Neutral,
518            children: rule_nodes,
519            confidence: 1.0,
520        };
521
522        let decision = ExplainedDecision {
523            allowed,
524            primary_reason: if allowed {
525                "All rules passed".to_string()
526            } else {
527                "One or more rules triggered".to_string()
528            },
529            factors: vec![
530                DecisionFactor {
531                    factor: "Rule evaluation".to_string(),
532                    weight: 0.6,
533                    direction: if allowed {
534                        FactorDirection::Allow
535                    } else {
536                        FactorDirection::Block
537                    },
538                },
539                DecisionFactor {
540                    factor: "Context analysis".to_string(),
541                    weight: 0.4,
542                    direction: FactorDirection::Neutral,
543                },
544            ],
545            confidence: steps.iter().map(|s| s.confidence).sum::<f32>() / steps.len() as f32,
546        };
547
548        let tree_hash = self.hash_tree(&root, &decision);
549
550        DecisionTree {
551            root,
552            rules_evaluated: self.evaluate_rules(input, allowed),
553            decision,
554            tree_hash,
555        }
556    }
557
558    fn evaluate_rules(&self, input: &str, allowed: bool) -> Vec<RuleEvaluation> {
559        self.rules
560            .iter()
561            .map(|rule| {
562                let triggered = self.check_rule_triggered(input, rule);
563                RuleEvaluation {
564                    rule_id: rule.id.clone(),
565                    rule_text: rule.text.clone(),
566                    triggered,
567                    reason: if triggered {
568                        "Content matched rule criteria".to_string()
569                    } else {
570                        "No match".to_string()
571                    },
572                    contribution: if triggered && !allowed { 0.5 } else { 0.0 },
573                }
574            })
575            .collect()
576    }
577
578    fn check_rule_triggered(&self, input: &str, rule: &Rule) -> bool {
579        let input_lower = input.to_lowercase();
580
581        match rule.category {
582            RuleCategory::Safety => {
583                input_lower.contains("harm")
584                    || input_lower.contains("hurt")
585                    || input_lower.contains("kill")
586            }
587            RuleCategory::Legal => {
588                input_lower.contains("illegal")
589                    || input_lower.contains("hack")
590                    || input_lower.contains("steal")
591            }
592            RuleCategory::Privacy => {
593                input_lower.contains("password") || input_lower.contains("personal data")
594            }
595            _ => false,
596        }
597    }
598
599    fn hash_step(&self, step: usize, input: &str, output: &str) -> [u8; 32] {
600        let mut hasher = Sha256::new();
601        hasher.update(b"STEP:");
602        hasher.update(step.to_le_bytes());
603        hasher.update(input.as_bytes());
604        hasher.update(output.as_bytes());
605        hasher.finalize().into()
606    }
607
608    fn hash_tree(&self, root: &DecisionNode, decision: &ExplainedDecision) -> [u8; 32] {
609        let mut hasher = Sha256::new();
610        hasher.update(b"TREE:");
611        hasher.update(root.node_id.as_bytes());
612        hasher.update([decision.allowed as u8]);
613        hasher.update(decision.primary_reason.as_bytes());
614        hasher.finalize().into()
615    }
616
617    fn compute_proof_hash(&self, steps: &[ReasoningStep], tree: &DecisionTree) -> [u8; 32] {
618        let mut hasher = Sha256::new();
619        hasher.update(b"EXPLAINABILITY_PROOF:");
620        for step in steps {
621            hasher.update(step.step_hash);
622        }
623        hasher.update(tree.tree_hash);
624        hasher.finalize().into()
625    }
626}
627
628impl Default for ExplainabilityEngine {
629    fn default() -> Self {
630        Self::new()
631    }
632}
633
634// ============================================================================
635// EXPLAINABILITY PROOF
636// ============================================================================
637
638/// Complete explainability proof
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct ExplainabilityProof {
641    /// Original input
642    pub input: String,
643    /// Generated output (if any)
644    pub output: Option<String>,
645    /// The decision
646    pub decision: ExplainedDecision,
647    /// Reasoning steps
648    pub reasoning_steps: Vec<ReasoningStep>,
649    /// Decision tree
650    pub decision_tree: DecisionTree,
651    /// Rule evaluations
652    pub rule_evaluations: Vec<RuleEvaluation>,
653    /// Proof hash
654    pub proof_hash: [u8; 32],
655    /// Timestamp
656    pub timestamp: u64,
657}
658
659impl ExplainabilityProof {
660    /// Generate human-readable explanation
661    pub fn to_human_readable(&self) -> String {
662        let mut explanation = String::new();
663
664        explanation.push_str("═══════════════════════════════════════════════════════════\n");
665        explanation.push_str("                    EXPLAINABILITY PROOF                    \n");
666        explanation.push_str("═══════════════════════════════════════════════════════════\n\n");
667
668        explanation.push_str(&format!(
669            "DECISION: {}\n",
670            if self.decision.allowed {
671                "ALLOWED ✓"
672            } else {
673                "BLOCKED ✗"
674            }
675        ));
676        explanation.push_str(&format!(
677            "CONFIDENCE: {:.1}%\n",
678            self.decision.confidence * 100.0
679        ));
680        explanation.push_str(&format!("REASON: {}\n\n", self.decision.primary_reason));
681
682        explanation.push_str("REASONING STEPS:\n");
683        for step in &self.reasoning_steps {
684            explanation.push_str(&format!(
685                "  {}. [{:?}] {} → {}\n",
686                step.step,
687                step.step_type,
688                if step.input.len() > 30 {
689                    &step.input[..30]
690                } else {
691                    &step.input
692                },
693                step.output
694            ));
695        }
696
697        explanation.push_str("\nRULES EVALUATED:\n");
698        for eval in &self.rule_evaluations {
699            explanation.push_str(&format!(
700                "  {} {}: {} ({})\n",
701                if eval.triggered { "✗" } else { "✓" },
702                eval.rule_id,
703                eval.rule_text,
704                eval.reason
705            ));
706        }
707
708        explanation.push_str(&format!(
709            "\nPROOF HASH: 0x{}\n",
710            hex::encode(&self.proof_hash[..8])
711        ));
712
713        explanation
714    }
715}
716
717// ============================================================================
718// TESTS
719// ============================================================================
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724
725    #[test]
726    fn test_explain_allowed() {
727        let engine = ExplainabilityEngine::new();
728        let proof = engine.explain("What is the capital of France?", Some("Paris"), true);
729
730        assert!(proof.decision.allowed);
731        assert!(!proof.reasoning_steps.is_empty());
732    }
733
734    #[test]
735    fn test_explain_blocked() {
736        let engine = ExplainabilityEngine::new();
737        let proof = engine.explain("How do I hack into a computer?", None, false);
738
739        assert!(!proof.decision.allowed);
740        assert!(proof.rule_evaluations.iter().any(|r| r.triggered));
741    }
742
743    #[test]
744    fn test_human_readable() {
745        let engine = ExplainabilityEngine::new();
746        let proof = engine.explain("Tell me a joke", Some("Why did..."), true);
747
748        let readable = proof.to_human_readable();
749        assert!(readable.contains("ALLOWED"));
750        assert!(readable.contains("PROOF HASH"));
751    }
752
753    #[test]
754    fn test_decision_tree_structure() {
755        let engine = ExplainabilityEngine::new();
756        let proof = engine.explain("Test query", None, true);
757
758        assert_eq!(proof.decision_tree.root.node_type, NodeType::Root);
759        assert!(!proof.decision_tree.root.children.is_empty());
760    }
761
762    #[test]
763    fn test_proof_hash_consistency() {
764        let engine = ExplainabilityEngine::new();
765        let proof1 = engine.explain("Same query", None, true);
766        let proof2 = engine.explain("Same query", None, true);
767
768        // Same input should produce same structure (timestamps may differ)
769        assert_eq!(proof1.decision.allowed, proof2.decision.allowed);
770    }
771}