1mod 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#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Session {
27 pub id: String,
29 pub mode: String,
31 pub created_at: DateTime<Utc>,
33 pub updated_at: DateTime<Utc>,
35 pub metadata: Option<serde_json::Value>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub active_branch_id: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Thought {
45 pub id: String,
47 pub session_id: String,
49 pub content: String,
51 pub confidence: f64,
53 pub mode: String,
55 pub parent_id: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub branch_id: Option<String>,
60 pub created_at: DateTime<Utc>,
62 pub metadata: Option<serde_json::Value>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct Branch {
69 pub id: String,
71 pub session_id: String,
73 pub name: Option<String>,
75 pub parent_branch_id: Option<String>,
77 pub priority: f64,
79 pub confidence: f64,
81 pub state: BranchState,
83 pub created_at: DateTime<Utc>,
85 pub updated_at: DateTime<Utc>,
87 pub metadata: Option<serde_json::Value>,
89}
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub enum BranchState {
95 #[default]
97 Active,
98 Completed,
100 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#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CrossRef {
130 pub id: String,
132 pub from_branch_id: String,
134 pub to_branch_id: String,
136 pub ref_type: CrossRefType,
138 pub reason: Option<String>,
140 pub strength: f64,
142 pub created_at: DateTime<Utc>,
144}
145
146#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149pub enum CrossRefType {
150 #[default]
152 Supports,
153 Contradicts,
155 Extends,
157 Alternative,
159 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#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Checkpoint {
193 pub id: String,
195 pub session_id: String,
197 pub branch_id: Option<String>,
199 pub name: String,
201 pub description: Option<String>,
203 pub snapshot: serde_json::Value,
205 pub created_at: DateTime<Utc>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct GraphNode {
212 pub id: String,
214 pub session_id: String,
216 pub content: String,
218 pub node_type: NodeType,
220 pub score: Option<f64>,
222 pub depth: i32,
224 pub is_terminal: bool,
226 pub is_root: bool,
228 pub is_active: bool,
230 pub created_at: DateTime<Utc>,
232 pub metadata: Option<serde_json::Value>,
234}
235
236#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "snake_case")]
239pub enum NodeType {
240 #[default]
242 Thought,
243 Hypothesis,
245 Conclusion,
247 Aggregation,
249 Root,
251 Refinement,
253 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#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct GraphEdge {
291 pub id: String,
293 pub session_id: String,
295 pub from_node: String,
297 pub to_node: String,
299 pub edge_type: EdgeType,
301 pub weight: f64,
303 pub created_at: DateTime<Utc>,
305 pub metadata: Option<serde_json::Value>,
307}
308
309#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum EdgeType {
313 #[default]
315 Generates,
316 Refines,
318 Aggregates,
320 Supports,
322 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#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct StateSnapshot {
356 pub id: String,
358 pub session_id: String,
360 pub snapshot_type: SnapshotType,
362 pub state_data: serde_json::Value,
364 pub parent_snapshot_id: Option<String>,
366 pub created_at: DateTime<Utc>,
368 pub description: Option<String>,
370}
371
372#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(rename_all = "snake_case")]
375pub enum SnapshotType {
376 #[default]
378 Full,
379 Incremental,
381 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
410#[serde(rename_all = "snake_case")]
411pub enum DetectionType {
412 #[default]
414 Bias,
415 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#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct Detection {
443 pub id: String,
445 pub session_id: Option<String>,
447 pub thought_id: Option<String>,
449 pub detection_type: DetectionType,
451 pub detected_issue: String,
453 pub severity: i32,
455 pub confidence: f64,
457 pub explanation: String,
459 pub remediation: Option<String>,
461 pub created_at: DateTime<Utc>,
463 pub metadata: Option<serde_json::Value>,
465}
466
467impl GraphNode {
468 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 pub fn with_type(mut self, node_type: NodeType) -> Self {
487 self.node_type = node_type;
488 self
489 }
490
491 pub fn with_score(mut self, score: f64) -> Self {
493 self.score = Some(score.clamp(0.0, 1.0));
494 self
495 }
496
497 pub fn with_depth(mut self, depth: i32) -> Self {
499 self.depth = depth;
500 self
501 }
502
503 pub fn as_terminal(mut self) -> Self {
505 self.is_terminal = true;
506 self
507 }
508
509 pub fn as_root(mut self) -> Self {
511 self.is_root = true;
512 self
513 }
514
515 pub fn as_active(mut self) -> Self {
517 self.is_active = true;
518 self
519 }
520
521 pub fn as_inactive(mut self) -> Self {
523 self.is_active = false;
524 self
525 }
526}
527
528impl GraphEdge {
529 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 pub fn with_type(mut self, edge_type: EdgeType) -> Self {
549 self.edge_type = edge_type;
550 self
551 }
552
553 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 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 pub fn with_type(mut self, snapshot_type: SnapshotType) -> Self {
576 self.snapshot_type = snapshot_type;
577 self
578 }
579
580 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 pub fn with_description(mut self, description: impl Into<String>) -> Self {
588 self.description = Some(description.into());
589 self
590 }
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct Invocation {
596 pub id: String,
598 pub session_id: Option<String>,
600 pub tool_name: String,
602 pub input: serde_json::Value,
604 pub output: Option<serde_json::Value>,
606 pub pipe_name: Option<String>,
608 pub latency_ms: Option<i64>,
610 pub success: bool,
612 pub error: Option<String>,
614 pub created_at: DateTime<Utc>,
616 pub fallback_used: bool,
618 pub fallback_type: Option<String>,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
631pub struct PipeUsageSummary {
632 pub pipe_name: String,
634 pub total_calls: u64,
636 pub success_count: u64,
638 pub failure_count: u64,
640 pub success_rate: f64,
642 pub avg_latency_ms: f64,
644 pub min_latency_ms: Option<i64>,
646 pub max_latency_ms: Option<i64>,
648 pub first_call: DateTime<Utc>,
650 pub last_call: DateTime<Utc>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
659pub struct FallbackMetricsSummary {
660 pub total_fallbacks: u64,
662 pub fallbacks_by_type: std::collections::HashMap<String, u64>,
664 pub fallbacks_by_pipe: std::collections::HashMap<String, u64>,
666 pub total_invocations: u64,
668 pub fallback_rate: f64,
670 pub recommendation: String,
672 pub timestamp_reconstructions: u64,
675 pub records_skipped: u64,
678}
679
680#[derive(Debug, Clone, Default, Serialize, Deserialize)]
684pub struct MetricsFilter {
685 #[serde(skip_serializing_if = "Option::is_none")]
687 pub pipe_name: Option<String>,
688 #[serde(skip_serializing_if = "Option::is_none")]
690 pub session_id: Option<String>,
691 #[serde(skip_serializing_if = "Option::is_none")]
693 pub tool_name: Option<String>,
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub after: Option<DateTime<Utc>>,
697 #[serde(skip_serializing_if = "Option::is_none")]
699 pub before: Option<DateTime<Utc>>,
700 #[serde(skip_serializing_if = "Option::is_none")]
702 pub success_only: Option<bool>,
703 #[serde(skip_serializing_if = "Option::is_none")]
705 pub limit: Option<u32>,
706}
707
708impl MetricsFilter {
709 pub fn new() -> Self {
711 Self::default()
712 }
713
714 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 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 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 pub fn after(mut self, time: DateTime<Utc>) -> Self {
734 self.after = Some(time);
735 self
736 }
737
738 pub fn before(mut self, time: DateTime<Utc>) -> Self {
740 self.before = Some(time);
741 self
742 }
743
744 pub fn successful_only(mut self) -> Self {
746 self.success_only = Some(true);
747 self
748 }
749
750 pub fn failed_only(mut self) -> Self {
752 self.success_only = Some(false);
753 self
754 }
755
756 pub fn with_limit(mut self, limit: u32) -> Self {
758 self.limit = Some(limit);
759 self
760 }
761}
762
763impl Session {
764 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 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 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 pub fn with_confidence(mut self, confidence: f64) -> Self {
806 self.confidence = confidence.clamp(0.0, 1.0);
807 self
808 }
809
810 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 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 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
824 self.metadata = Some(metadata);
825 self
826 }
827}
828
829impl Branch {
830 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 pub fn with_name(mut self, name: impl Into<String>) -> Self {
849 self.name = Some(name.into());
850 self
851 }
852
853 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 pub fn with_priority(mut self, priority: f64) -> Self {
861 self.priority = priority;
862 self
863 }
864
865 pub fn with_confidence(mut self, confidence: f64) -> Self {
867 self.confidence = confidence.clamp(0.0, 1.0);
868 self
869 }
870
871 pub fn with_state(mut self, state: BranchState) -> Self {
873 self.state = state;
874 self
875 }
876}
877
878impl CrossRef {
879 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 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
898 self.reason = Some(reason.into());
899 self
900 }
901
902 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 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 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 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 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 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 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 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 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 pub fn with_latency(mut self, latency_ms: i64) -> Self {
989 self.latency_ms = Some(latency_ms);
990 self
991 }
992
993 pub fn mark_success(mut self) -> Self {
995 self.success = true;
996 self
997 }
998
999 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 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 pub fn with_parse_error_fallback(self) -> Self {
1015 self.with_fallback("parse_error")
1016 }
1017
1018 pub fn with_api_unavailable_fallback(self) -> Self {
1020 self.with_fallback("api_unavailable")
1021 }
1022
1023 pub fn with_local_calculation_fallback(self) -> Self {
1025 self.with_fallback("local_calculation")
1026 }
1027}
1028
1029impl Detection {
1030 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 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 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 pub fn with_remediation(mut self, remediation: impl Into<String>) -> Self {
1067 self.remediation = Some(remediation.into());
1068 self
1069 }
1070
1071 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1073 self.metadata = Some(metadata);
1074 self
1075 }
1076}
1077
1078#[derive(Debug, Clone, Serialize, Deserialize)]
1084pub struct Decision {
1085 pub id: String,
1087 pub session_id: String,
1089 pub question: String,
1091 pub options: Vec<String>,
1093 pub criteria: Option<Vec<StoredCriterion>>,
1095 pub method: String,
1097 pub recommendation: serde_json::Value,
1099 pub scores: serde_json::Value,
1101 pub sensitivity_analysis: Option<serde_json::Value>,
1103 pub trade_offs: Option<serde_json::Value>,
1105 pub constraints_satisfied: Option<serde_json::Value>,
1107 pub created_at: DateTime<Utc>,
1109 pub metadata: Option<serde_json::Value>,
1111}
1112
1113#[derive(Debug, Clone, Serialize, Deserialize)]
1115pub struct StoredCriterion {
1116 pub name: String,
1118 pub weight: f64,
1120 pub description: Option<String>,
1122}
1123
1124impl Decision {
1125 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 pub fn with_criteria(mut self, criteria: Vec<StoredCriterion>) -> Self {
1153 self.criteria = Some(criteria);
1154 self
1155 }
1156
1157 pub fn with_sensitivity(mut self, analysis: serde_json::Value) -> Self {
1159 self.sensitivity_analysis = Some(analysis);
1160 self
1161 }
1162
1163 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 pub fn with_constraints(mut self, satisfied: serde_json::Value) -> Self {
1171 self.constraints_satisfied = Some(satisfied);
1172 self
1173 }
1174
1175 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1177 self.metadata = Some(metadata);
1178 self
1179 }
1180}
1181
1182#[derive(Debug, Clone, Serialize, Deserialize)]
1184pub struct PerspectiveAnalysis {
1185 pub id: String,
1187 pub session_id: String,
1189 pub topic: String,
1191 pub stakeholders: serde_json::Value,
1193 pub power_matrix: Option<serde_json::Value>,
1195 pub conflicts: Option<serde_json::Value>,
1197 pub alignments: Option<serde_json::Value>,
1199 pub synthesis: serde_json::Value,
1201 pub confidence: f64,
1203 pub created_at: DateTime<Utc>,
1205 pub metadata: Option<serde_json::Value>,
1207}
1208
1209impl PerspectiveAnalysis {
1210 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 pub fn with_power_matrix(mut self, matrix: serde_json::Value) -> Self {
1235 self.power_matrix = Some(matrix);
1236 self
1237 }
1238
1239 pub fn with_conflicts(mut self, conflicts: serde_json::Value) -> Self {
1241 self.conflicts = Some(conflicts);
1242 self
1243 }
1244
1245 pub fn with_alignments(mut self, alignments: serde_json::Value) -> Self {
1247 self.alignments = Some(alignments);
1248 self
1249 }
1250
1251 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1253 self.metadata = Some(metadata);
1254 self
1255 }
1256}
1257
1258#[derive(Debug, Clone, Serialize, Deserialize)]
1264pub struct EvidenceAssessment {
1265 pub id: String,
1267 pub session_id: String,
1269 pub claim: String,
1271 pub evidence: serde_json::Value,
1273 pub overall_support: serde_json::Value,
1275 pub evidence_analysis: serde_json::Value,
1277 pub chain_analysis: Option<serde_json::Value>,
1279 pub contradictions: Option<serde_json::Value>,
1281 pub gaps: Option<serde_json::Value>,
1283 pub recommendations: Option<serde_json::Value>,
1285 pub created_at: DateTime<Utc>,
1287 pub metadata: Option<serde_json::Value>,
1289}
1290
1291impl EvidenceAssessment {
1292 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 pub fn with_chain_analysis(mut self, analysis: serde_json::Value) -> Self {
1318 self.chain_analysis = Some(analysis);
1319 self
1320 }
1321
1322 pub fn with_contradictions(mut self, contradictions: serde_json::Value) -> Self {
1324 self.contradictions = Some(contradictions);
1325 self
1326 }
1327
1328 pub fn with_gaps(mut self, gaps: serde_json::Value) -> Self {
1330 self.gaps = Some(gaps);
1331 self
1332 }
1333
1334 pub fn with_recommendations(mut self, recommendations: serde_json::Value) -> Self {
1336 self.recommendations = Some(recommendations);
1337 self
1338 }
1339
1340 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1342 self.metadata = Some(metadata);
1343 self
1344 }
1345}
1346
1347#[derive(Debug, Clone, Serialize, Deserialize)]
1349pub struct ProbabilityUpdate {
1350 pub id: String,
1352 pub session_id: String,
1354 pub hypothesis: String,
1356 pub prior: f64,
1358 pub posterior: f64,
1360 pub confidence_lower: Option<f64>,
1362 pub confidence_upper: Option<f64>,
1364 pub confidence_level: Option<f64>,
1366 pub update_steps: serde_json::Value,
1368 pub uncertainty_analysis: Option<serde_json::Value>,
1370 pub sensitivity: Option<serde_json::Value>,
1372 pub interpretation: serde_json::Value,
1374 pub created_at: DateTime<Utc>,
1376 pub metadata: Option<serde_json::Value>,
1378}
1379
1380impl ProbabilityUpdate {
1381 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 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 pub fn with_uncertainty(mut self, analysis: serde_json::Value) -> Self {
1423 self.uncertainty_analysis = Some(analysis);
1424 self
1425 }
1426
1427 pub fn with_sensitivity(mut self, sensitivity: serde_json::Value) -> Self {
1429 self.sensitivity = Some(sensitivity);
1430 self
1431 }
1432
1433 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
1435 self.metadata = Some(metadata);
1436 self
1437 }
1438}
1439
1440#[async_trait]
1445pub trait Storage: Send + Sync {
1446 async fn create_session(&self, session: &Session) -> StorageResult<()>;
1450 async fn get_session(&self, id: &str) -> StorageResult<Option<Session>>;
1452 async fn update_session(&self, session: &Session) -> StorageResult<()>;
1454 async fn delete_session(&self, id: &str) -> StorageResult<()>;
1456
1457 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 async fn create_thought(&self, thought: &Thought) -> StorageResult<()>;
1497 async fn get_thought(&self, id: &str) -> StorageResult<Option<Thought>>;
1499 async fn get_session_thoughts(&self, session_id: &str) -> StorageResult<Vec<Thought>>;
1501 async fn get_branch_thoughts(&self, branch_id: &str) -> StorageResult<Vec<Thought>>;
1503 async fn get_latest_thought(&self, session_id: &str) -> StorageResult<Option<Thought>>;
1505
1506 async fn create_branch(&self, branch: &Branch) -> StorageResult<()>;
1510 async fn get_branch(&self, id: &str) -> StorageResult<Option<Branch>>;
1512 async fn get_session_branches(&self, session_id: &str) -> StorageResult<Vec<Branch>>;
1514 async fn get_child_branches(&self, parent_id: &str) -> StorageResult<Vec<Branch>>;
1516 async fn update_branch(&self, branch: &Branch) -> StorageResult<()>;
1518 async fn delete_branch(&self, id: &str) -> StorageResult<()>;
1520
1521 async fn create_cross_ref(&self, cross_ref: &CrossRef) -> StorageResult<()>;
1525 async fn get_cross_refs_from(&self, branch_id: &str) -> StorageResult<Vec<CrossRef>>;
1527 async fn get_cross_refs_to(&self, branch_id: &str) -> StorageResult<Vec<CrossRef>>;
1529 async fn delete_cross_ref(&self, id: &str) -> StorageResult<()>;
1531
1532 async fn create_checkpoint(&self, checkpoint: &Checkpoint) -> StorageResult<()>;
1536 async fn get_checkpoint(&self, id: &str) -> StorageResult<Option<Checkpoint>>;
1538 async fn get_session_checkpoints(&self, session_id: &str) -> StorageResult<Vec<Checkpoint>>;
1540 async fn get_branch_checkpoints(&self, branch_id: &str) -> StorageResult<Vec<Checkpoint>>;
1542 async fn delete_checkpoint(&self, id: &str) -> StorageResult<()>;
1544
1545 async fn log_invocation(&self, invocation: &Invocation) -> StorageResult<()>;
1549
1550 async fn get_pipe_usage_summary(&self) -> StorageResult<Vec<PipeUsageSummary>>;
1554
1555 async fn get_pipe_summary(&self, pipe_name: &str) -> StorageResult<Option<PipeUsageSummary>>;
1559
1560 async fn get_invocations(&self, filter: MetricsFilter) -> StorageResult<Vec<Invocation>>;
1565
1566 async fn get_invocation_count(&self, pipe_name: Option<&str>) -> StorageResult<u64>;
1570
1571 async fn get_fallback_metrics(&self) -> StorageResult<FallbackMetricsSummary>;
1576
1577 async fn create_graph_node(&self, node: &GraphNode) -> StorageResult<()>;
1581 async fn get_graph_node(&self, id: &str) -> StorageResult<Option<GraphNode>>;
1583 async fn get_session_graph_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1585 async fn get_active_graph_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1587 async fn get_root_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1589 async fn get_terminal_nodes(&self, session_id: &str) -> StorageResult<Vec<GraphNode>>;
1591 async fn update_graph_node(&self, node: &GraphNode) -> StorageResult<()>;
1593 async fn delete_graph_node(&self, id: &str) -> StorageResult<()>;
1595
1596 async fn create_graph_edge(&self, edge: &GraphEdge) -> StorageResult<()>;
1600 async fn get_graph_edge(&self, id: &str) -> StorageResult<Option<GraphEdge>>;
1602 async fn get_edges_from(&self, node_id: &str) -> StorageResult<Vec<GraphEdge>>;
1604 async fn get_edges_to(&self, node_id: &str) -> StorageResult<Vec<GraphEdge>>;
1606 async fn get_session_edges(&self, session_id: &str) -> StorageResult<Vec<GraphEdge>>;
1608 async fn delete_graph_edge(&self, id: &str) -> StorageResult<()>;
1610
1611 async fn create_snapshot(&self, snapshot: &StateSnapshot) -> StorageResult<()>;
1615 async fn get_snapshot(&self, id: &str) -> StorageResult<Option<StateSnapshot>>;
1617 async fn get_session_snapshots(&self, session_id: &str) -> StorageResult<Vec<StateSnapshot>>;
1619 async fn get_latest_snapshot(&self, session_id: &str) -> StorageResult<Option<StateSnapshot>>;
1621 async fn delete_snapshot(&self, id: &str) -> StorageResult<()>;
1623
1624 async fn create_detection(&self, detection: &Detection) -> StorageResult<()>;
1628 async fn get_detection(&self, id: &str) -> StorageResult<Option<Detection>>;
1630 async fn get_session_detections(&self, session_id: &str) -> StorageResult<Vec<Detection>>;
1632 async fn get_thought_detections(&self, thought_id: &str) -> StorageResult<Vec<Detection>>;
1634 async fn get_detections_by_type(
1636 &self,
1637 detection_type: DetectionType,
1638 ) -> StorageResult<Vec<Detection>>;
1639 async fn get_session_detections_by_type(
1641 &self,
1642 session_id: &str,
1643 detection_type: DetectionType,
1644 ) -> StorageResult<Vec<Detection>>;
1645 async fn delete_detection(&self, id: &str) -> StorageResult<()>;
1647
1648 async fn create_decision(&self, decision: &Decision) -> StorageResult<()>;
1654
1655 async fn get_decision(&self, id: &str) -> StorageResult<Option<Decision>>;
1657
1658 async fn get_session_decisions(&self, session_id: &str) -> StorageResult<Vec<Decision>>;
1660
1661 async fn get_decisions_by_method(&self, method: &str) -> StorageResult<Vec<Decision>>;
1663
1664 async fn delete_decision(&self, id: &str) -> StorageResult<()>;
1666
1667 async fn create_perspective(&self, analysis: &PerspectiveAnalysis) -> StorageResult<()>;
1673
1674 async fn get_perspective(&self, id: &str) -> StorageResult<Option<PerspectiveAnalysis>>;
1676
1677 async fn get_session_perspectives(
1679 &self,
1680 session_id: &str,
1681 ) -> StorageResult<Vec<PerspectiveAnalysis>>;
1682
1683 async fn delete_perspective(&self, id: &str) -> StorageResult<()>;
1685
1686 async fn create_evidence_assessment(
1692 &self,
1693 assessment: &EvidenceAssessment,
1694 ) -> StorageResult<()>;
1695
1696 async fn get_evidence_assessment(&self, id: &str) -> StorageResult<Option<EvidenceAssessment>>;
1698
1699 async fn get_session_evidence_assessments(
1701 &self,
1702 session_id: &str,
1703 ) -> StorageResult<Vec<EvidenceAssessment>>;
1704
1705 async fn delete_evidence_assessment(&self, id: &str) -> StorageResult<()>;
1707
1708 async fn create_probability_update(&self, update: &ProbabilityUpdate) -> StorageResult<()>;
1714
1715 async fn get_probability_update(&self, id: &str) -> StorageResult<Option<ProbabilityUpdate>>;
1717
1718 async fn get_session_probability_updates(
1720 &self,
1721 session_id: &str,
1722 ) -> StorageResult<Vec<ProbabilityUpdate>>;
1723
1724 async fn get_hypothesis_updates(
1726 &self,
1727 session_id: &str,
1728 hypothesis: &str,
1729 ) -> StorageResult<Vec<ProbabilityUpdate>>;
1730
1731 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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}