mcp_langbase_reasoning/storage/
mod.rs

1//! Storage layer for reasoning session persistence.
2//!
3//! This module provides SQLite-based storage for sessions, thoughts, branches,
4//! checkpoints, graph nodes, and other reasoning artifacts.
5
6mod sqlite;
7
8#[cfg(test)]
9#[path = "types_tests.rs"]
10mod types_tests;
11
12pub use sqlite::{
13    get_record_skip_count, get_timestamp_reconstruction_count, reset_record_skip_count,
14    reset_timestamp_reconstruction_count, SqliteStorage,
15};
16
17use async_trait::async_trait;
18use chrono::{DateTime, Utc};
19use serde::{Deserialize, Serialize};
20use uuid::Uuid;
21
22use crate::error::StorageResult;
23
24/// A reasoning session context that groups related thoughts.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Session {
27    /// Unique session identifier.
28    pub id: String,
29    /// Reasoning mode (e.g., "linear", "tree", "got").
30    pub mode: String,
31    /// When the session was created.
32    pub created_at: DateTime<Utc>,
33    /// When the session was last updated.
34    pub updated_at: DateTime<Utc>,
35    /// Optional metadata for the session.
36    pub metadata: Option<serde_json::Value>,
37    /// Active branch for tree mode.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub active_branch_id: Option<String>,
40}
41
42/// A single reasoning step or thought within a session.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Thought {
45    /// Unique thought identifier.
46    pub id: String,
47    /// Parent session ID.
48    pub session_id: String,
49    /// The thought content/text.
50    pub content: String,
51    /// Confidence score (0.0-1.0).
52    pub confidence: f64,
53    /// Reasoning mode that generated this thought.
54    pub mode: String,
55    /// Parent thought ID for chained reasoning.
56    pub parent_id: Option<String>,
57    /// Branch ID for tree mode.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub branch_id: Option<String>,
60    /// When the thought was created.
61    pub created_at: DateTime<Utc>,
62    /// Optional metadata.
63    pub metadata: Option<serde_json::Value>,
64}
65
66/// A reasoning branch in tree mode, representing an exploration path.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Branch {
69    /// Unique branch identifier.
70    pub id: String,
71    /// Parent session ID.
72    pub session_id: String,
73    /// Optional human-readable name.
74    pub name: Option<String>,
75    /// Parent branch ID for nested branches.
76    pub parent_branch_id: Option<String>,
77    /// Priority score for branch selection.
78    pub priority: f64,
79    /// Confidence score for this branch.
80    pub confidence: f64,
81    /// Current state of the branch.
82    pub state: BranchState,
83    /// When the branch was created.
84    pub created_at: DateTime<Utc>,
85    /// When the branch was last updated.
86    pub updated_at: DateTime<Utc>,
87    /// Optional metadata.
88    pub metadata: Option<serde_json::Value>,
89}
90
91/// State of a reasoning branch.
92#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub enum BranchState {
95    /// Branch is actively being explored.
96    #[default]
97    Active,
98    /// Branch has been completed successfully.
99    Completed,
100    /// Branch has been abandoned.
101    Abandoned,
102}
103
104impl std::fmt::Display for BranchState {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        match self {
107            BranchState::Active => write!(f, "active"),
108            BranchState::Completed => write!(f, "completed"),
109            BranchState::Abandoned => write!(f, "abandoned"),
110        }
111    }
112}
113
114impl std::str::FromStr for BranchState {
115    type Err = String;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        match s.to_lowercase().as_str() {
119            "active" => Ok(BranchState::Active),
120            "completed" => Ok(BranchState::Completed),
121            "abandoned" => Ok(BranchState::Abandoned),
122            _ => Err(format!("Unknown branch state: {}", s)),
123        }
124    }
125}
126
127/// Cross-reference between branches for linking related reasoning paths.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CrossRef {
130    /// Unique cross-reference identifier.
131    pub id: String,
132    /// Source branch ID.
133    pub from_branch_id: String,
134    /// Target branch ID.
135    pub to_branch_id: String,
136    /// Type of relationship between branches.
137    pub ref_type: CrossRefType,
138    /// Optional explanation for the cross-reference.
139    pub reason: Option<String>,
140    /// Strength of the relationship (0.0-1.0).
141    pub strength: f64,
142    /// When the cross-reference was created.
143    pub created_at: DateTime<Utc>,
144}
145
146/// Type of cross-reference between branches.
147#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149pub enum CrossRefType {
150    /// This branch supports the target branch's conclusions.
151    #[default]
152    Supports,
153    /// This branch contradicts the target branch's conclusions.
154    Contradicts,
155    /// This branch extends or builds upon the target branch.
156    Extends,
157    /// This branch offers an alternative approach to the target.
158    Alternative,
159    /// This branch depends on the target branch's conclusions.
160    Depends,
161}
162
163impl std::fmt::Display for CrossRefType {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            CrossRefType::Supports => write!(f, "supports"),
167            CrossRefType::Contradicts => write!(f, "contradicts"),
168            CrossRefType::Extends => write!(f, "extends"),
169            CrossRefType::Alternative => write!(f, "alternative"),
170            CrossRefType::Depends => write!(f, "depends"),
171        }
172    }
173}
174
175impl std::str::FromStr for CrossRefType {
176    type Err = String;
177
178    fn from_str(s: &str) -> Result<Self, Self::Err> {
179        match s.to_lowercase().as_str() {
180            "supports" => Ok(CrossRefType::Supports),
181            "contradicts" => Ok(CrossRefType::Contradicts),
182            "extends" => Ok(CrossRefType::Extends),
183            "alternative" => Ok(CrossRefType::Alternative),
184            "depends" => Ok(CrossRefType::Depends),
185            _ => Err(format!("Unknown cross-ref type: {}", s)),
186        }
187    }
188}
189
190/// Checkpoint for state snapshots enabling backtracking.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Checkpoint {
193    /// Unique checkpoint identifier.
194    pub id: String,
195    /// Parent session ID.
196    pub session_id: String,
197    /// Optional branch ID for branch-specific checkpoints.
198    pub branch_id: Option<String>,
199    /// Human-readable checkpoint name.
200    pub name: String,
201    /// Optional description of the checkpoint state.
202    pub description: Option<String>,
203    /// Serialized state snapshot data.
204    pub snapshot: serde_json::Value,
205    /// When the checkpoint was created.
206    pub created_at: DateTime<Utc>,
207}
208
209/// Graph node for Graph-of-Thoughts reasoning.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct GraphNode {
212    /// Unique node identifier.
213    pub id: String,
214    /// Parent session ID.
215    pub session_id: String,
216    /// Node content/thought text.
217    pub content: String,
218    /// Type of node in the reasoning graph.
219    pub node_type: NodeType,
220    /// Quality score from evaluation (0.0-1.0).
221    pub score: Option<f64>,
222    /// Depth level in the graph (0 = root).
223    pub depth: i32,
224    /// Whether this is a terminal/conclusion node.
225    pub is_terminal: bool,
226    /// Whether this is a root/starting node.
227    pub is_root: bool,
228    /// Whether this node is active (not pruned).
229    pub is_active: bool,
230    /// When the node was created.
231    pub created_at: DateTime<Utc>,
232    /// Optional metadata.
233    pub metadata: Option<serde_json::Value>,
234}
235
236/// Type of graph node in Graph-of-Thoughts reasoning.
237#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "snake_case")]
239pub enum NodeType {
240    /// A standard reasoning thought.
241    #[default]
242    Thought,
243    /// A hypothesis to be tested.
244    Hypothesis,
245    /// A conclusion drawn from reasoning.
246    Conclusion,
247    /// An aggregation of multiple nodes.
248    Aggregation,
249    /// The root/starting node.
250    Root,
251    /// A refinement of a previous node.
252    Refinement,
253    /// A terminal/final node.
254    Terminal,
255}
256
257impl std::fmt::Display for NodeType {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        match self {
260            NodeType::Thought => write!(f, "thought"),
261            NodeType::Hypothesis => write!(f, "hypothesis"),
262            NodeType::Conclusion => write!(f, "conclusion"),
263            NodeType::Aggregation => write!(f, "aggregation"),
264            NodeType::Root => write!(f, "root"),
265            NodeType::Refinement => write!(f, "refinement"),
266            NodeType::Terminal => write!(f, "terminal"),
267        }
268    }
269}
270
271impl std::str::FromStr for NodeType {
272    type Err = String;
273
274    fn from_str(s: &str) -> Result<Self, Self::Err> {
275        match s.to_lowercase().as_str() {
276            "thought" => Ok(NodeType::Thought),
277            "hypothesis" => Ok(NodeType::Hypothesis),
278            "conclusion" => Ok(NodeType::Conclusion),
279            "aggregation" => Ok(NodeType::Aggregation),
280            "root" => Ok(NodeType::Root),
281            "refinement" => Ok(NodeType::Refinement),
282            "terminal" => Ok(NodeType::Terminal),
283            _ => Err(format!("Unknown node type: {}", s)),
284        }
285    }
286}
287
288/// Graph edge for Graph-of-Thoughts connections between nodes.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct GraphEdge {
291    /// Unique edge identifier.
292    pub id: String,
293    /// Parent session ID.
294    pub session_id: String,
295    /// Source node ID.
296    pub from_node: String,
297    /// Target node ID.
298    pub to_node: String,
299    /// Type of relationship between nodes.
300    pub edge_type: EdgeType,
301    /// Edge weight/strength (0.0-1.0).
302    pub weight: f64,
303    /// When the edge was created.
304    pub created_at: DateTime<Utc>,
305    /// Optional metadata.
306    pub metadata: Option<serde_json::Value>,
307}
308
309/// Type of graph edge in Graph-of-Thoughts reasoning.
310#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum EdgeType {
313    /// Source node generates target node.
314    #[default]
315    Generates,
316    /// Source node refines/improves target node.
317    Refines,
318    /// Source node aggregates target node.
319    Aggregates,
320    /// Source node supports target node's conclusion.
321    Supports,
322    /// Source node contradicts target node's conclusion.
323    Contradicts,
324}
325
326impl std::fmt::Display for EdgeType {
327    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328        match self {
329            EdgeType::Generates => write!(f, "generates"),
330            EdgeType::Refines => write!(f, "refines"),
331            EdgeType::Aggregates => write!(f, "aggregates"),
332            EdgeType::Supports => write!(f, "supports"),
333            EdgeType::Contradicts => write!(f, "contradicts"),
334        }
335    }
336}
337
338impl std::str::FromStr for EdgeType {
339    type Err = String;
340
341    fn from_str(s: &str) -> Result<Self, Self::Err> {
342        match s.to_lowercase().as_str() {
343            "generates" => Ok(EdgeType::Generates),
344            "refines" => Ok(EdgeType::Refines),
345            "aggregates" => Ok(EdgeType::Aggregates),
346            "supports" => Ok(EdgeType::Supports),
347            "contradicts" => Ok(EdgeType::Contradicts),
348            _ => Err(format!("Unknown edge type: {}", s)),
349        }
350    }
351}
352
353/// State snapshot for backtracking and state restoration.
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct StateSnapshot {
356    /// Unique snapshot identifier.
357    pub id: String,
358    /// Parent session ID.
359    pub session_id: String,
360    /// Type of snapshot (full, incremental, branch).
361    pub snapshot_type: SnapshotType,
362    /// Serialized state data.
363    pub state_data: serde_json::Value,
364    /// Parent snapshot ID for incremental snapshots.
365    pub parent_snapshot_id: Option<String>,
366    /// When the snapshot was created.
367    pub created_at: DateTime<Utc>,
368    /// Optional description of the snapshot state.
369    pub description: Option<String>,
370}
371
372/// Type of state snapshot for backtracking.
373#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(rename_all = "snake_case")]
375pub enum SnapshotType {
376    /// Complete state snapshot.
377    #[default]
378    Full,
379    /// Incremental changes since last snapshot.
380    Incremental,
381    /// Branch-specific snapshot.
382    Branch,
383}
384
385impl std::fmt::Display for SnapshotType {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        match self {
388            SnapshotType::Full => write!(f, "full"),
389            SnapshotType::Incremental => write!(f, "incremental"),
390            SnapshotType::Branch => write!(f, "branch"),
391        }
392    }
393}
394
395impl std::str::FromStr for SnapshotType {
396    type Err = String;
397
398    fn from_str(s: &str) -> Result<Self, Self::Err> {
399        match s.to_lowercase().as_str() {
400            "full" => Ok(SnapshotType::Full),
401            "incremental" => Ok(SnapshotType::Incremental),
402            "branch" => Ok(SnapshotType::Branch),
403            _ => Err(format!("Unknown snapshot type: {}", s)),
404        }
405    }
406}
407
408/// Detection type for bias and fallacy analysis.
409#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
410#[serde(rename_all = "snake_case")]
411pub enum DetectionType {
412    /// Cognitive bias detection.
413    #[default]
414    Bias,
415    /// Logical fallacy detection.
416    Fallacy,
417}
418
419impl std::fmt::Display for DetectionType {
420    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421        match self {
422            DetectionType::Bias => write!(f, "bias"),
423            DetectionType::Fallacy => write!(f, "fallacy"),
424        }
425    }
426}
427
428impl std::str::FromStr for DetectionType {
429    type Err = String;
430
431    fn from_str(s: &str) -> Result<Self, Self::Err> {
432        match s.to_lowercase().as_str() {
433            "bias" => Ok(DetectionType::Bias),
434            "fallacy" => Ok(DetectionType::Fallacy),
435            _ => Err(format!("Unknown detection type: {}", s)),
436        }
437    }
438}
439
440/// Detection result from bias or fallacy analysis.
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct Detection {
443    /// Unique detection identifier.
444    pub id: String,
445    /// Optional parent session ID.
446    pub session_id: Option<String>,
447    /// Optional thought ID being analyzed.
448    pub thought_id: Option<String>,
449    /// Type of detection (bias or fallacy).
450    pub detection_type: DetectionType,
451    /// Name of the detected issue (e.g., "confirmation_bias").
452    pub detected_issue: String,
453    /// Severity level (1-5, where 5 is most severe).
454    pub severity: i32,
455    /// Confidence in the detection (0.0-1.0).
456    pub confidence: f64,
457    /// Explanation of why this was detected.
458    pub explanation: String,
459    /// Optional remediation suggestion.
460    pub remediation: Option<String>,
461    /// When the detection was created.
462    pub created_at: DateTime<Utc>,
463    /// Optional metadata.
464    pub metadata: Option<serde_json::Value>,
465}
466
467impl GraphNode {
468    /// Create a new graph node
469    pub fn new(session_id: impl Into<String>, content: impl Into<String>) -> Self {
470        Self {
471            id: Uuid::new_v4().to_string(),
472            session_id: session_id.into(),
473            content: content.into(),
474            node_type: NodeType::Thought,
475            score: None,
476            depth: 0,
477            is_terminal: false,
478            is_root: false,
479            is_active: true,
480            created_at: Utc::now(),
481            metadata: None,
482        }
483    }
484
485    /// Set node type
486    pub fn with_type(mut self, node_type: NodeType) -> Self {
487        self.node_type = node_type;
488        self
489    }
490
491    /// Set score
492    pub fn with_score(mut self, score: f64) -> Self {
493        self.score = Some(score.clamp(0.0, 1.0));
494        self
495    }
496
497    /// Set depth
498    pub fn with_depth(mut self, depth: i32) -> Self {
499        self.depth = depth;
500        self
501    }
502
503    /// Mark as terminal
504    pub fn as_terminal(mut self) -> Self {
505        self.is_terminal = true;
506        self
507    }
508
509    /// Mark as root
510    pub fn as_root(mut self) -> Self {
511        self.is_root = true;
512        self
513    }
514
515    /// Mark as active
516    pub fn as_active(mut self) -> Self {
517        self.is_active = true;
518        self
519    }
520
521    /// Mark as inactive (pruned)
522    pub fn as_inactive(mut self) -> Self {
523        self.is_active = false;
524        self
525    }
526}
527
528impl GraphEdge {
529    /// Create a new graph edge
530    pub fn new(
531        session_id: impl Into<String>,
532        from_node: impl Into<String>,
533        to_node: impl Into<String>,
534    ) -> Self {
535        Self {
536            id: Uuid::new_v4().to_string(),
537            session_id: session_id.into(),
538            from_node: from_node.into(),
539            to_node: to_node.into(),
540            edge_type: EdgeType::Generates,
541            weight: 1.0,
542            created_at: Utc::now(),
543            metadata: None,
544        }
545    }
546
547    /// Set edge type
548    pub fn with_type(mut self, edge_type: EdgeType) -> Self {
549        self.edge_type = edge_type;
550        self
551    }
552
553    /// Set weight
554    pub fn with_weight(mut self, weight: f64) -> Self {
555        self.weight = weight.clamp(0.0, 1.0);
556        self
557    }
558}
559
560impl StateSnapshot {
561    /// Create a new state snapshot
562    pub fn new(session_id: impl Into<String>, state_data: serde_json::Value) -> Self {
563        Self {
564            id: Uuid::new_v4().to_string(),
565            session_id: session_id.into(),
566            snapshot_type: SnapshotType::Full,
567            state_data,
568            parent_snapshot_id: None,
569            created_at: Utc::now(),
570            description: None,
571        }
572    }
573
574    /// Set snapshot type
575    pub fn with_type(mut self, snapshot_type: SnapshotType) -> Self {
576        self.snapshot_type = snapshot_type;
577        self
578    }
579
580    /// Set parent snapshot
581    pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
582        self.parent_snapshot_id = Some(parent_id.into());
583        self
584    }
585
586    /// Set description
587    pub fn with_description(mut self, description: impl Into<String>) -> Self {
588        self.description = Some(description.into());
589        self
590    }
591}
592
593/// Invocation log entry for debugging and tracing.
594#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct Invocation {
596    /// Unique invocation identifier.
597    pub id: String,
598    /// Optional parent session ID.
599    pub session_id: Option<String>,
600    /// Name of the MCP tool invoked.
601    pub tool_name: String,
602    /// Input parameters as JSON.
603    pub input: serde_json::Value,
604    /// Output result as JSON (if successful).
605    pub output: Option<serde_json::Value>,
606    /// Name of the Langbase pipe called.
607    pub pipe_name: Option<String>,
608    /// Latency in milliseconds.
609    pub latency_ms: Option<i64>,
610    /// Whether the invocation succeeded.
611    pub success: bool,
612    /// Error message (if failed).
613    pub error: Option<String>,
614    /// When the invocation occurred.
615    pub created_at: DateTime<Utc>,
616    /// Whether a fallback was used for this invocation.
617    pub fallback_used: bool,
618    /// Type of fallback if used (parse_error, api_unavailable, local_calculation).
619    pub fallback_type: Option<String>,
620}
621
622// ============================================================================
623// Pipe Usage Metrics Types
624// ============================================================================
625
626/// Summary of pipe usage statistics.
627///
628/// Provides aggregated metrics for a single Langbase pipe including
629/// call counts, success rates, and latency statistics.
630#[derive(Debug, Clone, Serialize, Deserialize)]
631pub struct PipeUsageSummary {
632    /// Name of the Langbase pipe.
633    pub pipe_name: String,
634    /// Total number of invocations.
635    pub total_calls: u64,
636    /// Number of successful calls.
637    pub success_count: u64,
638    /// Number of failed calls.
639    pub failure_count: u64,
640    /// Success rate (0.0-1.0).
641    pub success_rate: f64,
642    /// Average latency in milliseconds.
643    pub avg_latency_ms: f64,
644    /// Minimum latency in milliseconds.
645    pub min_latency_ms: Option<i64>,
646    /// Maximum latency in milliseconds.
647    pub max_latency_ms: Option<i64>,
648    /// First invocation timestamp.
649    pub first_call: DateTime<Utc>,
650    /// Most recent invocation timestamp.
651    pub last_call: DateTime<Utc>,
652}
653
654/// Summary of fallback usage across invocations.
655///
656/// Provides metrics for tracking how often fallbacks are used,
657/// which is critical for measuring actual pipe reliability.
658#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct FallbackMetricsSummary {
660    /// Total number of fallbacks used.
661    pub total_fallbacks: u64,
662    /// Breakdown by fallback type.
663    pub fallbacks_by_type: std::collections::HashMap<String, u64>,
664    /// Breakdown by pipe name.
665    pub fallbacks_by_pipe: std::collections::HashMap<String, u64>,
666    /// Total invocations analyzed.
667    pub total_invocations: u64,
668    /// Fallback rate (0.0-1.0).
669    pub fallback_rate: f64,
670    /// Recommendation based on fallback usage.
671    pub recommendation: String,
672    /// Number of timestamps that were reconstructed due to parse failures.
673    /// This indicates data integrity issues in the database.
674    pub timestamp_reconstructions: u64,
675    /// Number of database records skipped due to JSON or timestamp parse failures.
676    /// This indicates data loss when records fail parsing in query results.
677    pub records_skipped: u64,
678}
679
680/// Filter options for metrics queries.
681///
682/// Allows filtering invocations by various criteria for targeted analysis.
683#[derive(Debug, Clone, Default, Serialize, Deserialize)]
684pub struct MetricsFilter {
685    /// Filter by pipe name (exact match).
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub pipe_name: Option<String>,
688    /// Filter by session ID.
689    #[serde(skip_serializing_if = "Option::is_none")]
690    pub session_id: Option<String>,
691    /// Filter by tool name.
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub tool_name: Option<String>,
694    /// Filter calls after this time.
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub after: Option<DateTime<Utc>>,
697    /// Filter calls before this time.
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub before: Option<DateTime<Utc>>,
700    /// Only include successful (true) or failed (false) calls.
701    #[serde(skip_serializing_if = "Option::is_none")]
702    pub success_only: Option<bool>,
703    /// Limit number of results.
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub limit: Option<u32>,
706}
707
708impl MetricsFilter {
709    /// Create a new empty filter.
710    pub fn new() -> Self {
711        Self::default()
712    }
713
714    /// Filter by pipe name.
715    pub fn with_pipe(mut self, pipe_name: impl Into<String>) -> Self {
716        self.pipe_name = Some(pipe_name.into());
717        self
718    }
719
720    /// Filter by session ID.
721    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
722        self.session_id = Some(session_id.into());
723        self
724    }
725
726    /// Filter by tool name.
727    pub fn with_tool(mut self, tool_name: impl Into<String>) -> Self {
728        self.tool_name = Some(tool_name.into());
729        self
730    }
731
732    /// Filter calls after this time.
733    pub fn after(mut self, time: DateTime<Utc>) -> Self {
734        self.after = Some(time);
735        self
736    }
737
738    /// Filter calls before this time.
739    pub fn before(mut self, time: DateTime<Utc>) -> Self {
740        self.before = Some(time);
741        self
742    }
743
744    /// Only include successful calls.
745    pub fn successful_only(mut self) -> Self {
746        self.success_only = Some(true);
747        self
748    }
749
750    /// Only include failed calls.
751    pub fn failed_only(mut self) -> Self {
752        self.success_only = Some(false);
753        self
754    }
755
756    /// Limit number of results.
757    pub fn with_limit(mut self, limit: u32) -> Self {
758        self.limit = Some(limit);
759        self
760    }
761}
762
763impl Session {
764    /// Create a new session with the given mode
765    pub fn new(mode: impl Into<String>) -> Self {
766        let now = Utc::now();
767        Self {
768            id: Uuid::new_v4().to_string(),
769            mode: mode.into(),
770            created_at: now,
771            updated_at: now,
772            metadata: None,
773            active_branch_id: None,
774        }
775    }
776
777    /// Set the active branch
778    pub fn with_active_branch(mut self, branch_id: impl Into<String>) -> Self {
779        self.active_branch_id = Some(branch_id.into());
780        self
781    }
782}
783
784impl Thought {
785    /// Create a new thought in a session
786    pub fn new(
787        session_id: impl Into<String>,
788        content: impl Into<String>,
789        mode: impl Into<String>,
790    ) -> Self {
791        Self {
792            id: Uuid::new_v4().to_string(),
793            session_id: session_id.into(),
794            content: content.into(),
795            confidence: 0.8,
796            mode: mode.into(),
797            parent_id: None,
798            branch_id: None,
799            created_at: Utc::now(),
800            metadata: None,
801        }
802    }
803
804    /// Set the confidence level
805    pub fn with_confidence(mut self, confidence: f64) -> Self {
806        self.confidence = confidence.clamp(0.0, 1.0);
807        self
808    }
809
810    /// Set the parent thought
811    pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
812        self.parent_id = Some(parent_id.into());
813        self
814    }
815
816    /// Set the branch ID for tree mode
817    pub fn with_branch(mut self, branch_id: impl Into<String>) -> Self {
818        self.branch_id = Some(branch_id.into());
819        self
820    }
821
822    /// Set metadata
823    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
824        self.metadata = Some(metadata);
825        self
826    }
827}
828
829impl Branch {
830    /// Create a new branch in a session
831    pub fn new(session_id: impl Into<String>) -> Self {
832        let now = Utc::now();
833        Self {
834            id: Uuid::new_v4().to_string(),
835            session_id: session_id.into(),
836            name: None,
837            parent_branch_id: None,
838            priority: 1.0,
839            confidence: 0.8,
840            state: BranchState::Active,
841            created_at: now,
842            updated_at: now,
843            metadata: None,
844        }
845    }
846
847    /// Set the branch name
848    pub fn with_name(mut self, name: impl Into<String>) -> Self {
849        self.name = Some(name.into());
850        self
851    }
852
853    /// Set the parent branch
854    pub fn with_parent(mut self, parent_id: impl Into<String>) -> Self {
855        self.parent_branch_id = Some(parent_id.into());
856        self
857    }
858
859    /// Set the priority
860    pub fn with_priority(mut self, priority: f64) -> Self {
861        self.priority = priority;
862        self
863    }
864
865    /// Set the confidence
866    pub fn with_confidence(mut self, confidence: f64) -> Self {
867        self.confidence = confidence.clamp(0.0, 1.0);
868        self
869    }
870
871    /// Set the state
872    pub fn with_state(mut self, state: BranchState) -> Self {
873        self.state = state;
874        self
875    }
876}
877
878impl CrossRef {
879    /// Create a new cross-reference between branches
880    pub fn new(
881        from_branch_id: impl Into<String>,
882        to_branch_id: impl Into<String>,
883        ref_type: CrossRefType,
884    ) -> Self {
885        Self {
886            id: Uuid::new_v4().to_string(),
887            from_branch_id: from_branch_id.into(),
888            to_branch_id: to_branch_id.into(),
889            ref_type,
890            reason: None,
891            strength: 1.0,
892            created_at: Utc::now(),
893        }
894    }
895
896    /// Set the reason for the cross-reference
897    pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
898        self.reason = Some(reason.into());
899        self
900    }
901
902    /// Set the strength of the cross-reference
903    pub fn with_strength(mut self, strength: f64) -> Self {
904        self.strength = strength.clamp(0.0, 1.0);
905        self
906    }
907}
908
909impl Checkpoint {
910    /// Create a new checkpoint
911    pub fn new(
912        session_id: impl Into<String>,
913        name: impl Into<String>,
914        snapshot: serde_json::Value,
915    ) -> Self {
916        Self {
917            id: Uuid::new_v4().to_string(),
918            session_id: session_id.into(),
919            branch_id: None,
920            name: name.into(),
921            description: None,
922            snapshot,
923            created_at: Utc::now(),
924        }
925    }
926
927    /// Set the branch ID
928    pub fn with_branch(mut self, branch_id: impl Into<String>) -> Self {
929        self.branch_id = Some(branch_id.into());
930        self
931    }
932
933    /// Set the description
934    pub fn with_description(mut self, description: impl Into<String>) -> Self {
935        self.description = Some(description.into());
936        self
937    }
938}
939
940impl Invocation {
941    /// Create a new invocation log entry
942    pub fn new(tool_name: impl Into<String>, input: serde_json::Value) -> Self {
943        Self {
944            id: Uuid::new_v4().to_string(),
945            session_id: None,
946            tool_name: tool_name.into(),
947            input,
948            output: None,
949            pipe_name: None,
950            latency_ms: None,
951            success: true,
952            error: None,
953            created_at: Utc::now(),
954            fallback_used: false,
955            fallback_type: None,
956        }
957    }
958
959    /// Set the session ID
960    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
961        self.session_id = Some(session_id.into());
962        self
963    }
964
965    /// Set the pipe name
966    pub fn with_pipe(mut self, pipe_name: impl Into<String>) -> Self {
967        self.pipe_name = Some(pipe_name.into());
968        self
969    }
970
971    /// Mark as successful with output
972    pub fn success(mut self, output: serde_json::Value, latency_ms: i64) -> Self {
973        self.success = true;
974        self.output = Some(output);
975        self.latency_ms = Some(latency_ms);
976        self
977    }
978
979    /// Mark as failed with error
980    pub fn failure(mut self, error: impl Into<String>, latency_ms: i64) -> Self {
981        self.success = false;
982        self.error = Some(error.into());
983        self.latency_ms = Some(latency_ms);
984        self
985    }
986
987    /// Set latency separately
988    pub fn with_latency(mut self, latency_ms: i64) -> Self {
989        self.latency_ms = Some(latency_ms);
990        self
991    }
992
993    /// Mark as successful (simple version without output)
994    pub fn mark_success(mut self) -> Self {
995        self.success = true;
996        self
997    }
998
999    /// Mark as failed (simple version)
1000    pub fn mark_failed(mut self, error: impl Into<String>) -> Self {
1001        self.success = false;
1002        self.error = Some(error.into());
1003        self
1004    }
1005
1006    /// Mark that a fallback was used
1007    pub fn with_fallback(mut self, fallback_type: impl Into<String>) -> Self {
1008        self.fallback_used = true;
1009        self.fallback_type = Some(fallback_type.into());
1010        self
1011    }
1012
1013    /// Mark fallback with specific type constants
1014    pub fn with_parse_error_fallback(self) -> Self {
1015        self.with_fallback("parse_error")
1016    }
1017
1018    /// Mark fallback due to API unavailability
1019    pub fn with_api_unavailable_fallback(self) -> Self {
1020        self.with_fallback("api_unavailable")
1021    }
1022
1023    /// Mark fallback due to local calculation
1024    pub fn with_local_calculation_fallback(self) -> Self {
1025        self.with_fallback("local_calculation")
1026    }
1027}
1028
1029impl Detection {
1030    /// Create a new detection result
1031    pub fn new(
1032        detection_type: DetectionType,
1033        detected_issue: impl Into<String>,
1034        severity: i32,
1035        confidence: f64,
1036        explanation: impl Into<String>,
1037    ) -> Self {
1038        Self {
1039            id: Uuid::new_v4().to_string(),
1040            session_id: None,
1041            thought_id: None,
1042            detection_type,
1043            detected_issue: detected_issue.into(),
1044            severity: severity.clamp(1, 5),
1045            confidence: confidence.clamp(0.0, 1.0),
1046            explanation: explanation.into(),
1047            remediation: None,
1048            created_at: Utc::now(),
1049            metadata: None,
1050        }
1051    }
1052
1053    /// Set the session ID
1054    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
1055        self.session_id = Some(session_id.into());
1056        self
1057    }
1058
1059    /// Set the thought ID
1060    pub fn with_thought(mut self, thought_id: impl Into<String>) -> Self {
1061        self.thought_id = Some(thought_id.into());
1062        self
1063    }
1064
1065    /// Set the remediation suggestion
1066    pub fn with_remediation(mut self, remediation: impl Into<String>) -> Self {
1067        self.remediation = Some(remediation.into());
1068        self
1069    }
1070
1071    /// Set metadata
1072    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1073        self.metadata = Some(metadata);
1074        self
1075    }
1076}
1077
1078// ============================================================================
1079// Decision Framework Storage Types
1080// ============================================================================
1081
1082/// Stored decision analysis result.
1083#[derive(Debug, Clone, Serialize, Deserialize)]
1084pub struct Decision {
1085    /// Unique decision identifier.
1086    pub id: String,
1087    /// Parent session ID.
1088    pub session_id: String,
1089    /// The decision question.
1090    pub question: String,
1091    /// Available options (JSON array).
1092    pub options: Vec<String>,
1093    /// Evaluation criteria with weights (JSON array).
1094    pub criteria: Option<Vec<StoredCriterion>>,
1095    /// Decision method ('weighted_sum', 'pairwise', 'topsis').
1096    pub method: String,
1097    /// Recommendation (JSON object).
1098    pub recommendation: serde_json::Value,
1099    /// Option scores (JSON array).
1100    pub scores: serde_json::Value,
1101    /// Sensitivity analysis results.
1102    pub sensitivity_analysis: Option<serde_json::Value>,
1103    /// Trade-offs between options.
1104    pub trade_offs: Option<serde_json::Value>,
1105    /// Constraint satisfaction per option.
1106    pub constraints_satisfied: Option<serde_json::Value>,
1107    /// When the decision was created.
1108    pub created_at: DateTime<Utc>,
1109    /// Optional metadata.
1110    pub metadata: Option<serde_json::Value>,
1111}
1112
1113/// Stored criterion for decision analysis.
1114#[derive(Debug, Clone, Serialize, Deserialize)]
1115pub struct StoredCriterion {
1116    /// Criterion name.
1117    pub name: String,
1118    /// Weight (0.0-1.0).
1119    pub weight: f64,
1120    /// Optional description.
1121    pub description: Option<String>,
1122}
1123
1124impl Decision {
1125    /// Create a new decision.
1126    pub fn new(
1127        session_id: impl Into<String>,
1128        question: impl Into<String>,
1129        options: Vec<String>,
1130        method: impl Into<String>,
1131        recommendation: serde_json::Value,
1132        scores: serde_json::Value,
1133    ) -> Self {
1134        Self {
1135            id: Uuid::new_v4().to_string(),
1136            session_id: session_id.into(),
1137            question: question.into(),
1138            options,
1139            criteria: None,
1140            method: method.into(),
1141            recommendation,
1142            scores,
1143            sensitivity_analysis: None,
1144            trade_offs: None,
1145            constraints_satisfied: None,
1146            created_at: Utc::now(),
1147            metadata: None,
1148        }
1149    }
1150
1151    /// Set criteria.
1152    pub fn with_criteria(mut self, criteria: Vec<StoredCriterion>) -> Self {
1153        self.criteria = Some(criteria);
1154        self
1155    }
1156
1157    /// Set sensitivity analysis.
1158    pub fn with_sensitivity(mut self, analysis: serde_json::Value) -> Self {
1159        self.sensitivity_analysis = Some(analysis);
1160        self
1161    }
1162
1163    /// Set trade-offs.
1164    pub fn with_trade_offs(mut self, trade_offs: serde_json::Value) -> Self {
1165        self.trade_offs = Some(trade_offs);
1166        self
1167    }
1168
1169    /// Set constraints satisfied.
1170    pub fn with_constraints(mut self, satisfied: serde_json::Value) -> Self {
1171        self.constraints_satisfied = Some(satisfied);
1172        self
1173    }
1174
1175    /// Set metadata.
1176    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1177        self.metadata = Some(metadata);
1178        self
1179    }
1180}
1181
1182/// Stored perspective analysis result.
1183#[derive(Debug, Clone, Serialize, Deserialize)]
1184pub struct PerspectiveAnalysis {
1185    /// Unique analysis identifier.
1186    pub id: String,
1187    /// Parent session ID.
1188    pub session_id: String,
1189    /// The topic analyzed.
1190    pub topic: String,
1191    /// Stakeholder analyses (JSON array).
1192    pub stakeholders: serde_json::Value,
1193    /// Power/interest matrix (JSON object).
1194    pub power_matrix: Option<serde_json::Value>,
1195    /// Identified conflicts (JSON array).
1196    pub conflicts: Option<serde_json::Value>,
1197    /// Identified alignments (JSON array).
1198    pub alignments: Option<serde_json::Value>,
1199    /// Synthesis of perspectives (JSON object).
1200    pub synthesis: serde_json::Value,
1201    /// Overall confidence (0.0-1.0).
1202    pub confidence: f64,
1203    /// When the analysis was created.
1204    pub created_at: DateTime<Utc>,
1205    /// Optional metadata.
1206    pub metadata: Option<serde_json::Value>,
1207}
1208
1209impl PerspectiveAnalysis {
1210    /// Create a new perspective analysis.
1211    pub fn new(
1212        session_id: impl Into<String>,
1213        topic: impl Into<String>,
1214        stakeholders: serde_json::Value,
1215        synthesis: serde_json::Value,
1216        confidence: f64,
1217    ) -> Self {
1218        Self {
1219            id: Uuid::new_v4().to_string(),
1220            session_id: session_id.into(),
1221            topic: topic.into(),
1222            stakeholders,
1223            power_matrix: None,
1224            conflicts: None,
1225            alignments: None,
1226            synthesis,
1227            confidence: confidence.clamp(0.0, 1.0),
1228            created_at: Utc::now(),
1229            metadata: None,
1230        }
1231    }
1232
1233    /// Set power matrix.
1234    pub fn with_power_matrix(mut self, matrix: serde_json::Value) -> Self {
1235        self.power_matrix = Some(matrix);
1236        self
1237    }
1238
1239    /// Set conflicts.
1240    pub fn with_conflicts(mut self, conflicts: serde_json::Value) -> Self {
1241        self.conflicts = Some(conflicts);
1242        self
1243    }
1244
1245    /// Set alignments.
1246    pub fn with_alignments(mut self, alignments: serde_json::Value) -> Self {
1247        self.alignments = Some(alignments);
1248        self
1249    }
1250
1251    /// Set metadata.
1252    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1253        self.metadata = Some(metadata);
1254        self
1255    }
1256}
1257
1258// ============================================================================
1259// Evidence Assessment Storage Types
1260// ============================================================================
1261
1262/// Stored evidence assessment result.
1263#[derive(Debug, Clone, Serialize, Deserialize)]
1264pub struct EvidenceAssessment {
1265    /// Unique assessment identifier.
1266    pub id: String,
1267    /// Parent session ID.
1268    pub session_id: String,
1269    /// The claim being assessed.
1270    pub claim: String,
1271    /// Evidence items (JSON array).
1272    pub evidence: serde_json::Value,
1273    /// Overall support level (JSON object).
1274    pub overall_support: serde_json::Value,
1275    /// Individual evidence analyses (JSON array).
1276    pub evidence_analysis: serde_json::Value,
1277    /// Chain of reasoning analysis (JSON object).
1278    pub chain_analysis: Option<serde_json::Value>,
1279    /// Detected contradictions (JSON array).
1280    pub contradictions: Option<serde_json::Value>,
1281    /// Identified gaps (JSON array).
1282    pub gaps: Option<serde_json::Value>,
1283    /// Recommendations (JSON array).
1284    pub recommendations: Option<serde_json::Value>,
1285    /// When the assessment was created.
1286    pub created_at: DateTime<Utc>,
1287    /// Optional metadata.
1288    pub metadata: Option<serde_json::Value>,
1289}
1290
1291impl EvidenceAssessment {
1292    /// Create a new evidence assessment.
1293    pub fn new(
1294        session_id: impl Into<String>,
1295        claim: impl Into<String>,
1296        evidence: serde_json::Value,
1297        overall_support: serde_json::Value,
1298        evidence_analysis: serde_json::Value,
1299    ) -> Self {
1300        Self {
1301            id: Uuid::new_v4().to_string(),
1302            session_id: session_id.into(),
1303            claim: claim.into(),
1304            evidence,
1305            overall_support,
1306            evidence_analysis,
1307            chain_analysis: None,
1308            contradictions: None,
1309            gaps: None,
1310            recommendations: None,
1311            created_at: Utc::now(),
1312            metadata: None,
1313        }
1314    }
1315
1316    /// Set chain analysis.
1317    pub fn with_chain_analysis(mut self, analysis: serde_json::Value) -> Self {
1318        self.chain_analysis = Some(analysis);
1319        self
1320    }
1321
1322    /// Set contradictions.
1323    pub fn with_contradictions(mut self, contradictions: serde_json::Value) -> Self {
1324        self.contradictions = Some(contradictions);
1325        self
1326    }
1327
1328    /// Set gaps.
1329    pub fn with_gaps(mut self, gaps: serde_json::Value) -> Self {
1330        self.gaps = Some(gaps);
1331        self
1332    }
1333
1334    /// Set recommendations.
1335    pub fn with_recommendations(mut self, recommendations: serde_json::Value) -> Self {
1336        self.recommendations = Some(recommendations);
1337        self
1338    }
1339
1340    /// Set metadata.
1341    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1342        self.metadata = Some(metadata);
1343        self
1344    }
1345}
1346
1347/// Stored probability update result.
1348#[derive(Debug, Clone, Serialize, Deserialize)]
1349pub struct ProbabilityUpdate {
1350    /// Unique update identifier.
1351    pub id: String,
1352    /// Parent session ID.
1353    pub session_id: String,
1354    /// The hypothesis evaluated.
1355    pub hypothesis: String,
1356    /// Prior probability (0-1).
1357    pub prior: f64,
1358    /// Posterior probability (0-1).
1359    pub posterior: f64,
1360    /// Lower bound of confidence interval.
1361    pub confidence_lower: Option<f64>,
1362    /// Upper bound of confidence interval.
1363    pub confidence_upper: Option<f64>,
1364    /// Confidence interval level (e.g., 0.95).
1365    pub confidence_level: Option<f64>,
1366    /// Bayesian update steps (JSON array).
1367    pub update_steps: serde_json::Value,
1368    /// Uncertainty analysis (JSON object).
1369    pub uncertainty_analysis: Option<serde_json::Value>,
1370    /// Sensitivity analysis (JSON object).
1371    pub sensitivity: Option<serde_json::Value>,
1372    /// Human interpretation (JSON object).
1373    pub interpretation: serde_json::Value,
1374    /// When the update was created.
1375    pub created_at: DateTime<Utc>,
1376    /// Optional metadata.
1377    pub metadata: Option<serde_json::Value>,
1378}
1379
1380impl ProbabilityUpdate {
1381    /// Create a new probability update.
1382    pub fn new(
1383        session_id: impl Into<String>,
1384        hypothesis: impl Into<String>,
1385        prior: f64,
1386        posterior: f64,
1387        update_steps: serde_json::Value,
1388        interpretation: serde_json::Value,
1389    ) -> Self {
1390        Self {
1391            id: Uuid::new_v4().to_string(),
1392            session_id: session_id.into(),
1393            hypothesis: hypothesis.into(),
1394            prior: prior.clamp(0.0, 1.0),
1395            posterior: posterior.clamp(0.0, 1.0),
1396            confidence_lower: None,
1397            confidence_upper: None,
1398            confidence_level: None,
1399            update_steps,
1400            uncertainty_analysis: None,
1401            sensitivity: None,
1402            interpretation,
1403            created_at: Utc::now(),
1404            metadata: None,
1405        }
1406    }
1407
1408    /// Set confidence interval.
1409    pub fn with_confidence_interval(
1410        mut self,
1411        lower: Option<f64>,
1412        upper: Option<f64>,
1413        level: Option<f64>,
1414    ) -> Self {
1415        self.confidence_lower = lower.map(|v| v.clamp(0.0, 1.0));
1416        self.confidence_upper = upper.map(|v| v.clamp(0.0, 1.0));
1417        self.confidence_level = level.map(|v| v.clamp(0.0, 1.0));
1418        self
1419    }
1420
1421    /// Set uncertainty analysis.
1422    pub fn with_uncertainty(mut self, analysis: serde_json::Value) -> Self {
1423        self.uncertainty_analysis = Some(analysis);
1424        self
1425    }
1426
1427    /// Set sensitivity analysis.
1428    pub fn with_sensitivity(mut self, sensitivity: serde_json::Value) -> Self {
1429        self.sensitivity = Some(sensitivity);
1430        self
1431    }
1432
1433    /// Set metadata.
1434    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1435        self.metadata = Some(metadata);
1436        self
1437    }
1438}
1439
1440/// Storage trait for database operations.
1441///
1442/// This trait defines all persistence operations for reasoning sessions,
1443/// thoughts, branches, checkpoints, graph nodes, and other artifacts.
1444#[async_trait]
1445pub trait Storage: Send + Sync {
1446    // Session operations
1447
1448    /// Create a new session.
1449    async fn create_session(&self, session: &Session) -> StorageResult<()>;
1450    /// Get a session by ID.
1451    async fn get_session(&self, id: &str) -> StorageResult<Option<Session>>;
1452    /// Update an existing session.
1453    async fn update_session(&self, session: &Session) -> StorageResult<()>;
1454    /// Delete a session by ID.
1455    async fn delete_session(&self, id: &str) -> StorageResult<()>;
1456
1457    /// Get an existing session or create a new one.
1458    ///
1459    /// If `session_id` is `Some`, looks up the session:
1460    /// - If found, returns it
1461    /// - If not found, creates a new session with that ID
1462    ///
1463    /// If `session_id` is `None`, creates a new session with a generated ID.
1464    ///
1465    /// This is a provided method with a default implementation that uses
1466    /// `get_session` and `create_session`.
1467    async fn get_or_create_session(
1468        &self,
1469        session_id: &Option<String>,
1470        mode: &str,
1471    ) -> StorageResult<Session>
1472    where
1473        Self: Sized,
1474    {
1475        match session_id {
1476            Some(id) => match self.get_session(id).await? {
1477                Some(session) => Ok(session),
1478                None => {
1479                    let mut new_session = Session::new(mode);
1480                    new_session.id = id.clone();
1481                    self.create_session(&new_session).await?;
1482                    Ok(new_session)
1483                }
1484            },
1485            None => {
1486                let session = Session::new(mode);
1487                self.create_session(&session).await?;
1488                Ok(session)
1489            }
1490        }
1491    }
1492
1493    // Thought operations
1494
1495    /// Create a new thought.
1496    async fn create_thought(&self, thought: &Thought) -> StorageResult<()>;
1497    /// Get a thought by ID.
1498    async fn get_thought(&self, id: &str) -> StorageResult<Option<Thought>>;
1499    /// Get all thoughts in a session.
1500    async fn get_session_thoughts(&self, session_id: &str) -> StorageResult<Vec<Thought>>;
1501    /// Get all thoughts in a branch.
1502    async fn get_branch_thoughts(&self, branch_id: &str) -> StorageResult<Vec<Thought>>;
1503    /// Get the most recent thought in a session.
1504    async fn get_latest_thought(&self, session_id: &str) -> StorageResult<Option<Thought>>;
1505
1506    // Branch operations (tree mode)
1507
1508    /// Create a new branch.
1509    async fn create_branch(&self, branch: &Branch) -> StorageResult<()>;
1510    /// Get a branch by ID.
1511    async fn get_branch(&self, id: &str) -> StorageResult<Option<Branch>>;
1512    /// Get all branches in a session.
1513    async fn get_session_branches(&self, session_id: &str) -> StorageResult<Vec<Branch>>;
1514    /// Get child branches of a parent branch.
1515    async fn get_child_branches(&self, parent_id: &str) -> StorageResult<Vec<Branch>>;
1516    /// Update an existing branch.
1517    async fn update_branch(&self, branch: &Branch) -> StorageResult<()>;
1518    /// Delete a branch by ID.
1519    async fn delete_branch(&self, id: &str) -> StorageResult<()>;
1520
1521    // Cross-reference operations (tree mode)
1522
1523    /// Create a new cross-reference between branches.
1524    async fn create_cross_ref(&self, cross_ref: &CrossRef) -> StorageResult<()>;
1525    /// Get cross-references originating from a branch.
1526    async fn get_cross_refs_from(&self, branch_id: &str) -> StorageResult<Vec<CrossRef>>;
1527    /// Get cross-references pointing to a branch.
1528    async fn get_cross_refs_to(&self, branch_id: &str) -> StorageResult<Vec<CrossRef>>;
1529    /// Delete a cross-reference by ID.
1530    async fn delete_cross_ref(&self, id: &str) -> StorageResult<()>;
1531
1532    // Checkpoint operations (backtracking)
1533
1534    /// Create a new checkpoint.
1535    async fn create_checkpoint(&self, checkpoint: &Checkpoint) -> StorageResult<()>;
1536    /// Get a checkpoint by ID.
1537    async fn get_checkpoint(&self, id: &str) -> StorageResult<Option<Checkpoint>>;
1538    /// Get all checkpoints in a session.
1539    async fn get_session_checkpoints(&self, session_id: &str) -> StorageResult<Vec<Checkpoint>>;
1540    /// Get all checkpoints for a branch.
1541    async fn get_branch_checkpoints(&self, branch_id: &str) -> StorageResult<Vec<Checkpoint>>;
1542    /// Delete a checkpoint by ID.
1543    async fn delete_checkpoint(&self, id: &str) -> StorageResult<()>;
1544
1545    // Invocation logging and metrics
1546
1547    /// Log a tool invocation for debugging.
1548    async fn log_invocation(&self, invocation: &Invocation) -> StorageResult<()>;
1549
1550    /// Get aggregated usage summary for all pipes.
1551    ///
1552    /// Returns metrics for each pipe that has been invoked, ordered by total calls descending.
1553    async fn get_pipe_usage_summary(&self) -> StorageResult<Vec<PipeUsageSummary>>;
1554
1555    /// Get usage summary for a specific pipe.
1556    ///
1557    /// Returns None if the pipe has never been invoked.
1558    async fn get_pipe_summary(&self, pipe_name: &str) -> StorageResult<Option<PipeUsageSummary>>;
1559
1560    /// Get invocations with optional filtering.
1561    ///
1562    /// Supports filtering by pipe name, session, tool, time range, and success status.
1563    /// Results are ordered by created_at descending (most recent first).
1564    async fn get_invocations(&self, filter: MetricsFilter) -> StorageResult<Vec<Invocation>>;
1565
1566    /// Get total invocation count.
1567    ///
1568    /// Optionally filter by pipe name.
1569    async fn get_invocation_count(&self, pipe_name: Option<&str>) -> StorageResult<u64>;
1570
1571    /// Get fallback usage metrics.
1572    ///
1573    /// Returns aggregated statistics about fallback usage across all invocations,
1574    /// including breakdown by type and pipe.
1575    async fn get_fallback_metrics(&self) -> StorageResult<FallbackMetricsSummary>;
1576
1577    // Graph node operations (GoT mode)
1578
1579    /// Create a new graph node.
1580    async fn create_graph_node(&self, node: &GraphNode) -> StorageResult<()>;
1581    /// Get a graph node by ID.
1582    async fn get_graph_node(&self, id: &str) -> StorageResult<Option<GraphNode>>;
1583    /// Get all graph nodes in a session.
1584    async fn get_session_graph_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1585    /// Get active (non-pruned) graph nodes in a session.
1586    async fn get_active_graph_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1587    /// Get root nodes in a session.
1588    async fn get_root_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1589    /// Get terminal nodes in a session.
1590    async fn get_terminal_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1591    /// Update an existing graph node.
1592    async fn update_graph_node(&self, node: &GraphNode) -> StorageResult<()>;
1593    /// Delete a graph node by ID.
1594    async fn delete_graph_node(&self, id: &str) -> StorageResult<()>;
1595
1596    // Graph edge operations (GoT mode)
1597
1598    /// Create a new graph edge.
1599    async fn create_graph_edge(&self, edge: &GraphEdge) -> StorageResult<()>;
1600    /// Get a graph edge by ID.
1601    async fn get_graph_edge(&self, id: &str) -> StorageResult<Option<GraphEdge>>;
1602    /// Get edges originating from a node.
1603    async fn get_edges_from(&self, node_id: &str) -> StorageResult<Vec<GraphEdge>>;
1604    /// Get edges pointing to a node.
1605    async fn get_edges_to(&self, node_id: &str) -> StorageResult<Vec<GraphEdge>>;
1606    /// Get all edges in a session.
1607    async fn get_session_edges(&self, session_id: &str) -> StorageResult<Vec<GraphEdge>>;
1608    /// Delete a graph edge by ID.
1609    async fn delete_graph_edge(&self, id: &str) -> StorageResult<()>;
1610
1611    // State snapshot operations (backtracking)
1612
1613    /// Create a new state snapshot.
1614    async fn create_snapshot(&self, snapshot: &StateSnapshot) -> StorageResult<()>;
1615    /// Get a state snapshot by ID.
1616    async fn get_snapshot(&self, id: &str) -> StorageResult<Option<StateSnapshot>>;
1617    /// Get all snapshots in a session.
1618    async fn get_session_snapshots(&self, session_id: &str) -> StorageResult<Vec<StateSnapshot>>;
1619    /// Get the most recent snapshot in a session.
1620    async fn get_latest_snapshot(&self, session_id: &str) -> StorageResult<Option<StateSnapshot>>;
1621    /// Delete a state snapshot by ID.
1622    async fn delete_snapshot(&self, id: &str) -> StorageResult<()>;
1623
1624    // Detection operations (bias/fallacy analysis)
1625
1626    /// Create a new detection result.
1627    async fn create_detection(&self, detection: &Detection) -> StorageResult<()>;
1628    /// Get a detection by ID.
1629    async fn get_detection(&self, id: &str) -> StorageResult<Option<Detection>>;
1630    /// Get all detections in a session.
1631    async fn get_session_detections(&self, session_id: &str) -> StorageResult<Vec<Detection>>;
1632    /// Get all detections for a thought.
1633    async fn get_thought_detections(&self, thought_id: &str) -> StorageResult<Vec<Detection>>;
1634    /// Get all detections of a specific type.
1635    async fn get_detections_by_type(
1636        &self,
1637        detection_type: DetectionType,
1638    ) -> StorageResult<Vec<Detection>>;
1639    /// Get detections of a specific type in a session.
1640    async fn get_session_detections_by_type(
1641        &self,
1642        session_id: &str,
1643        detection_type: DetectionType,
1644    ) -> StorageResult<Vec<Detection>>;
1645    /// Delete a detection by ID.
1646    async fn delete_detection(&self, id: &str) -> StorageResult<()>;
1647
1648    // ========================================================================
1649    // Decision operations (decision framework)
1650    // ========================================================================
1651
1652    /// Create a new decision analysis.
1653    async fn create_decision(&self, decision: &Decision) -> StorageResult<()>;
1654
1655    /// Get a decision by ID.
1656    async fn get_decision(&self, id: &str) -> StorageResult<Option<Decision>>;
1657
1658    /// Get all decisions in a session.
1659    async fn get_session_decisions(&self, session_id: &str) -> StorageResult<Vec<Decision>>;
1660
1661    /// Get decisions by method type.
1662    async fn get_decisions_by_method(&self, method: &str) -> StorageResult<Vec<Decision>>;
1663
1664    /// Delete a decision by ID.
1665    async fn delete_decision(&self, id: &str) -> StorageResult<()>;
1666
1667    // ========================================================================
1668    // Perspective analysis operations (decision framework)
1669    // ========================================================================
1670
1671    /// Create a new perspective analysis.
1672    async fn create_perspective(&self, analysis: &PerspectiveAnalysis) -> StorageResult<()>;
1673
1674    /// Get a perspective analysis by ID.
1675    async fn get_perspective(&self, id: &str) -> StorageResult<Option<PerspectiveAnalysis>>;
1676
1677    /// Get all perspective analyses in a session.
1678    async fn get_session_perspectives(
1679        &self,
1680        session_id: &str,
1681    ) -> StorageResult<Vec<PerspectiveAnalysis>>;
1682
1683    /// Delete a perspective analysis by ID.
1684    async fn delete_perspective(&self, id: &str) -> StorageResult<()>;
1685
1686    // ========================================================================
1687    // Evidence assessment operations (evidence mode)
1688    // ========================================================================
1689
1690    /// Create a new evidence assessment.
1691    async fn create_evidence_assessment(
1692        &self,
1693        assessment: &EvidenceAssessment,
1694    ) -> StorageResult<()>;
1695
1696    /// Get an evidence assessment by ID.
1697    async fn get_evidence_assessment(&self, id: &str) -> StorageResult<Option<EvidenceAssessment>>;
1698
1699    /// Get all evidence assessments in a session.
1700    async fn get_session_evidence_assessments(
1701        &self,
1702        session_id: &str,
1703    ) -> StorageResult<Vec<EvidenceAssessment>>;
1704
1705    /// Delete an evidence assessment by ID.
1706    async fn delete_evidence_assessment(&self, id: &str) -> StorageResult<()>;
1707
1708    // ========================================================================
1709    // Probability update operations (evidence mode)
1710    // ========================================================================
1711
1712    /// Create a new probability update.
1713    async fn create_probability_update(&self, update: &ProbabilityUpdate) -> StorageResult<()>;
1714
1715    /// Get a probability update by ID.
1716    async fn get_probability_update(&self, id: &str) -> StorageResult<Option<ProbabilityUpdate>>;
1717
1718    /// Get all probability updates in a session.
1719    async fn get_session_probability_updates(
1720        &self,
1721        session_id: &str,
1722    ) -> StorageResult<Vec<ProbabilityUpdate>>;
1723
1724    /// Get probability updates for a hypothesis in a session.
1725    async fn get_hypothesis_updates(
1726        &self,
1727        session_id: &str,
1728        hypothesis: &str,
1729    ) -> StorageResult<Vec<ProbabilityUpdate>>;
1730
1731    /// Delete a probability update by ID.
1732    async fn delete_probability_update(&self, id: &str) -> StorageResult<()>;
1733}
1734
1735#[cfg(test)]
1736mod tests {
1737    use super::*;
1738    use std::str::FromStr;
1739
1740    // ========================================================================
1741    // BranchState tests
1742    // ========================================================================
1743
1744    #[test]
1745    fn test_branch_state_display() {
1746        assert_eq!(BranchState::Active.to_string(), "active");
1747        assert_eq!(BranchState::Completed.to_string(), "completed");
1748        assert_eq!(BranchState::Abandoned.to_string(), "abandoned");
1749    }
1750
1751    #[test]
1752    fn test_branch_state_from_str() {
1753        assert_eq!(
1754            BranchState::from_str("active").unwrap(),
1755            BranchState::Active
1756        );
1757        assert_eq!(
1758            BranchState::from_str("completed").unwrap(),
1759            BranchState::Completed
1760        );
1761        assert_eq!(
1762            BranchState::from_str("abandoned").unwrap(),
1763            BranchState::Abandoned
1764        );
1765    }
1766
1767    #[test]
1768    fn test_branch_state_from_str_case_insensitive() {
1769        assert_eq!(
1770            BranchState::from_str("ACTIVE").unwrap(),
1771            BranchState::Active
1772        );
1773        assert_eq!(
1774            BranchState::from_str("Completed").unwrap(),
1775            BranchState::Completed
1776        );
1777        assert_eq!(
1778            BranchState::from_str("ABANDONED").unwrap(),
1779            BranchState::Abandoned
1780        );
1781    }
1782
1783    #[test]
1784    fn test_branch_state_from_str_invalid() {
1785        assert!(BranchState::from_str("invalid").is_err());
1786        assert!(BranchState::from_str("").is_err());
1787        assert_eq!(
1788            BranchState::from_str("unknown").unwrap_err(),
1789            "Unknown branch state: unknown"
1790        );
1791    }
1792
1793    #[test]
1794    fn test_branch_state_default() {
1795        assert_eq!(BranchState::default(), BranchState::Active);
1796    }
1797
1798    #[test]
1799    fn test_branch_state_round_trip() {
1800        for state in [
1801            BranchState::Active,
1802            BranchState::Completed,
1803            BranchState::Abandoned,
1804        ] {
1805            let str_val = state.to_string();
1806            let parsed = BranchState::from_str(&str_val).unwrap();
1807            assert_eq!(parsed, state);
1808        }
1809    }
1810
1811    // ========================================================================
1812    // CrossRefType tests
1813    // ========================================================================
1814
1815    #[test]
1816    fn test_cross_ref_type_display() {
1817        assert_eq!(CrossRefType::Supports.to_string(), "supports");
1818        assert_eq!(CrossRefType::Contradicts.to_string(), "contradicts");
1819        assert_eq!(CrossRefType::Extends.to_string(), "extends");
1820        assert_eq!(CrossRefType::Alternative.to_string(), "alternative");
1821        assert_eq!(CrossRefType::Depends.to_string(), "depends");
1822    }
1823
1824    #[test]
1825    fn test_cross_ref_type_from_str() {
1826        assert_eq!(
1827            CrossRefType::from_str("supports").unwrap(),
1828            CrossRefType::Supports
1829        );
1830        assert_eq!(
1831            CrossRefType::from_str("contradicts").unwrap(),
1832            CrossRefType::Contradicts
1833        );
1834        assert_eq!(
1835            CrossRefType::from_str("extends").unwrap(),
1836            CrossRefType::Extends
1837        );
1838        assert_eq!(
1839            CrossRefType::from_str("alternative").unwrap(),
1840            CrossRefType::Alternative
1841        );
1842        assert_eq!(
1843            CrossRefType::from_str("depends").unwrap(),
1844            CrossRefType::Depends
1845        );
1846    }
1847
1848    #[test]
1849    fn test_cross_ref_type_from_str_case_insensitive() {
1850        assert_eq!(
1851            CrossRefType::from_str("SUPPORTS").unwrap(),
1852            CrossRefType::Supports
1853        );
1854        assert_eq!(
1855            CrossRefType::from_str("Contradicts").unwrap(),
1856            CrossRefType::Contradicts
1857        );
1858        assert_eq!(
1859            CrossRefType::from_str("EXTENDS").unwrap(),
1860            CrossRefType::Extends
1861        );
1862    }
1863
1864    #[test]
1865    fn test_cross_ref_type_from_str_invalid() {
1866        assert!(CrossRefType::from_str("invalid").is_err());
1867        assert!(CrossRefType::from_str("").is_err());
1868        assert_eq!(
1869            CrossRefType::from_str("unknown").unwrap_err(),
1870            "Unknown cross-ref type: unknown"
1871        );
1872    }
1873
1874    #[test]
1875    fn test_cross_ref_type_default() {
1876        assert_eq!(CrossRefType::default(), CrossRefType::Supports);
1877    }
1878
1879    #[test]
1880    fn test_cross_ref_type_round_trip() {
1881        for ref_type in [
1882            CrossRefType::Supports,
1883            CrossRefType::Contradicts,
1884            CrossRefType::Extends,
1885            CrossRefType::Alternative,
1886            CrossRefType::Depends,
1887        ] {
1888            let str_val = ref_type.to_string();
1889            let parsed = CrossRefType::from_str(&str_val).unwrap();
1890            assert_eq!(parsed, ref_type);
1891        }
1892    }
1893
1894    // ========================================================================
1895    // NodeType tests
1896    // ========================================================================
1897
1898    #[test]
1899    fn test_node_type_display() {
1900        assert_eq!(NodeType::Thought.to_string(), "thought");
1901        assert_eq!(NodeType::Hypothesis.to_string(), "hypothesis");
1902        assert_eq!(NodeType::Conclusion.to_string(), "conclusion");
1903        assert_eq!(NodeType::Aggregation.to_string(), "aggregation");
1904        assert_eq!(NodeType::Root.to_string(), "root");
1905        assert_eq!(NodeType::Refinement.to_string(), "refinement");
1906        assert_eq!(NodeType::Terminal.to_string(), "terminal");
1907    }
1908
1909    #[test]
1910    fn test_node_type_from_str() {
1911        assert_eq!(NodeType::from_str("thought").unwrap(), NodeType::Thought);
1912        assert_eq!(
1913            NodeType::from_str("hypothesis").unwrap(),
1914            NodeType::Hypothesis
1915        );
1916        assert_eq!(
1917            NodeType::from_str("conclusion").unwrap(),
1918            NodeType::Conclusion
1919        );
1920        assert_eq!(
1921            NodeType::from_str("aggregation").unwrap(),
1922            NodeType::Aggregation
1923        );
1924        assert_eq!(NodeType::from_str("root").unwrap(), NodeType::Root);
1925        assert_eq!(
1926            NodeType::from_str("refinement").unwrap(),
1927            NodeType::Refinement
1928        );
1929        assert_eq!(NodeType::from_str("terminal").unwrap(), NodeType::Terminal);
1930    }
1931
1932    #[test]
1933    fn test_node_type_from_str_case_insensitive() {
1934        assert_eq!(NodeType::from_str("THOUGHT").unwrap(), NodeType::Thought);
1935        assert_eq!(
1936            NodeType::from_str("Hypothesis").unwrap(),
1937            NodeType::Hypothesis
1938        );
1939        assert_eq!(
1940            NodeType::from_str("CONCLUSION").unwrap(),
1941            NodeType::Conclusion
1942        );
1943    }
1944
1945    #[test]
1946    fn test_node_type_from_str_invalid() {
1947        assert!(NodeType::from_str("invalid").is_err());
1948        assert!(NodeType::from_str("").is_err());
1949        assert_eq!(
1950            NodeType::from_str("unknown").unwrap_err(),
1951            "Unknown node type: unknown"
1952        );
1953    }
1954
1955    #[test]
1956    fn test_node_type_default() {
1957        assert_eq!(NodeType::default(), NodeType::Thought);
1958    }
1959
1960    #[test]
1961    fn test_node_type_round_trip() {
1962        for node_type in [
1963            NodeType::Thought,
1964            NodeType::Hypothesis,
1965            NodeType::Conclusion,
1966            NodeType::Aggregation,
1967            NodeType::Root,
1968            NodeType::Refinement,
1969            NodeType::Terminal,
1970        ] {
1971            let str_val = node_type.to_string();
1972            let parsed = NodeType::from_str(&str_val).unwrap();
1973            assert_eq!(parsed, node_type);
1974        }
1975    }
1976
1977    // ========================================================================
1978    // EdgeType tests
1979    // ========================================================================
1980
1981    #[test]
1982    fn test_edge_type_display() {
1983        assert_eq!(EdgeType::Generates.to_string(), "generates");
1984        assert_eq!(EdgeType::Refines.to_string(), "refines");
1985        assert_eq!(EdgeType::Aggregates.to_string(), "aggregates");
1986        assert_eq!(EdgeType::Supports.to_string(), "supports");
1987        assert_eq!(EdgeType::Contradicts.to_string(), "contradicts");
1988    }
1989
1990    #[test]
1991    fn test_edge_type_from_str() {
1992        assert_eq!(
1993            EdgeType::from_str("generates").unwrap(),
1994            EdgeType::Generates
1995        );
1996        assert_eq!(EdgeType::from_str("refines").unwrap(), EdgeType::Refines);
1997        assert_eq!(
1998            EdgeType::from_str("aggregates").unwrap(),
1999            EdgeType::Aggregates
2000        );
2001        assert_eq!(EdgeType::from_str("supports").unwrap(), EdgeType::Supports);
2002        assert_eq!(
2003            EdgeType::from_str("contradicts").unwrap(),
2004            EdgeType::Contradicts
2005        );
2006    }
2007
2008    #[test]
2009    fn test_edge_type_from_str_case_insensitive() {
2010        assert_eq!(
2011            EdgeType::from_str("GENERATES").unwrap(),
2012            EdgeType::Generates
2013        );
2014        assert_eq!(EdgeType::from_str("Refines").unwrap(), EdgeType::Refines);
2015        assert_eq!(
2016            EdgeType::from_str("AGGREGATES").unwrap(),
2017            EdgeType::Aggregates
2018        );
2019    }
2020
2021    #[test]
2022    fn test_edge_type_from_str_invalid() {
2023        assert!(EdgeType::from_str("invalid").is_err());
2024        assert!(EdgeType::from_str("").is_err());
2025        assert_eq!(
2026            EdgeType::from_str("unknown").unwrap_err(),
2027            "Unknown edge type: unknown"
2028        );
2029    }
2030
2031    #[test]
2032    fn test_edge_type_default() {
2033        assert_eq!(EdgeType::default(), EdgeType::Generates);
2034    }
2035
2036    #[test]
2037    fn test_edge_type_round_trip() {
2038        for edge_type in [
2039            EdgeType::Generates,
2040            EdgeType::Refines,
2041            EdgeType::Aggregates,
2042            EdgeType::Supports,
2043            EdgeType::Contradicts,
2044        ] {
2045            let str_val = edge_type.to_string();
2046            let parsed = EdgeType::from_str(&str_val).unwrap();
2047            assert_eq!(parsed, edge_type);
2048        }
2049    }
2050
2051    // ========================================================================
2052    // SnapshotType tests
2053    // ========================================================================
2054
2055    #[test]
2056    fn test_snapshot_type_display() {
2057        assert_eq!(SnapshotType::Full.to_string(), "full");
2058        assert_eq!(SnapshotType::Incremental.to_string(), "incremental");
2059        assert_eq!(SnapshotType::Branch.to_string(), "branch");
2060    }
2061
2062    #[test]
2063    fn test_snapshot_type_from_str() {
2064        assert_eq!(SnapshotType::from_str("full").unwrap(), SnapshotType::Full);
2065        assert_eq!(
2066            SnapshotType::from_str("incremental").unwrap(),
2067            SnapshotType::Incremental
2068        );
2069        assert_eq!(
2070            SnapshotType::from_str("branch").unwrap(),
2071            SnapshotType::Branch
2072        );
2073    }
2074
2075    #[test]
2076    fn test_snapshot_type_from_str_case_insensitive() {
2077        assert_eq!(SnapshotType::from_str("FULL").unwrap(), SnapshotType::Full);
2078        assert_eq!(
2079            SnapshotType::from_str("Incremental").unwrap(),
2080            SnapshotType::Incremental
2081        );
2082        assert_eq!(
2083            SnapshotType::from_str("BRANCH").unwrap(),
2084            SnapshotType::Branch
2085        );
2086    }
2087
2088    #[test]
2089    fn test_snapshot_type_from_str_invalid() {
2090        assert!(SnapshotType::from_str("invalid").is_err());
2091        assert!(SnapshotType::from_str("").is_err());
2092        assert_eq!(
2093            SnapshotType::from_str("unknown").unwrap_err(),
2094            "Unknown snapshot type: unknown"
2095        );
2096    }
2097
2098    #[test]
2099    fn test_snapshot_type_default() {
2100        assert_eq!(SnapshotType::default(), SnapshotType::Full);
2101    }
2102
2103    #[test]
2104    fn test_snapshot_type_round_trip() {
2105        for snapshot_type in [
2106            SnapshotType::Full,
2107            SnapshotType::Incremental,
2108            SnapshotType::Branch,
2109        ] {
2110            let str_val = snapshot_type.to_string();
2111            let parsed = SnapshotType::from_str(&str_val).unwrap();
2112            assert_eq!(parsed, snapshot_type);
2113        }
2114    }
2115
2116    // ========================================================================
2117    // DetectionType tests
2118    // ========================================================================
2119
2120    #[test]
2121    fn test_detection_type_display() {
2122        assert_eq!(DetectionType::Bias.to_string(), "bias");
2123        assert_eq!(DetectionType::Fallacy.to_string(), "fallacy");
2124    }
2125
2126    #[test]
2127    fn test_detection_type_from_str() {
2128        assert_eq!(
2129            DetectionType::from_str("bias").unwrap(),
2130            DetectionType::Bias
2131        );
2132        assert_eq!(
2133            DetectionType::from_str("fallacy").unwrap(),
2134            DetectionType::Fallacy
2135        );
2136    }
2137
2138    #[test]
2139    fn test_detection_type_from_str_case_insensitive() {
2140        assert_eq!(
2141            DetectionType::from_str("BIAS").unwrap(),
2142            DetectionType::Bias
2143        );
2144        assert_eq!(
2145            DetectionType::from_str("Fallacy").unwrap(),
2146            DetectionType::Fallacy
2147        );
2148    }
2149
2150    #[test]
2151    fn test_detection_type_from_str_invalid() {
2152        assert!(DetectionType::from_str("invalid").is_err());
2153        assert!(DetectionType::from_str("").is_err());
2154        assert_eq!(
2155            DetectionType::from_str("unknown").unwrap_err(),
2156            "Unknown detection type: unknown"
2157        );
2158    }
2159
2160    #[test]
2161    fn test_detection_type_default() {
2162        assert_eq!(DetectionType::default(), DetectionType::Bias);
2163    }
2164
2165    #[test]
2166    fn test_detection_type_round_trip() {
2167        for detection_type in [DetectionType::Bias, DetectionType::Fallacy] {
2168            let str_val = detection_type.to_string();
2169            let parsed = DetectionType::from_str(&str_val).unwrap();
2170            assert_eq!(parsed, detection_type);
2171        }
2172    }
2173
2174    // ========================================================================
2175    // Builder method tests
2176    // ========================================================================
2177
2178    #[test]
2179    fn test_session_new() {
2180        let session = Session::new("linear");
2181        assert_eq!(session.mode, "linear");
2182        assert!(session.metadata.is_none());
2183        assert!(session.active_branch_id.is_none());
2184        assert!(!session.id.is_empty());
2185    }
2186
2187    #[test]
2188    fn test_session_with_active_branch() {
2189        let session = Session::new("tree").with_active_branch("branch-123");
2190        assert_eq!(session.active_branch_id, Some("branch-123".to_string()));
2191    }
2192
2193    #[test]
2194    fn test_thought_new() {
2195        let thought = Thought::new("session-123", "test content", "linear");
2196        assert_eq!(thought.session_id, "session-123");
2197        assert_eq!(thought.content, "test content");
2198        assert_eq!(thought.mode, "linear");
2199        assert_eq!(thought.confidence, 0.8);
2200        assert!(thought.parent_id.is_none());
2201        assert!(thought.branch_id.is_none());
2202        assert!(!thought.id.is_empty());
2203    }
2204
2205    #[test]
2206    fn test_thought_with_confidence() {
2207        let thought = Thought::new("s1", "content", "linear").with_confidence(0.95);
2208        assert_eq!(thought.confidence, 0.95);
2209    }
2210
2211    #[test]
2212    fn test_thought_with_confidence_clamping() {
2213        let thought1 = Thought::new("s1", "content", "linear").with_confidence(1.5);
2214        assert_eq!(thought1.confidence, 1.0);
2215
2216        let thought2 = Thought::new("s1", "content", "linear").with_confidence(-0.5);
2217        assert_eq!(thought2.confidence, 0.0);
2218    }
2219
2220    #[test]
2221    fn test_thought_with_parent() {
2222        let thought = Thought::new("s1", "content", "linear").with_parent("parent-123");
2223        assert_eq!(thought.parent_id, Some("parent-123".to_string()));
2224    }
2225
2226    #[test]
2227    fn test_thought_with_branch() {
2228        let thought = Thought::new("s1", "content", "tree").with_branch("branch-123");
2229        assert_eq!(thought.branch_id, Some("branch-123".to_string()));
2230    }
2231
2232    #[test]
2233    fn test_thought_builder_chain() {
2234        let thought = Thought::new("s1", "content", "tree")
2235            .with_confidence(0.9)
2236            .with_parent("p1")
2237            .with_branch("b1")
2238            .with_metadata(serde_json::json!({"key": "value"}));
2239
2240        assert_eq!(thought.confidence, 0.9);
2241        assert_eq!(thought.parent_id, Some("p1".to_string()));
2242        assert_eq!(thought.branch_id, Some("b1".to_string()));
2243        assert!(thought.metadata.is_some());
2244    }
2245
2246    #[test]
2247    fn test_branch_new() {
2248        let branch = Branch::new("session-123");
2249        assert_eq!(branch.session_id, "session-123");
2250        assert!(branch.name.is_none());
2251        assert!(branch.parent_branch_id.is_none());
2252        assert_eq!(branch.priority, 1.0);
2253        assert_eq!(branch.confidence, 0.8);
2254        assert_eq!(branch.state, BranchState::Active);
2255        assert!(!branch.id.is_empty());
2256    }
2257
2258    #[test]
2259    fn test_branch_with_name() {
2260        let branch = Branch::new("s1").with_name("main branch");
2261        assert_eq!(branch.name, Some("main branch".to_string()));
2262    }
2263
2264    #[test]
2265    fn test_branch_with_parent() {
2266        let branch = Branch::new("s1").with_parent("parent-branch");
2267        assert_eq!(branch.parent_branch_id, Some("parent-branch".to_string()));
2268    }
2269
2270    #[test]
2271    fn test_branch_with_priority() {
2272        let branch = Branch::new("s1").with_priority(0.5);
2273        assert_eq!(branch.priority, 0.5);
2274    }
2275
2276    #[test]
2277    fn test_branch_with_confidence() {
2278        let branch = Branch::new("s1").with_confidence(0.95);
2279        assert_eq!(branch.confidence, 0.95);
2280    }
2281
2282    #[test]
2283    fn test_branch_with_confidence_clamping() {
2284        let branch1 = Branch::new("s1").with_confidence(1.5);
2285        assert_eq!(branch1.confidence, 1.0);
2286
2287        let branch2 = Branch::new("s1").with_confidence(-0.5);
2288        assert_eq!(branch2.confidence, 0.0);
2289    }
2290
2291    #[test]
2292    fn test_branch_with_state() {
2293        let branch = Branch::new("s1").with_state(BranchState::Completed);
2294        assert_eq!(branch.state, BranchState::Completed);
2295    }
2296
2297    #[test]
2298    fn test_cross_ref_new() {
2299        let cross_ref = CrossRef::new("branch1", "branch2", CrossRefType::Supports);
2300        assert_eq!(cross_ref.from_branch_id, "branch1");
2301        assert_eq!(cross_ref.to_branch_id, "branch2");
2302        assert_eq!(cross_ref.ref_type, CrossRefType::Supports);
2303        assert!(cross_ref.reason.is_none());
2304        assert_eq!(cross_ref.strength, 1.0);
2305        assert!(!cross_ref.id.is_empty());
2306    }
2307
2308    #[test]
2309    fn test_cross_ref_with_reason() {
2310        let cross_ref = CrossRef::new("b1", "b2", CrossRefType::Extends)
2311            .with_reason("builds on previous analysis");
2312        assert_eq!(
2313            cross_ref.reason,
2314            Some("builds on previous analysis".to_string())
2315        );
2316    }
2317
2318    #[test]
2319    fn test_cross_ref_with_strength() {
2320        let cross_ref = CrossRef::new("b1", "b2", CrossRefType::Contradicts).with_strength(0.75);
2321        assert_eq!(cross_ref.strength, 0.75);
2322    }
2323
2324    #[test]
2325    fn test_cross_ref_strength_clamping() {
2326        let cross_ref1 = CrossRef::new("b1", "b2", CrossRefType::Supports).with_strength(1.5);
2327        assert_eq!(cross_ref1.strength, 1.0);
2328
2329        let cross_ref2 = CrossRef::new("b1", "b2", CrossRefType::Supports).with_strength(-0.5);
2330        assert_eq!(cross_ref2.strength, 0.0);
2331    }
2332
2333    #[test]
2334    fn test_checkpoint_new() {
2335        let snapshot = serde_json::json!({"state": "test"});
2336        let checkpoint = Checkpoint::new("session-123", "checkpoint1", snapshot.clone());
2337        assert_eq!(checkpoint.session_id, "session-123");
2338        assert_eq!(checkpoint.name, "checkpoint1");
2339        assert_eq!(checkpoint.snapshot, snapshot);
2340        assert!(checkpoint.branch_id.is_none());
2341        assert!(checkpoint.description.is_none());
2342        assert!(!checkpoint.id.is_empty());
2343    }
2344
2345    #[test]
2346    fn test_checkpoint_with_branch() {
2347        let snapshot = serde_json::json!({"state": "test"});
2348        let checkpoint = Checkpoint::new("s1", "cp1", snapshot).with_branch("branch-123");
2349        assert_eq!(checkpoint.branch_id, Some("branch-123".to_string()));
2350    }
2351
2352    #[test]
2353    fn test_checkpoint_with_description() {
2354        let snapshot = serde_json::json!({"state": "test"});
2355        let checkpoint =
2356            Checkpoint::new("s1", "cp1", snapshot).with_description("before major decision");
2357        assert_eq!(
2358            checkpoint.description,
2359            Some("before major decision".to_string())
2360        );
2361    }
2362
2363    #[test]
2364    fn test_graph_node_new() {
2365        let node = GraphNode::new("session-123", "node content");
2366        assert_eq!(node.session_id, "session-123");
2367        assert_eq!(node.content, "node content");
2368        assert_eq!(node.node_type, NodeType::Thought);
2369        assert!(node.score.is_none());
2370        assert_eq!(node.depth, 0);
2371        assert!(!node.is_terminal);
2372        assert!(!node.is_root);
2373        assert!(node.is_active);
2374        assert!(!node.id.is_empty());
2375    }
2376
2377    #[test]
2378    fn test_graph_node_with_type() {
2379        let node = GraphNode::new("s1", "content").with_type(NodeType::Hypothesis);
2380        assert_eq!(node.node_type, NodeType::Hypothesis);
2381    }
2382
2383    #[test]
2384    fn test_graph_node_with_score() {
2385        let node = GraphNode::new("s1", "content").with_score(0.85);
2386        assert_eq!(node.score, Some(0.85));
2387    }
2388
2389    #[test]
2390    fn test_graph_node_with_score_clamping() {
2391        let node1 = GraphNode::new("s1", "content").with_score(1.5);
2392        assert_eq!(node1.score, Some(1.0));
2393
2394        let node2 = GraphNode::new("s1", "content").with_score(-0.5);
2395        assert_eq!(node2.score, Some(0.0));
2396    }
2397
2398    #[test]
2399    fn test_graph_node_with_depth() {
2400        let node = GraphNode::new("s1", "content").with_depth(3);
2401        assert_eq!(node.depth, 3);
2402    }
2403
2404    #[test]
2405    fn test_graph_node_as_terminal() {
2406        let node = GraphNode::new("s1", "content").as_terminal();
2407        assert!(node.is_terminal);
2408    }
2409
2410    #[test]
2411    fn test_graph_node_as_root() {
2412        let node = GraphNode::new("s1", "content").as_root();
2413        assert!(node.is_root);
2414    }
2415
2416    #[test]
2417    fn test_graph_node_as_active() {
2418        let node = GraphNode::new("s1", "content").as_inactive().as_active();
2419        assert!(node.is_active);
2420    }
2421
2422    #[test]
2423    fn test_graph_node_as_inactive() {
2424        let node = GraphNode::new("s1", "content").as_inactive();
2425        assert!(!node.is_active);
2426    }
2427
2428    #[test]
2429    fn test_graph_edge_new() {
2430        let edge = GraphEdge::new("session-123", "node1", "node2");
2431        assert_eq!(edge.session_id, "session-123");
2432        assert_eq!(edge.from_node, "node1");
2433        assert_eq!(edge.to_node, "node2");
2434        assert_eq!(edge.edge_type, EdgeType::Generates);
2435        assert_eq!(edge.weight, 1.0);
2436        assert!(!edge.id.is_empty());
2437    }
2438
2439    #[test]
2440    fn test_graph_edge_with_type() {
2441        let edge = GraphEdge::new("s1", "n1", "n2").with_type(EdgeType::Refines);
2442        assert_eq!(edge.edge_type, EdgeType::Refines);
2443    }
2444
2445    #[test]
2446    fn test_graph_edge_with_weight() {
2447        let edge = GraphEdge::new("s1", "n1", "n2").with_weight(0.75);
2448        assert_eq!(edge.weight, 0.75);
2449    }
2450
2451    #[test]
2452    fn test_graph_edge_with_weight_clamping() {
2453        let edge1 = GraphEdge::new("s1", "n1", "n2").with_weight(1.5);
2454        assert_eq!(edge1.weight, 1.0);
2455
2456        let edge2 = GraphEdge::new("s1", "n1", "n2").with_weight(-0.5);
2457        assert_eq!(edge2.weight, 0.0);
2458    }
2459
2460    #[test]
2461    fn test_state_snapshot_new() {
2462        let data = serde_json::json!({"key": "value"});
2463        let snapshot = StateSnapshot::new("session-123", data.clone());
2464        assert_eq!(snapshot.session_id, "session-123");
2465        assert_eq!(snapshot.state_data, data);
2466        assert_eq!(snapshot.snapshot_type, SnapshotType::Full);
2467        assert!(snapshot.parent_snapshot_id.is_none());
2468        assert!(snapshot.description.is_none());
2469        assert!(!snapshot.id.is_empty());
2470    }
2471
2472    #[test]
2473    fn test_state_snapshot_with_type() {
2474        let data = serde_json::json!({"key": "value"});
2475        let snapshot = StateSnapshot::new("s1", data).with_type(SnapshotType::Incremental);
2476        assert_eq!(snapshot.snapshot_type, SnapshotType::Incremental);
2477    }
2478
2479    #[test]
2480    fn test_state_snapshot_with_parent() {
2481        let data = serde_json::json!({"key": "value"});
2482        let snapshot = StateSnapshot::new("s1", data).with_parent("parent-123");
2483        assert_eq!(snapshot.parent_snapshot_id, Some("parent-123".to_string()));
2484    }
2485
2486    #[test]
2487    fn test_state_snapshot_with_description() {
2488        let data = serde_json::json!({"key": "value"});
2489        let snapshot = StateSnapshot::new("s1", data).with_description("after step 5");
2490        assert_eq!(snapshot.description, Some("after step 5".to_string()));
2491    }
2492
2493    #[test]
2494    fn test_invocation_new() {
2495        let input = serde_json::json!({"param": "value"});
2496        let invocation = Invocation::new("linear_reasoning", input.clone());
2497        assert_eq!(invocation.tool_name, "linear_reasoning");
2498        assert_eq!(invocation.input, input);
2499        assert!(invocation.session_id.is_none());
2500        assert!(invocation.output.is_none());
2501        assert!(invocation.pipe_name.is_none());
2502        assert!(invocation.latency_ms.is_none());
2503        assert!(invocation.success);
2504        assert!(invocation.error.is_none());
2505        assert!(!invocation.id.is_empty());
2506    }
2507
2508    #[test]
2509    fn test_invocation_with_session() {
2510        let input = serde_json::json!({"param": "value"});
2511        let invocation = Invocation::new("tool", input).with_session("session-123");
2512        assert_eq!(invocation.session_id, Some("session-123".to_string()));
2513    }
2514
2515    #[test]
2516    fn test_invocation_with_pipe() {
2517        let input = serde_json::json!({"param": "value"});
2518        let invocation = Invocation::new("tool", input).with_pipe("linear-v1");
2519        assert_eq!(invocation.pipe_name, Some("linear-v1".to_string()));
2520    }
2521
2522    #[test]
2523    fn test_invocation_success() {
2524        let input = serde_json::json!({"param": "value"});
2525        let output = serde_json::json!({"result": "ok"});
2526        let invocation = Invocation::new("tool", input).success(output.clone(), 150);
2527        assert!(invocation.success);
2528        assert_eq!(invocation.output, Some(output));
2529        assert_eq!(invocation.latency_ms, Some(150));
2530        assert!(invocation.error.is_none());
2531    }
2532
2533    #[test]
2534    fn test_invocation_failure() {
2535        let input = serde_json::json!({"param": "value"});
2536        let invocation = Invocation::new("tool", input).failure("connection timeout", 3000);
2537        assert!(!invocation.success);
2538        assert_eq!(invocation.error, Some("connection timeout".to_string()));
2539        assert_eq!(invocation.latency_ms, Some(3000));
2540        assert!(invocation.output.is_none());
2541    }
2542
2543    #[test]
2544    fn test_detection_new() {
2545        let detection = Detection::new(
2546            DetectionType::Bias,
2547            "confirmation_bias",
2548            4,
2549            0.85,
2550            "Only seeking confirming evidence",
2551        );
2552        assert_eq!(detection.detection_type, DetectionType::Bias);
2553        assert_eq!(detection.detected_issue, "confirmation_bias");
2554        assert_eq!(detection.severity, 4);
2555        assert_eq!(detection.confidence, 0.85);
2556        assert_eq!(detection.explanation, "Only seeking confirming evidence");
2557        assert!(detection.session_id.is_none());
2558        assert!(detection.thought_id.is_none());
2559        assert!(detection.remediation.is_none());
2560        assert!(!detection.id.is_empty());
2561    }
2562
2563    #[test]
2564    fn test_detection_severity_clamping() {
2565        let detection1 = Detection::new(
2566            DetectionType::Fallacy,
2567            "ad_hominem",
2568            10,
2569            0.9,
2570            "Attacking person not argument",
2571        );
2572        assert_eq!(detection1.severity, 5);
2573
2574        let detection2 = Detection::new(
2575            DetectionType::Fallacy,
2576            "ad_hominem",
2577            0,
2578            0.9,
2579            "Attacking person not argument",
2580        );
2581        assert_eq!(detection2.severity, 1);
2582    }
2583
2584    #[test]
2585    fn test_detection_confidence_clamping() {
2586        let detection1 = Detection::new(
2587            DetectionType::Bias,
2588            "anchoring",
2589            3,
2590            1.5,
2591            "Over-reliance on initial info",
2592        );
2593        assert_eq!(detection1.confidence, 1.0);
2594
2595        let detection2 = Detection::new(
2596            DetectionType::Bias,
2597            "anchoring",
2598            3,
2599            -0.5,
2600            "Over-reliance on initial info",
2601        );
2602        assert_eq!(detection2.confidence, 0.0);
2603    }
2604
2605    #[test]
2606    fn test_detection_with_session() {
2607        let detection = Detection::new(DetectionType::Bias, "sunk_cost", 3, 0.75, "explanation")
2608            .with_session("session-123");
2609        assert_eq!(detection.session_id, Some("session-123".to_string()));
2610    }
2611
2612    #[test]
2613    fn test_detection_with_thought() {
2614        let detection = Detection::new(DetectionType::Fallacy, "strawman", 4, 0.8, "explanation")
2615            .with_thought("thought-123");
2616        assert_eq!(detection.thought_id, Some("thought-123".to_string()));
2617    }
2618
2619    #[test]
2620    fn test_detection_with_remediation() {
2621        let detection = Detection::new(DetectionType::Bias, "availability", 2, 0.7, "explanation")
2622            .with_remediation("Consider base rates");
2623        assert_eq!(
2624            detection.remediation,
2625            Some("Consider base rates".to_string())
2626        );
2627    }
2628
2629    #[test]
2630    fn test_detection_with_metadata() {
2631        let metadata = serde_json::json!({"source": "automatic"});
2632        let detection = Detection::new(DetectionType::Bias, "hindsight", 2, 0.65, "explanation")
2633            .with_metadata(metadata.clone());
2634        assert_eq!(detection.metadata, Some(metadata));
2635    }
2636
2637    #[test]
2638    fn test_decision_new() {
2639        let options = vec!["option1".to_string(), "option2".to_string()];
2640        let recommendation = serde_json::json!({"choice": "option1"});
2641        let scores = serde_json::json!([{"option": "option1", "score": 0.8}]);
2642
2643        let decision = Decision::new(
2644            "session-123",
2645            "Which option to choose?",
2646            options.clone(),
2647            "weighted_sum",
2648            recommendation.clone(),
2649            scores.clone(),
2650        );
2651
2652        assert_eq!(decision.session_id, "session-123");
2653        assert_eq!(decision.question, "Which option to choose?");
2654        assert_eq!(decision.options, options);
2655        assert_eq!(decision.method, "weighted_sum");
2656        assert_eq!(decision.recommendation, recommendation);
2657        assert_eq!(decision.scores, scores);
2658        assert!(decision.criteria.is_none());
2659        assert!(!decision.id.is_empty());
2660    }
2661
2662    #[test]
2663    fn test_decision_with_criteria() {
2664        let options = vec!["a".to_string()];
2665        let criteria = vec![StoredCriterion {
2666            name: "cost".to_string(),
2667            weight: 0.5,
2668            description: Some("Total cost".to_string()),
2669        }];
2670
2671        let decision = Decision::new(
2672            "s1",
2673            "question",
2674            options,
2675            "method",
2676            serde_json::json!({}),
2677            serde_json::json!([]),
2678        )
2679        .with_criteria(criteria.clone());
2680
2681        assert_eq!(decision.criteria.unwrap().len(), 1);
2682    }
2683
2684    #[test]
2685    fn test_perspective_analysis_new() {
2686        let stakeholders = serde_json::json!([{"name": "users"}]);
2687        let synthesis = serde_json::json!({"summary": "analysis"});
2688
2689        let analysis = PerspectiveAnalysis::new(
2690            "session-123",
2691            "new feature",
2692            stakeholders.clone(),
2693            synthesis.clone(),
2694            0.85,
2695        );
2696
2697        assert_eq!(analysis.session_id, "session-123");
2698        assert_eq!(analysis.topic, "new feature");
2699        assert_eq!(analysis.stakeholders, stakeholders);
2700        assert_eq!(analysis.synthesis, synthesis);
2701        assert_eq!(analysis.confidence, 0.85);
2702        assert!(analysis.power_matrix.is_none());
2703        assert!(!analysis.id.is_empty());
2704    }
2705
2706    #[test]
2707    fn test_perspective_analysis_confidence_clamping() {
2708        let stakeholders = serde_json::json!([]);
2709        let synthesis = serde_json::json!({});
2710
2711        let analysis1 =
2712            PerspectiveAnalysis::new("s1", "topic", stakeholders.clone(), synthesis.clone(), 1.5);
2713        assert_eq!(analysis1.confidence, 1.0);
2714
2715        let analysis2 = PerspectiveAnalysis::new("s1", "topic", stakeholders, synthesis, -0.5);
2716        assert_eq!(analysis2.confidence, 0.0);
2717    }
2718
2719    #[test]
2720    fn test_evidence_assessment_new() {
2721        let evidence = serde_json::json!([{"type": "empirical"}]);
2722        let support = serde_json::json!({"level": "strong"});
2723        let analysis = serde_json::json!([{"id": 1}]);
2724
2725        let assessment = EvidenceAssessment::new(
2726            "session-123",
2727            "climate change is real",
2728            evidence.clone(),
2729            support.clone(),
2730            analysis.clone(),
2731        );
2732
2733        assert_eq!(assessment.session_id, "session-123");
2734        assert_eq!(assessment.claim, "climate change is real");
2735        assert_eq!(assessment.evidence, evidence);
2736        assert_eq!(assessment.overall_support, support);
2737        assert_eq!(assessment.evidence_analysis, analysis);
2738        assert!(assessment.chain_analysis.is_none());
2739        assert!(!assessment.id.is_empty());
2740    }
2741
2742    #[test]
2743    fn test_probability_update_new() {
2744        let steps = serde_json::json!([{"step": 1}]);
2745        let interpretation = serde_json::json!({"result": "increased"});
2746
2747        let update = ProbabilityUpdate::new(
2748            "session-123",
2749            "hypothesis X is true",
2750            0.5,
2751            0.75,
2752            steps.clone(),
2753            interpretation.clone(),
2754        );
2755
2756        assert_eq!(update.session_id, "session-123");
2757        assert_eq!(update.hypothesis, "hypothesis X is true");
2758        assert_eq!(update.prior, 0.5);
2759        assert_eq!(update.posterior, 0.75);
2760        assert_eq!(update.update_steps, steps);
2761        assert_eq!(update.interpretation, interpretation);
2762        assert!(update.confidence_lower.is_none());
2763        assert!(!update.id.is_empty());
2764    }
2765
2766    #[test]
2767    fn test_probability_update_prior_posterior_clamping() {
2768        let steps = serde_json::json!([]);
2769        let interpretation = serde_json::json!({});
2770
2771        let update1 =
2772            ProbabilityUpdate::new("s1", "h1", 1.5, -0.5, steps.clone(), interpretation.clone());
2773        assert_eq!(update1.prior, 1.0);
2774        assert_eq!(update1.posterior, 0.0);
2775
2776        let update2 = ProbabilityUpdate::new("s1", "h1", -0.2, 1.2, steps, interpretation);
2777        assert_eq!(update2.prior, 0.0);
2778        assert_eq!(update2.posterior, 1.0);
2779    }
2780
2781    #[test]
2782    fn test_probability_update_with_confidence_interval() {
2783        let steps = serde_json::json!([]);
2784        let interpretation = serde_json::json!({});
2785
2786        let update = ProbabilityUpdate::new("s1", "h1", 0.5, 0.7, steps, interpretation)
2787            .with_confidence_interval(Some(0.6), Some(0.8), Some(0.95));
2788
2789        assert_eq!(update.confidence_lower, Some(0.6));
2790        assert_eq!(update.confidence_upper, Some(0.8));
2791        assert_eq!(update.confidence_level, Some(0.95));
2792    }
2793
2794    #[test]
2795    fn test_probability_update_confidence_interval_clamping() {
2796        let steps = serde_json::json!([]);
2797        let interpretation = serde_json::json!({});
2798
2799        let update = ProbabilityUpdate::new("s1", "h1", 0.5, 0.7, steps, interpretation)
2800            .with_confidence_interval(Some(1.5), Some(-0.5), Some(2.0));
2801
2802        assert_eq!(update.confidence_lower, Some(1.0));
2803        assert_eq!(update.confidence_upper, Some(0.0));
2804        assert_eq!(update.confidence_level, Some(1.0));
2805    }
2806
2807    // ========================================================================
2808    // Invocation fallback tracking tests
2809    // ========================================================================
2810
2811    #[test]
2812    fn test_invocation_new_defaults_no_fallback() {
2813        let inv = Invocation::new("test_tool", serde_json::json!({"key": "value"}));
2814        assert!(!inv.fallback_used);
2815        assert!(inv.fallback_type.is_none());
2816    }
2817
2818    #[test]
2819    fn test_invocation_with_fallback() {
2820        let inv = Invocation::new("test_tool", serde_json::json!({})).with_fallback("parse_error");
2821        assert!(inv.fallback_used);
2822        assert_eq!(inv.fallback_type, Some("parse_error".to_string()));
2823    }
2824
2825    #[test]
2826    fn test_invocation_with_parse_error_fallback() {
2827        let inv = Invocation::new("test_tool", serde_json::json!({})).with_parse_error_fallback();
2828        assert!(inv.fallback_used);
2829        assert_eq!(inv.fallback_type, Some("parse_error".to_string()));
2830    }
2831
2832    #[test]
2833    fn test_invocation_with_api_unavailable_fallback() {
2834        let inv =
2835            Invocation::new("test_tool", serde_json::json!({})).with_api_unavailable_fallback();
2836        assert!(inv.fallback_used);
2837        assert_eq!(inv.fallback_type, Some("api_unavailable".to_string()));
2838    }
2839
2840    #[test]
2841    fn test_invocation_with_local_calculation_fallback() {
2842        let inv =
2843            Invocation::new("test_tool", serde_json::json!({})).with_local_calculation_fallback();
2844        assert!(inv.fallback_used);
2845        assert_eq!(inv.fallback_type, Some("local_calculation".to_string()));
2846    }
2847
2848    #[test]
2849    fn test_invocation_builder_chain_with_fallback() {
2850        let inv = Invocation::new("test_tool", serde_json::json!({"test": true}))
2851            .with_session("session-123")
2852            .with_pipe("test-pipe-v1")
2853            .with_fallback("api_unavailable")
2854            .success(serde_json::json!({"result": "ok"}), 150);
2855
2856        assert_eq!(inv.session_id, Some("session-123".to_string()));
2857        assert_eq!(inv.pipe_name, Some("test-pipe-v1".to_string()));
2858        assert!(inv.fallback_used);
2859        assert_eq!(inv.fallback_type, Some("api_unavailable".to_string()));
2860        assert!(inv.success);
2861        assert_eq!(inv.latency_ms, Some(150));
2862    }
2863
2864    // ========================================================================
2865    // FallbackMetricsSummary tests
2866    // ========================================================================
2867
2868    #[test]
2869    fn test_fallback_metrics_summary_serialization() {
2870        use std::collections::HashMap;
2871
2872        let mut by_type = HashMap::new();
2873        by_type.insert("parse_error".to_string(), 5);
2874        by_type.insert("api_unavailable".to_string(), 3);
2875
2876        let mut by_pipe = HashMap::new();
2877        by_pipe.insert("linear-reasoning-v1".to_string(), 4);
2878        by_pipe.insert("tree-reasoning-v1".to_string(), 4);
2879
2880        let summary = FallbackMetricsSummary {
2881            total_fallbacks: 8,
2882            fallbacks_by_type: by_type,
2883            fallbacks_by_pipe: by_pipe,
2884            total_invocations: 100,
2885            fallback_rate: 0.08,
2886            recommendation: "Test recommendation".to_string(),
2887            timestamp_reconstructions: 0,
2888            records_skipped: 0,
2889        };
2890
2891        // Test serialization
2892        let json = serde_json::to_string(&summary).unwrap();
2893        assert!(json.contains("\"total_fallbacks\":8"));
2894        assert!(json.contains("\"fallback_rate\":0.08"));
2895        assert!(json.contains("\"recommendation\":\"Test recommendation\""));
2896
2897        // Test deserialization
2898        let deserialized: FallbackMetricsSummary = serde_json::from_str(&json).unwrap();
2899        assert_eq!(deserialized.total_fallbacks, 8);
2900        assert_eq!(deserialized.total_invocations, 100);
2901        assert_eq!(deserialized.fallback_rate, 0.08);
2902    }
2903}