1use super::ids::{ArtifactId, ExecutionId, ParentLink, StepId, StepType, TenantId, UserId};
15use serde::{Deserialize, Serialize};
16use svix_ksuid::{Ksuid, KsuidLike};
17
18fn new_event_id() -> String {
20 format!("evt_{}", Ksuid::new(None, None))
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum ExecutionEventType {
33 ExecutionStart,
35 ExecutionEnd,
36 ExecutionFailed,
37 ExecutionCancelled,
38
39 StepStart,
41 StepEnd,
42 StepFailed,
43 StepDiscovered,
44
45 ArtifactCreated,
47
48 StateSnapshot,
50
51 DecisionMade,
53
54 ControlPause,
56 ControlResume,
57 ControlCancel,
58
59 InboxMessage,
61
62 ToolCallStart,
64 ToolCallEnd,
65
66 CheckpointSaved,
68 GoalEvaluated,
69
70 LlmCallStart,
72 LlmCallEnd,
73 LlmCallFailed,
74
75 TokenUsageRecorded,
77
78 MemoryRecalled,
80 MemoryStored,
81
82 GuardrailEvaluated,
84
85 ReasoningTrace,
87 ContextSnapshot,
88
89 FeedbackReceived,
91}
92
93impl ExecutionEventType {
94 pub fn as_str(&self) -> &'static str {
96 match self {
97 Self::ExecutionStart => "execution.start",
98 Self::ExecutionEnd => "execution.end",
99 Self::ExecutionFailed => "execution.failed",
100 Self::ExecutionCancelled => "execution.cancelled",
101 Self::StepStart => "step.start",
102 Self::StepEnd => "step.end",
103 Self::StepFailed => "step.failed",
104 Self::StepDiscovered => "step.discovered",
105 Self::ArtifactCreated => "artifact.created",
106 Self::StateSnapshot => "state.snapshot",
107 Self::DecisionMade => "decision.made",
108 Self::ControlPause => "control.pause",
109 Self::ControlResume => "control.resume",
110 Self::ControlCancel => "control.cancel",
111 Self::InboxMessage => "inbox.message",
112 Self::ToolCallStart => "tool.start",
113 Self::ToolCallEnd => "tool.end",
114 Self::CheckpointSaved => "state.checkpoint",
115 Self::GoalEvaluated => "goal.evaluated",
116 Self::LlmCallStart => "llm.call.start",
117 Self::LlmCallEnd => "llm.call.end",
118 Self::LlmCallFailed => "llm.call.failed",
119 Self::TokenUsageRecorded => "token.usage",
120 Self::MemoryRecalled => "memory.recalled",
121 Self::MemoryStored => "memory.stored",
122 Self::GuardrailEvaluated => "guardrail.evaluated",
123 Self::ReasoningTrace => "reasoning.trace",
124 Self::ContextSnapshot => "context.snapshot",
125 Self::FeedbackReceived => "feedback.received",
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct ExecutionContext {
139 pub execution_id: ExecutionId,
141
142 pub step_id: Option<StepId>,
144
145 pub artifact_id: Option<ArtifactId>,
147
148 pub parent: Option<ParentLink>,
150
151 pub tenant_id: Option<TenantId>,
153 pub user_id: Option<UserId>,
154}
155
156impl ExecutionContext {
157 pub fn new(execution_id: ExecutionId) -> Self {
159 Self {
160 execution_id,
161 step_id: None,
162 artifact_id: None,
163 parent: None,
164 tenant_id: None,
165 user_id: None,
166 }
167 }
168
169 pub fn with_step(mut self, step_id: StepId) -> Self {
171 self.step_id = Some(step_id);
172 self
173 }
174
175 pub fn with_artifact(mut self, artifact_id: ArtifactId) -> Self {
177 self.artifact_id = Some(artifact_id);
178 self
179 }
180
181 pub fn with_parent(mut self, parent: ParentLink) -> Self {
183 self.parent = Some(parent);
184 self
185 }
186
187 pub fn with_tenant(mut self, tenant_id: TenantId, user_id: Option<UserId>) -> Self {
189 self.tenant_id = Some(tenant_id);
190 self.user_id = user_id;
191 self
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ExecutionEvent {
204 pub event_id: String,
206
207 pub event_type: ExecutionEventType,
209
210 pub context: ExecutionContext,
212
213 pub timestamp: chrono::DateTime<chrono::Utc>,
215
216 pub duration_ms: Option<u64>,
218
219 pub payload: Option<serde_json::Value>,
221}
222
223impl ExecutionEvent {
224 pub fn new(event_type: ExecutionEventType, context: ExecutionContext) -> Self {
226 Self {
227 event_id: new_event_id(),
228 event_type,
229 context,
230 timestamp: chrono::Utc::now(),
231 duration_ms: None,
232 payload: None,
233 }
234 }
235
236 pub fn with_duration(mut self, ms: u64) -> Self {
238 self.duration_ms = Some(ms);
239 self
240 }
241
242 pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
244 self.payload = Some(payload);
245 self
246 }
247
248 pub fn execution_start(execution_id: ExecutionId, parent: Option<ParentLink>) -> Self {
252 let mut ctx = ExecutionContext::new(execution_id);
253 if let Some(p) = parent {
254 ctx = ctx.with_parent(p);
255 }
256 Self::new(ExecutionEventType::ExecutionStart, ctx)
257 }
258
259 pub fn execution_end(execution_id: ExecutionId, duration_ms: Option<u64>) -> Self {
261 let ctx = ExecutionContext::new(execution_id);
262 let mut event = Self::new(ExecutionEventType::ExecutionEnd, ctx);
263 event.duration_ms = duration_ms;
264 event
265 }
266
267 pub fn step_start(
269 execution_id: ExecutionId,
270 step_id: StepId,
271 step_type: StepType,
272 name: &str,
273 ) -> Self {
274 let ctx = ExecutionContext::new(execution_id).with_step(step_id);
275 Self::new(ExecutionEventType::StepStart, ctx).with_payload(serde_json::json!({
276 "step_type": step_type.to_string(),
277 "name": name,
278 }))
279 }
280
281 pub fn step_end(execution_id: ExecutionId, step_id: StepId, duration_ms: u64) -> Self {
283 let ctx = ExecutionContext::new(execution_id).with_step(step_id);
284 Self::new(ExecutionEventType::StepEnd, ctx).with_duration(duration_ms)
285 }
286
287 pub fn artifact_created(
289 execution_id: ExecutionId,
290 step_id: StepId,
291 artifact_id: ArtifactId,
292 artifact_type: &str,
293 ) -> Self {
294 let ctx = ExecutionContext::new(execution_id)
295 .with_step(step_id)
296 .with_artifact(artifact_id);
297 Self::new(ExecutionEventType::ArtifactCreated, ctx).with_payload(serde_json::json!({
298 "artifact_type": artifact_type,
299 }))
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum DecisionType {
313 Routing,
315 Branch,
317 Approval,
319 Escalation,
321 Retry,
323 Fallback,
325 PolicyEvaluation,
327 ToolSelection,
329 Rejection,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct DecisionInput {
336 pub facts: Vec<String>,
338 pub constraints: Vec<String>,
340 pub evidence_ids: Vec<String>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct DecisionAlternative {
347 pub option: String,
349 pub score: f64,
351 pub rejected_reason: String,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ModelContext {
358 pub provider: String,
360 pub model: String,
362 pub version: Option<String>,
364 pub temperature: Option<f64>,
366 pub max_tokens: Option<u32>,
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct DecisionRecord {
376 pub decision_id: String,
378
379 pub execution_id: ExecutionId,
381 pub step_id: Option<StepId>,
382
383 pub decision_type: DecisionType,
385 pub timestamp: chrono::DateTime<chrono::Utc>,
386
387 pub outcome: String,
389 pub confidence: f64,
391
392 pub inputs: DecisionInput,
394
395 pub alternatives: Option<Vec<DecisionAlternative>>,
397
398 pub model_context: Option<ModelContext>,
400
401 pub reasoning: Option<String>,
403}
404
405impl DecisionRecord {
406 pub fn new(
408 decision_type: DecisionType,
409 execution_id: ExecutionId,
410 outcome: impl Into<String>,
411 confidence: f64,
412 inputs: DecisionInput,
413 ) -> Self {
414 Self {
415 decision_id: format!("dec_{}", Ksuid::new(None, None)),
416 execution_id,
417 step_id: None,
418 decision_type,
419 timestamp: chrono::Utc::now(),
420 outcome: outcome.into(),
421 confidence,
422 inputs,
423 alternatives: None,
424 model_context: None,
425 reasoning: None,
426 }
427 }
428
429 pub fn with_step(mut self, step_id: StepId) -> Self {
431 self.step_id = Some(step_id);
432 self
433 }
434
435 pub fn with_alternatives(mut self, alternatives: Vec<DecisionAlternative>) -> Self {
437 self.alternatives = Some(alternatives);
438 self
439 }
440
441 pub fn with_model_context(mut self, model_context: ModelContext) -> Self {
443 self.model_context = Some(model_context);
444 self
445 }
446
447 pub fn with_reasoning(mut self, reasoning: impl Into<String>) -> Self {
449 self.reasoning = Some(reasoning.into());
450 self
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
460#[serde(rename_all = "snake_case")]
461pub enum ControlActor {
462 System,
464 User,
466 Agent,
468 PolicyEngine,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(rename_all = "snake_case")]
477pub enum ControlAction {
478 Stop,
480 Pause,
482 Resume,
484 Cancel,
486 Approve,
488 Deny,
490 Escalate,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(rename_all = "snake_case")]
497pub enum ControlOutcome {
498 Allowed,
500 Denied,
502 Modified,
504 Escalated,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct ControlEvent {
514 pub event_id: String,
516
517 pub execution_id: ExecutionId,
519 pub step_id: Option<StepId>,
520
521 pub timestamp: chrono::DateTime<chrono::Utc>,
523 pub actor: ControlActor,
524 pub action: ControlAction,
525
526 pub reason: String,
528
529 pub outcome: ControlOutcome,
531
532 pub actor_id: Option<String>,
534}
535
536impl ControlEvent {
537 pub fn new(
539 actor: ControlActor,
540 action: ControlAction,
541 execution_id: ExecutionId,
542 reason: impl Into<String>,
543 outcome: ControlOutcome,
544 ) -> Self {
545 Self {
546 event_id: format!("ctrl_{}", Ksuid::new(None, None)),
547 execution_id,
548 step_id: None,
549 timestamp: chrono::Utc::now(),
550 actor,
551 action,
552 reason: reason.into(),
553 outcome,
554 actor_id: None,
555 }
556 }
557
558 pub fn with_step(mut self, step_id: StepId) -> Self {
560 self.step_id = Some(step_id);
561 self
562 }
563
564 pub fn with_actor_id(mut self, actor_id: impl Into<String>) -> Self {
566 self.actor_id = Some(actor_id.into());
567 self
568 }
569
570 pub fn pause(
574 execution_id: ExecutionId,
575 actor: ControlActor,
576 reason: impl Into<String>,
577 ) -> Self {
578 Self::new(
579 actor,
580 ControlAction::Pause,
581 execution_id,
582 reason,
583 ControlOutcome::Allowed,
584 )
585 }
586
587 pub fn resume(
589 execution_id: ExecutionId,
590 actor: ControlActor,
591 reason: impl Into<String>,
592 ) -> Self {
593 Self::new(
594 actor,
595 ControlAction::Resume,
596 execution_id,
597 reason,
598 ControlOutcome::Allowed,
599 )
600 }
601
602 pub fn cancel(
604 execution_id: ExecutionId,
605 actor: ControlActor,
606 reason: impl Into<String>,
607 ) -> Self {
608 Self::new(
609 actor,
610 ControlAction::Cancel,
611 execution_id,
612 reason,
613 ControlOutcome::Allowed,
614 )
615 }
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize)]
624pub struct Event {
625 pub id: String,
627 pub run_id: ExecutionId,
629 pub node_id: Option<StepId>,
631 pub author: String,
633 pub timestamp: chrono::DateTime<chrono::Utc>,
635 pub content: Option<String>,
637 pub is_final: bool,
639}
640
641impl Event {
642 pub fn new(run_id: ExecutionId, author: impl Into<String>) -> Self {
643 Self {
644 id: new_event_id(),
645 run_id,
646 node_id: None,
647 author: author.into(),
648 timestamp: chrono::Utc::now(),
649 content: None,
650 is_final: false,
651 }
652 }
653
654 pub fn with_content(mut self, content: impl Into<String>) -> Self {
655 self.content = Some(content.into());
656 self
657 }
658
659 pub fn with_node(mut self, node_id: StepId) -> Self {
660 self.node_id = Some(node_id);
661 self
662 }
663
664 pub fn final_response(mut self) -> Self {
665 self.is_final = true;
666 self
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673
674 #[test]
679 fn test_execution_event_type_as_str_execution_start() {
680 assert_eq!(
681 ExecutionEventType::ExecutionStart.as_str(),
682 "execution.start"
683 );
684 }
685
686 #[test]
687 fn test_execution_event_type_as_str_execution_end() {
688 assert_eq!(ExecutionEventType::ExecutionEnd.as_str(), "execution.end");
689 }
690
691 #[test]
692 fn test_execution_event_type_as_str_execution_failed() {
693 assert_eq!(
694 ExecutionEventType::ExecutionFailed.as_str(),
695 "execution.failed"
696 );
697 }
698
699 #[test]
700 fn test_execution_event_type_as_str_execution_cancelled() {
701 assert_eq!(
702 ExecutionEventType::ExecutionCancelled.as_str(),
703 "execution.cancelled"
704 );
705 }
706
707 #[test]
708 fn test_execution_event_type_as_str_step_start() {
709 assert_eq!(ExecutionEventType::StepStart.as_str(), "step.start");
710 }
711
712 #[test]
713 fn test_execution_event_type_as_str_step_end() {
714 assert_eq!(ExecutionEventType::StepEnd.as_str(), "step.end");
715 }
716
717 #[test]
718 fn test_execution_event_type_as_str_step_failed() {
719 assert_eq!(ExecutionEventType::StepFailed.as_str(), "step.failed");
720 }
721
722 #[test]
723 fn test_execution_event_type_as_str_artifact_created() {
724 assert_eq!(
725 ExecutionEventType::ArtifactCreated.as_str(),
726 "artifact.created"
727 );
728 }
729
730 #[test]
731 fn test_execution_event_type_as_str_state_snapshot() {
732 assert_eq!(ExecutionEventType::StateSnapshot.as_str(), "state.snapshot");
733 }
734
735 #[test]
736 fn test_execution_event_type_as_str_decision_made() {
737 assert_eq!(ExecutionEventType::DecisionMade.as_str(), "decision.made");
738 }
739
740 #[test]
741 fn test_execution_event_type_as_str_control_pause() {
742 assert_eq!(ExecutionEventType::ControlPause.as_str(), "control.pause");
743 }
744
745 #[test]
746 fn test_execution_event_type_as_str_control_resume() {
747 assert_eq!(ExecutionEventType::ControlResume.as_str(), "control.resume");
748 }
749
750 #[test]
751 fn test_execution_event_type_as_str_control_cancel() {
752 assert_eq!(ExecutionEventType::ControlCancel.as_str(), "control.cancel");
753 }
754
755 #[test]
756 fn test_execution_event_type_serde() {
757 let event_type = ExecutionEventType::ExecutionStart;
758 let json = serde_json::to_string(&event_type).unwrap();
759 let parsed: ExecutionEventType = serde_json::from_str(&json).unwrap();
760 assert_eq!(event_type, parsed);
761 }
762
763 #[test]
764 fn test_execution_event_type_equality() {
765 assert_eq!(ExecutionEventType::StepStart, ExecutionEventType::StepStart);
766 assert_ne!(ExecutionEventType::StepStart, ExecutionEventType::StepEnd);
767 }
768
769 #[test]
774 fn test_execution_context_new() {
775 let exec_id = ExecutionId::from_string("exec_test");
776 let ctx = ExecutionContext::new(exec_id.clone());
777 assert_eq!(ctx.execution_id.as_str(), "exec_test");
778 assert!(ctx.step_id.is_none());
779 assert!(ctx.artifact_id.is_none());
780 assert!(ctx.parent.is_none());
781 assert!(ctx.tenant_id.is_none());
782 assert!(ctx.user_id.is_none());
783 }
784
785 #[test]
786 fn test_execution_context_with_step() {
787 let exec_id = ExecutionId::from_string("exec_test");
788 let step_id = StepId::from_string("step_test");
789 let ctx = ExecutionContext::new(exec_id).with_step(step_id.clone());
790 assert!(ctx.step_id.is_some());
791 assert_eq!(ctx.step_id.unwrap().as_str(), "step_test");
792 }
793
794 #[test]
795 fn test_execution_context_with_artifact() {
796 let exec_id = ExecutionId::from_string("exec_test");
797 let artifact_id = ArtifactId::from_string("artifact_test");
798 let ctx = ExecutionContext::new(exec_id).with_artifact(artifact_id);
799 assert!(ctx.artifact_id.is_some());
800 assert_eq!(ctx.artifact_id.unwrap().as_str(), "artifact_test");
801 }
802
803 #[test]
804 fn test_execution_context_with_parent() {
805 let exec_id = ExecutionId::from_string("exec_test");
806 let parent = ParentLink::from_user_message("msg_123");
807 let ctx = ExecutionContext::new(exec_id).with_parent(parent);
808 assert!(ctx.parent.is_some());
809 assert_eq!(ctx.parent.unwrap().parent_id, "msg_123");
810 }
811
812 #[test]
813 fn test_execution_context_with_tenant() {
814 let exec_id = ExecutionId::from_string("exec_test");
815 let tenant_id = TenantId::from_string("tenant_test");
816 let user_id = UserId::from_string("user_test");
817 let ctx = ExecutionContext::new(exec_id).with_tenant(tenant_id, Some(user_id));
818 assert!(ctx.tenant_id.is_some());
819 assert!(ctx.user_id.is_some());
820 assert_eq!(ctx.tenant_id.unwrap().as_str(), "tenant_test");
821 assert_eq!(ctx.user_id.unwrap().as_str(), "user_test");
822 }
823
824 #[test]
825 fn test_execution_context_builder_chain() {
826 let exec_id = ExecutionId::from_string("exec_chain");
827 let step_id = StepId::from_string("step_chain");
828 let artifact_id = ArtifactId::from_string("artifact_chain");
829 let parent = ParentLink::system();
830 let tenant_id = TenantId::from_string("tenant_chain");
831
832 let ctx = ExecutionContext::new(exec_id)
833 .with_step(step_id)
834 .with_artifact(artifact_id)
835 .with_parent(parent)
836 .with_tenant(tenant_id, None);
837
838 assert!(ctx.step_id.is_some());
839 assert!(ctx.artifact_id.is_some());
840 assert!(ctx.parent.is_some());
841 assert!(ctx.tenant_id.is_some());
842 assert!(ctx.user_id.is_none());
843 }
844
845 #[test]
846 fn test_execution_context_serde() {
847 let exec_id = ExecutionId::from_string("exec_serde");
848 let ctx = ExecutionContext::new(exec_id);
849 let json = serde_json::to_string(&ctx).unwrap();
850 let parsed: ExecutionContext = serde_json::from_str(&json).unwrap();
851 assert_eq!(ctx.execution_id.as_str(), parsed.execution_id.as_str());
852 }
853
854 #[test]
859 fn test_execution_event_new() {
860 let ctx = ExecutionContext::new(ExecutionId::from_string("exec_test"));
861 let event = ExecutionEvent::new(ExecutionEventType::ExecutionStart, ctx);
862 assert!(event.event_id.starts_with("evt_"));
863 assert_eq!(event.event_type, ExecutionEventType::ExecutionStart);
864 assert!(event.duration_ms.is_none());
865 assert!(event.payload.is_none());
866 }
867
868 #[test]
869 fn test_execution_event_with_duration() {
870 let ctx = ExecutionContext::new(ExecutionId::from_string("exec_test"));
871 let event = ExecutionEvent::new(ExecutionEventType::StepEnd, ctx).with_duration(1500);
872 assert_eq!(event.duration_ms, Some(1500));
873 }
874
875 #[test]
876 fn test_execution_event_with_payload() {
877 let ctx = ExecutionContext::new(ExecutionId::from_string("exec_test"));
878 let payload = serde_json::json!({"key": "value"});
879 let event =
880 ExecutionEvent::new(ExecutionEventType::StepStart, ctx).with_payload(payload.clone());
881 assert!(event.payload.is_some());
882 assert_eq!(event.payload.unwrap(), payload);
883 }
884
885 #[test]
886 fn test_execution_event_execution_start() {
887 let exec_id = ExecutionId::from_string("exec_start");
888 let event = ExecutionEvent::execution_start(exec_id, None);
889 assert_eq!(event.event_type, ExecutionEventType::ExecutionStart);
890 assert!(event.context.parent.is_none());
891 }
892
893 #[test]
894 fn test_execution_event_execution_start_with_parent() {
895 let exec_id = ExecutionId::from_string("exec_start");
896 let parent = ParentLink::from_user_message("msg_trigger");
897 let event = ExecutionEvent::execution_start(exec_id, Some(parent));
898 assert_eq!(event.event_type, ExecutionEventType::ExecutionStart);
899 assert!(event.context.parent.is_some());
900 }
901
902 #[test]
903 fn test_execution_event_execution_end() {
904 let exec_id = ExecutionId::from_string("exec_end");
905 let event = ExecutionEvent::execution_end(exec_id, Some(5000));
906 assert_eq!(event.event_type, ExecutionEventType::ExecutionEnd);
907 assert_eq!(event.duration_ms, Some(5000));
908 }
909
910 #[test]
911 fn test_execution_event_step_start() {
912 let exec_id = ExecutionId::from_string("exec_step");
913 let step_id = StepId::from_string("step_start");
914 let event = ExecutionEvent::step_start(exec_id, step_id, StepType::LlmNode, "test_step");
915 assert_eq!(event.event_type, ExecutionEventType::StepStart);
916 assert!(event.payload.is_some());
917 let payload = event.payload.unwrap();
918 assert_eq!(payload["name"], "test_step");
919 }
920
921 #[test]
922 fn test_execution_event_step_end() {
923 let exec_id = ExecutionId::from_string("exec_step");
924 let step_id = StepId::from_string("step_end");
925 let event = ExecutionEvent::step_end(exec_id, step_id, 1000);
926 assert_eq!(event.event_type, ExecutionEventType::StepEnd);
927 assert_eq!(event.duration_ms, Some(1000));
928 }
929
930 #[test]
931 fn test_execution_event_artifact_created() {
932 let exec_id = ExecutionId::from_string("exec_artifact");
933 let step_id = StepId::from_string("step_artifact");
934 let artifact_id = ArtifactId::from_string("artifact_created");
935 let event = ExecutionEvent::artifact_created(exec_id, step_id, artifact_id, "code");
936 assert_eq!(event.event_type, ExecutionEventType::ArtifactCreated);
937 assert!(event.context.artifact_id.is_some());
938 assert!(event.payload.is_some());
939 assert_eq!(event.payload.unwrap()["artifact_type"], "code");
940 }
941
942 #[test]
943 fn test_execution_event_serde() {
944 let ctx = ExecutionContext::new(ExecutionId::from_string("exec_serde"));
945 let event = ExecutionEvent::new(ExecutionEventType::ExecutionStart, ctx);
946 let json = serde_json::to_string(&event).unwrap();
947 let parsed: ExecutionEvent = serde_json::from_str(&json).unwrap();
948 assert_eq!(event.event_type, parsed.event_type);
949 }
950
951 #[test]
956 fn test_decision_type_variants() {
957 let variants = vec![
958 DecisionType::Routing,
959 DecisionType::Branch,
960 DecisionType::Approval,
961 DecisionType::Escalation,
962 DecisionType::Retry,
963 DecisionType::Fallback,
964 DecisionType::PolicyEvaluation,
965 DecisionType::ToolSelection,
966 DecisionType::Rejection,
967 ];
968 for variant in variants {
969 let json = serde_json::to_string(&variant).unwrap();
970 let parsed: DecisionType = serde_json::from_str(&json).unwrap();
971 assert_eq!(variant, parsed);
972 }
973 }
974
975 #[test]
976 fn test_decision_type_equality() {
977 assert_eq!(DecisionType::Routing, DecisionType::Routing);
978 assert_ne!(DecisionType::Routing, DecisionType::Branch);
979 }
980
981 #[test]
986 fn test_decision_input_creation() {
987 let input = DecisionInput {
988 facts: vec!["fact1".to_string(), "fact2".to_string()],
989 constraints: vec!["constraint1".to_string()],
990 evidence_ids: vec!["ev_123".to_string()],
991 };
992 assert_eq!(input.facts.len(), 2);
993 assert_eq!(input.constraints.len(), 1);
994 assert_eq!(input.evidence_ids.len(), 1);
995 }
996
997 #[test]
998 fn test_decision_input_serde() {
999 let input = DecisionInput {
1000 facts: vec!["fact1".to_string()],
1001 constraints: vec![],
1002 evidence_ids: vec![],
1003 };
1004 let json = serde_json::to_string(&input).unwrap();
1005 let parsed: DecisionInput = serde_json::from_str(&json).unwrap();
1006 assert_eq!(input.facts, parsed.facts);
1007 }
1008
1009 #[test]
1014 fn test_decision_alternative_creation() {
1015 let alt = DecisionAlternative {
1016 option: "option_b".to_string(),
1017 score: 0.75,
1018 rejected_reason: "Lower confidence".to_string(),
1019 };
1020 assert_eq!(alt.option, "option_b");
1021 assert_eq!(alt.score, 0.75);
1022 }
1023
1024 #[test]
1025 fn test_decision_alternative_serde() {
1026 let alt = DecisionAlternative {
1027 option: "alt".to_string(),
1028 score: 0.5,
1029 rejected_reason: "reason".to_string(),
1030 };
1031 let json = serde_json::to_string(&alt).unwrap();
1032 let parsed: DecisionAlternative = serde_json::from_str(&json).unwrap();
1033 assert_eq!(alt.option, parsed.option);
1034 assert_eq!(alt.score, parsed.score);
1035 }
1036
1037 #[test]
1042 fn test_model_context_creation() {
1043 let ctx = ModelContext {
1044 provider: "anthropic".to_string(),
1045 model: "claude-3-opus".to_string(),
1046 version: Some("2024".to_string()),
1047 temperature: Some(0.7),
1048 max_tokens: Some(1000),
1049 };
1050 assert_eq!(ctx.provider, "anthropic");
1051 assert_eq!(ctx.model, "claude-3-opus");
1052 assert!(ctx.temperature.is_some());
1053 }
1054
1055 #[test]
1056 fn test_model_context_minimal() {
1057 let ctx = ModelContext {
1058 provider: "openai".to_string(),
1059 model: "gpt-4".to_string(),
1060 version: None,
1061 temperature: None,
1062 max_tokens: None,
1063 };
1064 assert!(ctx.version.is_none());
1065 assert!(ctx.temperature.is_none());
1066 }
1067
1068 #[test]
1069 fn test_model_context_serde() {
1070 let ctx = ModelContext {
1071 provider: "test".to_string(),
1072 model: "test-model".to_string(),
1073 version: None,
1074 temperature: Some(0.5),
1075 max_tokens: None,
1076 };
1077 let json = serde_json::to_string(&ctx).unwrap();
1078 let parsed: ModelContext = serde_json::from_str(&json).unwrap();
1079 assert_eq!(ctx.provider, parsed.provider);
1080 assert_eq!(ctx.temperature, parsed.temperature);
1081 }
1082
1083 #[test]
1088 fn test_decision_record_new() {
1089 let exec_id = ExecutionId::from_string("exec_decision");
1090 let input = DecisionInput {
1091 facts: vec!["fact".to_string()],
1092 constraints: vec![],
1093 evidence_ids: vec![],
1094 };
1095 let record = DecisionRecord::new(DecisionType::Routing, exec_id, "agent_a", 0.95, input);
1096 assert!(record.decision_id.starts_with("dec_"));
1097 assert_eq!(record.decision_type, DecisionType::Routing);
1098 assert_eq!(record.outcome, "agent_a");
1099 assert_eq!(record.confidence, 0.95);
1100 }
1101
1102 #[test]
1103 fn test_decision_record_with_step() {
1104 let exec_id = ExecutionId::from_string("exec_decision");
1105 let step_id = StepId::from_string("step_decision");
1106 let input = DecisionInput {
1107 facts: vec![],
1108 constraints: vec![],
1109 evidence_ids: vec![],
1110 };
1111 let record = DecisionRecord::new(DecisionType::Branch, exec_id, "yes", 0.8, input)
1112 .with_step(step_id);
1113 assert!(record.step_id.is_some());
1114 }
1115
1116 #[test]
1117 fn test_decision_record_with_alternatives() {
1118 let exec_id = ExecutionId::from_string("exec_decision");
1119 let input = DecisionInput {
1120 facts: vec![],
1121 constraints: vec![],
1122 evidence_ids: vec![],
1123 };
1124 let alternatives = vec![DecisionAlternative {
1125 option: "option_b".to_string(),
1126 score: 0.6,
1127 rejected_reason: "Lower score".to_string(),
1128 }];
1129 let record = DecisionRecord::new(DecisionType::Routing, exec_id, "option_a", 0.9, input)
1130 .with_alternatives(alternatives);
1131 assert!(record.alternatives.is_some());
1132 assert_eq!(record.alternatives.unwrap().len(), 1);
1133 }
1134
1135 #[test]
1136 fn test_decision_record_with_model_context() {
1137 let exec_id = ExecutionId::from_string("exec_decision");
1138 let input = DecisionInput {
1139 facts: vec![],
1140 constraints: vec![],
1141 evidence_ids: vec![],
1142 };
1143 let model_ctx = ModelContext {
1144 provider: "anthropic".to_string(),
1145 model: "claude".to_string(),
1146 version: None,
1147 temperature: None,
1148 max_tokens: None,
1149 };
1150 let record = DecisionRecord::new(DecisionType::Approval, exec_id, "approved", 1.0, input)
1151 .with_model_context(model_ctx);
1152 assert!(record.model_context.is_some());
1153 }
1154
1155 #[test]
1156 fn test_decision_record_with_reasoning() {
1157 let exec_id = ExecutionId::from_string("exec_decision");
1158 let input = DecisionInput {
1159 facts: vec![],
1160 constraints: vec![],
1161 evidence_ids: vec![],
1162 };
1163 let record =
1164 DecisionRecord::new(DecisionType::Escalation, exec_id, "escalated", 0.5, input)
1165 .with_reasoning("Confidence too low for autonomous action");
1166 assert!(record.reasoning.is_some());
1167 assert!(record.reasoning.unwrap().contains("Confidence"));
1168 }
1169
1170 #[test]
1171 fn test_decision_record_serde() {
1172 let exec_id = ExecutionId::from_string("exec_serde");
1173 let input = DecisionInput {
1174 facts: vec!["f".to_string()],
1175 constraints: vec![],
1176 evidence_ids: vec![],
1177 };
1178 let record = DecisionRecord::new(DecisionType::Retry, exec_id, "retry", 0.7, input);
1179 let json = serde_json::to_string(&record).unwrap();
1180 let parsed: DecisionRecord = serde_json::from_str(&json).unwrap();
1181 assert_eq!(record.decision_type, parsed.decision_type);
1182 }
1183
1184 #[test]
1189 fn test_control_actor_variants() {
1190 let variants = vec![
1191 ControlActor::System,
1192 ControlActor::User,
1193 ControlActor::Agent,
1194 ControlActor::PolicyEngine,
1195 ];
1196 for variant in variants {
1197 let json = serde_json::to_string(&variant).unwrap();
1198 let parsed: ControlActor = serde_json::from_str(&json).unwrap();
1199 assert_eq!(variant, parsed);
1200 }
1201 }
1202
1203 #[test]
1204 fn test_control_actor_equality() {
1205 assert_eq!(ControlActor::System, ControlActor::System);
1206 assert_ne!(ControlActor::System, ControlActor::User);
1207 }
1208
1209 #[test]
1214 fn test_control_action_variants() {
1215 let variants = vec![
1216 ControlAction::Stop,
1217 ControlAction::Pause,
1218 ControlAction::Resume,
1219 ControlAction::Cancel,
1220 ControlAction::Approve,
1221 ControlAction::Deny,
1222 ControlAction::Escalate,
1223 ];
1224 for variant in variants {
1225 let json = serde_json::to_string(&variant).unwrap();
1226 let parsed: ControlAction = serde_json::from_str(&json).unwrap();
1227 assert_eq!(variant, parsed);
1228 }
1229 }
1230
1231 #[test]
1236 fn test_control_outcome_variants() {
1237 let variants = vec![
1238 ControlOutcome::Allowed,
1239 ControlOutcome::Denied,
1240 ControlOutcome::Modified,
1241 ControlOutcome::Escalated,
1242 ];
1243 for variant in variants {
1244 let json = serde_json::to_string(&variant).unwrap();
1245 let parsed: ControlOutcome = serde_json::from_str(&json).unwrap();
1246 assert_eq!(variant, parsed);
1247 }
1248 }
1249
1250 #[test]
1255 fn test_control_event_new() {
1256 let exec_id = ExecutionId::from_string("exec_ctrl");
1257 let event = ControlEvent::new(
1258 ControlActor::User,
1259 ControlAction::Pause,
1260 exec_id,
1261 "User requested pause",
1262 ControlOutcome::Allowed,
1263 );
1264 assert!(event.event_id.starts_with("ctrl_"));
1265 assert_eq!(event.actor, ControlActor::User);
1266 assert_eq!(event.action, ControlAction::Pause);
1267 assert_eq!(event.outcome, ControlOutcome::Allowed);
1268 }
1269
1270 #[test]
1271 fn test_control_event_with_step() {
1272 let exec_id = ExecutionId::from_string("exec_ctrl");
1273 let step_id = StepId::from_string("step_ctrl");
1274 let event = ControlEvent::new(
1275 ControlActor::System,
1276 ControlAction::Cancel,
1277 exec_id,
1278 "Timeout",
1279 ControlOutcome::Allowed,
1280 )
1281 .with_step(step_id);
1282 assert!(event.step_id.is_some());
1283 }
1284
1285 #[test]
1286 fn test_control_event_with_actor_id() {
1287 let exec_id = ExecutionId::from_string("exec_ctrl");
1288 let event = ControlEvent::new(
1289 ControlActor::User,
1290 ControlAction::Approve,
1291 exec_id,
1292 "Approved by admin",
1293 ControlOutcome::Allowed,
1294 )
1295 .with_actor_id("user_admin_123");
1296 assert!(event.actor_id.is_some());
1297 assert_eq!(event.actor_id.unwrap(), "user_admin_123");
1298 }
1299
1300 #[test]
1301 fn test_control_event_pause_factory() {
1302 let exec_id = ExecutionId::from_string("exec_pause");
1303 let event = ControlEvent::pause(exec_id, ControlActor::User, "Pausing for review");
1304 assert_eq!(event.action, ControlAction::Pause);
1305 assert_eq!(event.actor, ControlActor::User);
1306 assert_eq!(event.outcome, ControlOutcome::Allowed);
1307 }
1308
1309 #[test]
1310 fn test_control_event_resume_factory() {
1311 let exec_id = ExecutionId::from_string("exec_resume");
1312 let event = ControlEvent::resume(exec_id, ControlActor::User, "Resuming after review");
1313 assert_eq!(event.action, ControlAction::Resume);
1314 }
1315
1316 #[test]
1317 fn test_control_event_cancel_factory() {
1318 let exec_id = ExecutionId::from_string("exec_cancel");
1319 let event = ControlEvent::cancel(exec_id, ControlActor::System, "System timeout");
1320 assert_eq!(event.action, ControlAction::Cancel);
1321 assert_eq!(event.actor, ControlActor::System);
1322 }
1323
1324 #[test]
1325 fn test_control_event_serde() {
1326 let exec_id = ExecutionId::from_string("exec_serde");
1327 let event = ControlEvent::pause(exec_id, ControlActor::Agent, "Self-regulation");
1328 let json = serde_json::to_string(&event).unwrap();
1329 let parsed: ControlEvent = serde_json::from_str(&json).unwrap();
1330 assert_eq!(event.action, parsed.action);
1331 assert_eq!(event.actor, parsed.actor);
1332 }
1333
1334 #[test]
1339 fn test_event_new() {
1340 let run_id = ExecutionId::from_string("exec_legacy");
1341 let event = Event::new(run_id, "test_author");
1342 assert!(event.id.starts_with("evt_"));
1343 assert_eq!(event.author, "test_author");
1344 assert!(!event.is_final);
1345 assert!(event.content.is_none());
1346 assert!(event.node_id.is_none());
1347 }
1348
1349 #[test]
1350 fn test_event_with_content() {
1351 let run_id = ExecutionId::from_string("exec_legacy");
1352 let event = Event::new(run_id, "author").with_content("Hello, world!");
1353 assert!(event.content.is_some());
1354 assert_eq!(event.content.unwrap(), "Hello, world!");
1355 }
1356
1357 #[test]
1358 fn test_event_with_node() {
1359 let run_id = ExecutionId::from_string("exec_legacy");
1360 let node_id = StepId::from_string("step_legacy");
1361 let event = Event::new(run_id, "author").with_node(node_id.clone());
1362 assert!(event.node_id.is_some());
1363 assert_eq!(event.node_id.unwrap().as_str(), "step_legacy");
1364 }
1365
1366 #[test]
1367 fn test_event_final_response() {
1368 let run_id = ExecutionId::from_string("exec_legacy");
1369 let event = Event::new(run_id, "author").final_response();
1370 assert!(event.is_final);
1371 }
1372
1373 #[test]
1374 fn test_event_builder_chain() {
1375 let run_id = ExecutionId::from_string("exec_chain");
1376 let node_id = StepId::from_string("step_chain");
1377 let event = Event::new(run_id, "test")
1378 .with_content("Content here")
1379 .with_node(node_id)
1380 .final_response();
1381 assert!(event.content.is_some());
1382 assert!(event.node_id.is_some());
1383 assert!(event.is_final);
1384 }
1385
1386 #[test]
1387 fn test_event_serde() {
1388 let run_id = ExecutionId::from_string("exec_serde");
1389 let event = Event::new(run_id, "author").with_content("test");
1390 let json = serde_json::to_string(&event).unwrap();
1391 let parsed: Event = serde_json::from_str(&json).unwrap();
1392 assert_eq!(event.author, parsed.author);
1393 }
1394
1395 #[test]
1400 fn test_event_id_format() {
1401 let ctx = ExecutionContext::new(ExecutionId::from_string("exec_test"));
1402 let event = ExecutionEvent::new(ExecutionEventType::ExecutionStart, ctx);
1403 assert!(event.event_id.starts_with("evt_"));
1404 assert_eq!(event.event_id.len(), 31);
1406 }
1407
1408 #[test]
1409 fn test_decision_id_format() {
1410 let exec_id = ExecutionId::from_string("exec_test");
1411 let input = DecisionInput {
1412 facts: vec![],
1413 constraints: vec![],
1414 evidence_ids: vec![],
1415 };
1416 let record = DecisionRecord::new(DecisionType::Routing, exec_id, "outcome", 0.9, input);
1417 assert!(record.decision_id.starts_with("dec_"));
1418 }
1419
1420 #[test]
1421 fn test_control_event_id_format() {
1422 let exec_id = ExecutionId::from_string("exec_test");
1423 let event = ControlEvent::pause(exec_id, ControlActor::User, "test");
1424 assert!(event.event_id.starts_with("ctrl_"));
1425 }
1426}