1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use crate::crypto::{hash, Hash, PublicKey};
10use crate::error::{Error, Result};
11use crate::event::EventId;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct TraceId(pub [u8; 16]);
16
17impl TraceId {
18 pub fn generate() -> Self {
20 use rand::RngCore;
21 let mut bytes = [0u8; 16];
22 rand::thread_rng().fill_bytes(&mut bytes);
23 Self(bytes)
24 }
25
26 pub fn from_bytes(bytes: [u8; 16]) -> Self {
28 Self(bytes)
29 }
30
31 pub fn as_bytes(&self) -> &[u8; 16] {
33 &self.0
34 }
35
36 pub fn to_hex(&self) -> String {
38 hex::encode(self.0)
39 }
40
41 pub fn from_hex(s: &str) -> Result<Self> {
43 let bytes = hex::decode(s).map_err(|_| Error::invalid_input("invalid hex"))?;
44 if bytes.len() != 16 {
45 return Err(Error::invalid_input("trace ID must be 16 bytes"));
46 }
47 let mut arr = [0u8; 16];
48 arr.copy_from_slice(&bytes);
49 Ok(Self(arr))
50 }
51}
52
53impl std::fmt::Display for TraceId {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "{}", self.to_hex())
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum Priority {
63 Low,
65 Normal,
67 High,
69 Critical,
71}
72
73impl Default for Priority {
74 fn default() -> Self {
75 Self::Normal
76 }
77}
78
79impl std::fmt::Display for Priority {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Priority::Low => write!(f, "low"),
83 Priority::Normal => write!(f, "normal"),
84 Priority::High => write!(f, "high"),
85 Priority::Critical => write!(f, "critical"),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "type", rename_all = "snake_case")]
93pub enum GoalSource {
94 UserInstruction { event_id: EventId },
96 Derived { parent_goal_description: String },
98 System { policy: String },
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct Goal {
105 description: String,
107 structured: Option<serde_json::Value>,
109 source: GoalSource,
111 priority: Priority,
113}
114
115impl Goal {
116 pub fn from_user(description: impl Into<String>, event_id: EventId) -> Self {
118 Self {
119 description: description.into(),
120 structured: None,
121 source: GoalSource::UserInstruction { event_id },
122 priority: Priority::Normal,
123 }
124 }
125
126 pub fn derived(description: impl Into<String>, parent_description: impl Into<String>) -> Self {
128 Self {
129 description: description.into(),
130 structured: None,
131 source: GoalSource::Derived {
132 parent_goal_description: parent_description.into(),
133 },
134 priority: Priority::Normal,
135 }
136 }
137
138 pub fn system(description: impl Into<String>, policy: impl Into<String>) -> Self {
140 Self {
141 description: description.into(),
142 structured: None,
143 source: GoalSource::System {
144 policy: policy.into(),
145 },
146 priority: Priority::Normal,
147 }
148 }
149
150 pub fn with_structured(mut self, structured: serde_json::Value) -> Self {
152 self.structured = Some(structured);
153 self
154 }
155
156 pub fn with_priority(mut self, priority: Priority) -> Self {
158 self.priority = priority;
159 self
160 }
161
162 pub fn description(&self) -> &str {
164 &self.description
165 }
166
167 pub fn structured(&self) -> Option<&serde_json::Value> {
169 self.structured.as_ref()
170 }
171
172 pub fn source(&self) -> &GoalSource {
174 &self.source
175 }
176
177 pub fn priority(&self) -> Priority {
179 self.priority
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(tag = "type", rename_all = "snake_case")]
186pub enum StepAction {
187 Retrieve { query: String, source: String },
189 Analyze { subject: String, method: String },
191 ToolCall { tool: String, input_hash: Hash },
193 Delegate { agent: PublicKey, task: String },
195 Decide { decision: String },
197}
198
199impl StepAction {
200 pub fn retrieve(query: impl Into<String>, source: impl Into<String>) -> Self {
202 Self::Retrieve {
203 query: query.into(),
204 source: source.into(),
205 }
206 }
207
208 pub fn analyze(subject: impl Into<String>, method: impl Into<String>) -> Self {
210 Self::Analyze {
211 subject: subject.into(),
212 method: method.into(),
213 }
214 }
215
216 pub fn tool_call(tool: impl Into<String>, input_hash: Hash) -> Self {
218 Self::ToolCall {
219 tool: tool.into(),
220 input_hash,
221 }
222 }
223
224 pub fn delegate(agent: PublicKey, task: impl Into<String>) -> Self {
226 Self::Delegate {
227 agent,
228 task: task.into(),
229 }
230 }
231
232 pub fn decide(decision: impl Into<String>) -> Self {
234 Self::Decide {
235 decision: decision.into(),
236 }
237 }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct ReasoningStep {
243 sequence: u32,
245 thought: String,
247 action: Option<StepAction>,
249 observation: Option<String>,
251 timestamp: i64,
253}
254
255impl ReasoningStep {
256 pub fn new(sequence: u32, thought: impl Into<String>) -> Self {
258 Self {
259 sequence,
260 thought: thought.into(),
261 action: None,
262 observation: None,
263 timestamp: chrono::Utc::now().timestamp_millis(),
264 }
265 }
266
267 pub fn with_action(mut self, action: StepAction) -> Self {
269 self.action = Some(action);
270 self
271 }
272
273 pub fn with_observation(mut self, observation: impl Into<String>) -> Self {
275 self.observation = Some(observation.into());
276 self
277 }
278
279 pub fn with_timestamp(mut self, timestamp: i64) -> Self {
281 self.timestamp = timestamp;
282 self
283 }
284
285 pub fn sequence(&self) -> u32 {
287 self.sequence
288 }
289
290 pub fn thought(&self) -> &str {
292 &self.thought
293 }
294
295 pub fn action(&self) -> Option<&StepAction> {
297 self.action.as_ref()
298 }
299
300 pub fn observation(&self) -> Option<&str> {
302 self.observation.as_deref()
303 }
304
305 pub fn timestamp(&self) -> i64 {
307 self.timestamp
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct Decision {
314 action: String,
316 rationale: String,
318 expected_outcome: String,
320 success_criteria: Vec<String>,
322}
323
324impl Decision {
325 pub fn new(
327 action: impl Into<String>,
328 rationale: impl Into<String>,
329 expected_outcome: impl Into<String>,
330 ) -> Self {
331 Self {
332 action: action.into(),
333 rationale: rationale.into(),
334 expected_outcome: expected_outcome.into(),
335 success_criteria: Vec::new(),
336 }
337 }
338
339 pub fn with_criterion(mut self, criterion: impl Into<String>) -> Self {
341 self.success_criteria.push(criterion.into());
342 self
343 }
344
345 pub fn with_criteria(mut self, criteria: Vec<String>) -> Self {
347 self.success_criteria = criteria;
348 self
349 }
350
351 pub fn action(&self) -> &str {
353 &self.action
354 }
355
356 pub fn rationale(&self) -> &str {
358 &self.rationale
359 }
360
361 pub fn expected_outcome(&self) -> &str {
363 &self.expected_outcome
364 }
365
366 pub fn success_criteria(&self) -> &[String] {
368 &self.success_criteria
369 }
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct Confidence {
375 score: f64,
377 breakdown: HashMap<String, f64>,
379 uncertainties: Vec<String>,
381 would_help: Vec<String>,
383}
384
385impl Confidence {
386 pub fn new(score: f64) -> Self {
388 Self {
389 score: score.clamp(0.0, 1.0),
390 breakdown: HashMap::new(),
391 uncertainties: Vec::new(),
392 would_help: Vec::new(),
393 }
394 }
395
396 pub fn high() -> Self {
398 Self::new(0.9)
399 }
400
401 pub fn medium() -> Self {
403 Self::new(0.7)
404 }
405
406 pub fn low() -> Self {
408 Self::new(0.4)
409 }
410
411 pub fn with_factor(mut self, factor: impl Into<String>, score: f64) -> Self {
413 self.breakdown.insert(factor.into(), score.clamp(0.0, 1.0));
414 self
415 }
416
417 pub fn with_uncertainty(mut self, uncertainty: impl Into<String>) -> Self {
419 self.uncertainties.push(uncertainty.into());
420 self
421 }
422
423 pub fn with_would_help(mut self, help: impl Into<String>) -> Self {
425 self.would_help.push(help.into());
426 self
427 }
428
429 pub fn score(&self) -> f64 {
431 self.score
432 }
433
434 pub fn breakdown(&self) -> &HashMap<String, f64> {
436 &self.breakdown
437 }
438
439 pub fn uncertainties(&self) -> &[String] {
441 &self.uncertainties
442 }
443
444 pub fn would_help(&self) -> &[String] {
446 &self.would_help
447 }
448
449 pub fn should_reject(&self) -> bool {
451 self.score < 0.3
452 }
453
454 pub fn requires_approval(&self) -> bool {
456 self.score < 0.5
457 }
458
459 pub fn should_warn(&self) -> bool {
461 self.score < 0.7
462 }
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct Alternative {
468 description: String,
470 rejection_reason: String,
472 estimated_outcome: String,
474 confidence: f64,
476}
477
478impl Alternative {
479 pub fn new(
481 description: impl Into<String>,
482 rejection_reason: impl Into<String>,
483 estimated_outcome: impl Into<String>,
484 confidence: f64,
485 ) -> Self {
486 Self {
487 description: description.into(),
488 rejection_reason: rejection_reason.into(),
489 estimated_outcome: estimated_outcome.into(),
490 confidence: confidence.clamp(0.0, 1.0),
491 }
492 }
493
494 pub fn description(&self) -> &str {
496 &self.description
497 }
498
499 pub fn rejection_reason(&self) -> &str {
501 &self.rejection_reason
502 }
503
504 pub fn estimated_outcome(&self) -> &str {
506 &self.estimated_outcome
507 }
508
509 pub fn confidence(&self) -> f64 {
511 self.confidence
512 }
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct Factor {
518 description: String,
520 influence: f64,
522 evidence: Vec<String>,
524}
525
526impl Factor {
527 pub fn new(description: impl Into<String>, influence: f64) -> Self {
529 Self {
530 description: description.into(),
531 influence: influence.clamp(-1.0, 1.0),
532 evidence: Vec::new(),
533 }
534 }
535
536 pub fn positive(description: impl Into<String>, influence: f64) -> Self {
538 Self::new(description, influence.abs())
539 }
540
541 pub fn negative(description: impl Into<String>, influence: f64) -> Self {
543 Self::new(description, -influence.abs())
544 }
545
546 pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
548 self.evidence.push(evidence.into());
549 self
550 }
551
552 pub fn description(&self) -> &str {
554 &self.description
555 }
556
557 pub fn influence(&self) -> f64 {
559 self.influence
560 }
561
562 pub fn evidence(&self) -> &[String] {
564 &self.evidence
565 }
566
567 pub fn is_supportive(&self) -> bool {
569 self.influence > 0.0
570 }
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize)]
575pub struct ReasoningTrace {
576 id: TraceId,
578 goal: Goal,
580 steps: Vec<ReasoningStep>,
582 decision: Decision,
584 confidence: Confidence,
586 alternatives: Vec<Alternative>,
588 factors: Vec<Factor>,
590 trace_hash: Hash,
592}
593
594impl ReasoningTrace {
595 pub fn builder() -> ReasoningTraceBuilder {
597 ReasoningTraceBuilder::new()
598 }
599
600 pub fn id(&self) -> TraceId {
602 self.id
603 }
604
605 pub fn goal(&self) -> &Goal {
607 &self.goal
608 }
609
610 pub fn steps(&self) -> &[ReasoningStep] {
612 &self.steps
613 }
614
615 pub fn decision(&self) -> &Decision {
617 &self.decision
618 }
619
620 pub fn confidence(&self) -> &Confidence {
622 &self.confidence
623 }
624
625 pub fn alternatives(&self) -> &[Alternative] {
627 &self.alternatives
628 }
629
630 pub fn factors(&self) -> &[Factor] {
632 &self.factors
633 }
634
635 pub fn trace_hash(&self) -> Hash {
637 self.trace_hash
638 }
639
640 pub fn canonical_bytes(&self) -> Vec<u8> {
642 let mut data = Vec::new();
643 data.extend_from_slice(&self.id.0);
644
645 let goal_json = serde_json::to_vec(&self.goal).unwrap_or_default();
647 data.extend_from_slice(&goal_json);
648
649 for step in &self.steps {
651 let step_json = serde_json::to_vec(step).unwrap_or_default();
652 data.extend_from_slice(&step_json);
653 }
654
655 let decision_json = serde_json::to_vec(&self.decision).unwrap_or_default();
657 data.extend_from_slice(&decision_json);
658
659 data.extend_from_slice(&self.confidence.score.to_le_bytes());
661
662 for alt in &self.alternatives {
664 let alt_json = serde_json::to_vec(alt).unwrap_or_default();
665 data.extend_from_slice(&alt_json);
666 }
667
668 for factor in &self.factors {
670 let factor_json = serde_json::to_vec(factor).unwrap_or_default();
671 data.extend_from_slice(&factor_json);
672 }
673
674 data
675 }
676
677 pub fn verify_integrity(&self) -> bool {
679 let computed = hash(&self.canonical_bytes());
680 computed == self.trace_hash
681 }
682
683 pub fn is_complete(&self) -> bool {
685 if self.goal.description.is_empty() {
687 return false;
688 }
689
690 if self.steps.is_empty() {
692 return false;
693 }
694
695 if self.decision.action.is_empty() {
697 return false;
698 }
699
700 if !(0.0..=1.0).contains(&self.confidence.score) {
702 return false;
703 }
704
705 true
706 }
707}
708
709#[derive(Debug, Default)]
711pub struct ReasoningTraceBuilder {
712 id: Option<TraceId>,
713 goal: Option<Goal>,
714 steps: Vec<ReasoningStep>,
715 decision: Option<Decision>,
716 confidence: Option<Confidence>,
717 alternatives: Vec<Alternative>,
718 factors: Vec<Factor>,
719}
720
721impl ReasoningTraceBuilder {
722 pub fn new() -> Self {
724 Self::default()
725 }
726
727 pub fn id(mut self, id: TraceId) -> Self {
729 self.id = Some(id);
730 self
731 }
732
733 pub fn goal(mut self, goal: Goal) -> Self {
735 self.goal = Some(goal);
736 self
737 }
738
739 pub fn step(mut self, step: ReasoningStep) -> Self {
741 self.steps.push(step);
742 self
743 }
744
745 pub fn steps(mut self, steps: Vec<ReasoningStep>) -> Self {
747 self.steps = steps;
748 self
749 }
750
751 pub fn decision(mut self, decision: Decision) -> Self {
753 self.decision = Some(decision);
754 self
755 }
756
757 pub fn confidence(mut self, confidence: Confidence) -> Self {
759 self.confidence = Some(confidence);
760 self
761 }
762
763 pub fn alternative(mut self, alternative: Alternative) -> Self {
765 self.alternatives.push(alternative);
766 self
767 }
768
769 pub fn factor(mut self, factor: Factor) -> Self {
771 self.factors.push(factor);
772 self
773 }
774
775 pub fn build(self) -> Result<ReasoningTrace> {
777 let id = self.id.unwrap_or_else(TraceId::generate);
778
779 let goal = self
780 .goal
781 .ok_or_else(|| Error::invalid_input("goal is required"))?;
782
783 if self.steps.is_empty() {
784 return Err(Error::invalid_input(
785 "at least one reasoning step is required",
786 ));
787 }
788
789 let decision = self
790 .decision
791 .ok_or_else(|| Error::invalid_input("decision is required"))?;
792
793 let confidence = self.confidence.unwrap_or_else(Confidence::medium);
794
795 let mut trace = ReasoningTrace {
796 id,
797 goal,
798 steps: self.steps,
799 decision,
800 confidence,
801 alternatives: self.alternatives,
802 factors: self.factors,
803 trace_hash: Hash::from_bytes([0u8; 32]), };
805
806 trace.trace_hash = hash(&trace.canonical_bytes());
808
809 Ok(trace)
810 }
811}
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816
817 fn test_event_id() -> EventId {
818 EventId(hash(b"test-event"))
819 }
820
821 #[test]
824 fn trace_id_generates_unique() {
825 let id1 = TraceId::generate();
826 let id2 = TraceId::generate();
827 assert_ne!(id1, id2);
828 }
829
830 #[test]
831 fn trace_id_hex_roundtrip() {
832 let id = TraceId::generate();
833 let hex = id.to_hex();
834 let restored = TraceId::from_hex(&hex).unwrap();
835 assert_eq!(id, restored);
836 }
837
838 #[test]
841 fn priority_ordering() {
842 assert!(Priority::Low < Priority::Normal);
843 assert!(Priority::Normal < Priority::High);
844 assert!(Priority::High < Priority::Critical);
845 }
846
847 #[test]
850 fn goal_from_user() {
851 let goal = Goal::from_user("Complete the task", test_event_id());
852 assert_eq!(goal.description(), "Complete the task");
853 assert!(matches!(goal.source(), GoalSource::UserInstruction { .. }));
854 }
855
856 #[test]
857 fn goal_derived() {
858 let goal = Goal::derived("Sub-task", "Parent task");
859 assert_eq!(goal.description(), "Sub-task");
860 assert!(matches!(goal.source(), GoalSource::Derived { .. }));
861 }
862
863 #[test]
864 fn goal_with_priority() {
865 let goal =
866 Goal::from_user("Urgent task", test_event_id()).with_priority(Priority::Critical);
867 assert_eq!(goal.priority(), Priority::Critical);
868 }
869
870 #[test]
873 fn reasoning_step_basic() {
874 let step = ReasoningStep::new(1, "Analyzing the problem");
875 assert_eq!(step.sequence(), 1);
876 assert_eq!(step.thought(), "Analyzing the problem");
877 assert!(step.action().is_none());
878 }
879
880 #[test]
881 fn reasoning_step_with_action() {
882 let step = ReasoningStep::new(1, "Looking up information")
883 .with_action(StepAction::retrieve("user data", "database"));
884 assert!(step.action().is_some());
885 }
886
887 #[test]
888 fn reasoning_step_with_observation() {
889 let step = ReasoningStep::new(1, "Checking status").with_observation("Status is active");
890 assert_eq!(step.observation(), Some("Status is active"));
891 }
892
893 #[test]
896 fn decision_basic() {
897 let decision = Decision::new(
898 "Proceed with option A",
899 "It has the highest success probability",
900 "Task completed successfully",
901 );
902 assert_eq!(decision.action(), "Proceed with option A");
903 assert_eq!(
904 decision.rationale(),
905 "It has the highest success probability"
906 );
907 }
908
909 #[test]
910 fn decision_with_criteria() {
911 let decision = Decision::new("Execute", "Best option", "Success")
912 .with_criterion("Output matches expected format")
913 .with_criterion("No errors in logs");
914 assert_eq!(decision.success_criteria().len(), 2);
915 }
916
917 #[test]
920 fn confidence_clamped() {
921 let conf = Confidence::new(1.5);
922 assert_eq!(conf.score(), 1.0);
923
924 let conf2 = Confidence::new(-0.5);
925 assert_eq!(conf2.score(), 0.0);
926 }
927
928 #[test]
929 fn confidence_thresholds() {
930 let low = Confidence::new(0.2);
931 assert!(low.should_reject());
932 assert!(low.requires_approval());
933 assert!(low.should_warn());
934
935 let medium = Confidence::new(0.6);
936 assert!(!medium.should_reject());
937 assert!(!medium.requires_approval());
938 assert!(medium.should_warn());
939
940 let high = Confidence::new(0.8);
941 assert!(!high.should_reject());
942 assert!(!high.requires_approval());
943 assert!(!high.should_warn());
944 }
945
946 #[test]
947 fn confidence_with_breakdown() {
948 let conf = Confidence::medium()
949 .with_factor("data_quality", 0.8)
950 .with_factor("model_accuracy", 0.6)
951 .with_uncertainty("Limited training data");
952
953 assert_eq!(conf.breakdown().len(), 2);
954 assert_eq!(conf.uncertainties().len(), 1);
955 }
956
957 #[test]
960 fn alternative_basic() {
961 let alt = Alternative::new(
962 "Option B",
963 "Higher cost",
964 "Same result but more expensive",
965 0.6,
966 );
967 assert_eq!(alt.description(), "Option B");
968 assert_eq!(alt.rejection_reason(), "Higher cost");
969 }
970
971 #[test]
974 fn factor_positive() {
975 let factor = Factor::positive("Strong evidence", 0.8);
976 assert!(factor.is_supportive());
977 assert_eq!(factor.influence(), 0.8);
978 }
979
980 #[test]
981 fn factor_negative() {
982 let factor = Factor::negative("Risk of failure", 0.3);
983 assert!(!factor.is_supportive());
984 assert_eq!(factor.influence(), -0.3);
985 }
986
987 #[test]
988 fn factor_with_evidence() {
989 let factor = Factor::positive("Proven approach", 0.9)
990 .with_evidence("Study A shows 95% success rate")
991 .with_evidence("Historical data confirms");
992 assert_eq!(factor.evidence().len(), 2);
993 }
994
995 #[test]
998 fn reasoning_trace_requires_goal() {
999 let result = ReasoningTrace::builder()
1000 .step(ReasoningStep::new(1, "Thinking"))
1001 .decision(Decision::new("Do it", "Because", "Success"))
1002 .build();
1003 assert!(result.is_err());
1004 }
1005
1006 #[test]
1007 fn reasoning_trace_requires_steps() {
1008 let result = ReasoningTrace::builder()
1009 .goal(Goal::from_user("Task", test_event_id()))
1010 .decision(Decision::new("Do it", "Because", "Success"))
1011 .build();
1012 assert!(result.is_err());
1013 }
1014
1015 #[test]
1016 fn reasoning_trace_requires_decision() {
1017 let result = ReasoningTrace::builder()
1018 .goal(Goal::from_user("Task", test_event_id()))
1019 .step(ReasoningStep::new(1, "Thinking"))
1020 .build();
1021 assert!(result.is_err());
1022 }
1023
1024 #[test]
1025 fn reasoning_trace_complete() {
1026 let trace = ReasoningTrace::builder()
1027 .goal(Goal::from_user("Complete the task", test_event_id()))
1028 .step(ReasoningStep::new(1, "Analyzing requirements"))
1029 .step(ReasoningStep::new(2, "Evaluating options"))
1030 .decision(Decision::new(
1031 "Use approach A",
1032 "Most efficient",
1033 "Task completed",
1034 ))
1035 .confidence(Confidence::high())
1036 .alternative(Alternative::new("Approach B", "Slower", "Same result", 0.7))
1037 .factor(Factor::positive("Clear requirements", 0.9))
1038 .build()
1039 .unwrap();
1040
1041 assert!(trace.is_complete());
1042 assert_eq!(trace.steps().len(), 2);
1043 assert_eq!(trace.alternatives().len(), 1);
1044 assert_eq!(trace.factors().len(), 1);
1045 }
1046
1047 #[test]
1048 fn reasoning_trace_hash_computed() {
1049 let trace = ReasoningTrace::builder()
1050 .goal(Goal::from_user("Task", test_event_id()))
1051 .step(ReasoningStep::new(1, "Thinking"))
1052 .decision(Decision::new("Do it", "Because", "Success"))
1053 .build()
1054 .unwrap();
1055
1056 assert_ne!(trace.trace_hash().as_bytes(), &[0u8; 32]);
1058 }
1059
1060 #[test]
1061 fn reasoning_trace_integrity_verification() {
1062 let trace = ReasoningTrace::builder()
1063 .goal(Goal::from_user("Task", test_event_id()))
1064 .step(ReasoningStep::new(1, "Thinking"))
1065 .decision(Decision::new("Do it", "Because", "Success"))
1066 .build()
1067 .unwrap();
1068
1069 assert!(trace.verify_integrity());
1070 }
1071
1072 #[test]
1073 fn reasoning_trace_tamper_detected() {
1074 let mut trace = ReasoningTrace::builder()
1075 .goal(Goal::from_user("Task", test_event_id()))
1076 .step(ReasoningStep::new(1, "Thinking"))
1077 .decision(Decision::new("Do it", "Because", "Success"))
1078 .build()
1079 .unwrap();
1080
1081 trace.decision = Decision::new("Do something else", "Changed", "Different");
1083
1084 assert!(!trace.verify_integrity());
1086 }
1087
1088 #[test]
1091 fn step_action_variants() {
1092 let retrieve = StepAction::retrieve("query", "db");
1093 assert!(matches!(retrieve, StepAction::Retrieve { .. }));
1094
1095 let analyze = StepAction::analyze("data", "statistical");
1096 assert!(matches!(analyze, StepAction::Analyze { .. }));
1097
1098 let tool = StepAction::tool_call("bash", hash(b"input"));
1099 assert!(matches!(tool, StepAction::ToolCall { .. }));
1100
1101 let decide = StepAction::decide("Go ahead");
1102 assert!(matches!(decide, StepAction::Decide { .. }));
1103 }
1104}