1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use chrono::{DateTime, Utc};
8
9use crate::ids::{DecisionId, ExecutionId, PlanId, StepId};
10use crate::plan::Plan;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct PlanExecution {
15 pub id: ExecutionId,
17 pub plan_id: PlanId,
19 pub state: ExecutionState,
21 pub step_results: HashMap<StepId, StepResult>,
23 pub decisions: HashMap<DecisionId, RecordedDecision>,
25 pub started_at: DateTime<Utc>,
27 pub completed_at: Option<DateTime<Utc>>,
29}
30
31impl PlanExecution {
32 #[must_use]
34 pub fn new(id: ExecutionId, plan: &Plan) -> Self {
35 Self {
36 id,
37 plan_id: plan.id.clone(),
38 state: ExecutionState::NotStarted,
39 step_results: HashMap::new(),
40 decisions: HashMap::new(),
41 started_at: Utc::now(),
42 completed_at: None,
43 }
44 }
45
46 #[must_use]
48 pub fn is_complete(&self) -> bool {
49 matches!(
50 self.state,
51 ExecutionState::Completed
52 | ExecutionState::Failed { .. }
53 | ExecutionState::Cancelled { .. }
54 )
55 }
56
57 #[must_use]
59 pub fn is_success(&self) -> bool {
60 matches!(self.state, ExecutionState::Completed)
61 }
62
63 #[must_use]
65 pub fn get_step_result(&self, step_id: &StepId) -> Option<&StepResult> {
66 self.step_results.get(step_id)
67 }
68
69 pub fn record_step_result(&mut self, step_id: StepId, result: StepResult) {
71 self.step_results.insert(step_id, result);
72 }
73
74 pub fn record_decision(&mut self, decision_id: DecisionId, decision: RecordedDecision) {
76 self.decisions.insert(decision_id, decision);
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82pub enum ExecutionState {
83 NotStarted,
85 Running {
87 current_step: StepId,
89 },
90 WaitingForDecision {
92 decision_id: DecisionId,
94 },
95 Completed,
97 Failed {
99 reason: String,
101 failed_step: Option<StepId>,
103 },
104 Cancelled {
106 reason: String,
108 },
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct StepResult {
114 pub outcome: StepOutcome,
116 pub outputs: HashMap<String, serde_json::Value>,
118 pub started_at: DateTime<Utc>,
120 pub completed_at: DateTime<Utc>,
122 pub retry_count: u32,
124}
125
126impl StepResult {
127 #[must_use]
129 pub fn success(outputs: HashMap<String, serde_json::Value>) -> Self {
130 let now = Utc::now();
131 Self {
132 outcome: StepOutcome::Success,
133 outputs,
134 started_at: now,
135 completed_at: now,
136 retry_count: 0,
137 }
138 }
139
140 #[must_use]
142 pub fn failure(error: impl Into<String>) -> Self {
143 let now = Utc::now();
144 Self {
145 outcome: StepOutcome::Failure {
146 error: error.into(),
147 },
148 outputs: HashMap::new(),
149 started_at: now,
150 completed_at: now,
151 retry_count: 0,
152 }
153 }
154
155 #[must_use]
157 pub fn skipped(reason: impl Into<String>) -> Self {
158 let now = Utc::now();
159 Self {
160 outcome: StepOutcome::Skipped {
161 reason: reason.into(),
162 },
163 outputs: HashMap::new(),
164 started_at: now,
165 completed_at: now,
166 retry_count: 0,
167 }
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
173pub enum StepOutcome {
174 Success,
176 Failure {
178 error: String,
180 },
181 Pending,
183 Skipped {
185 reason: String,
187 },
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct RecordedDecision {
193 pub context_hash: String,
195 pub action: String,
197 pub reasoning: String,
199 pub confidence: f64,
201 pub timestamp: DateTime<Utc>,
203 pub model_version: String,
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::ids::PlanId;
211 use crate::plan::Plan;
212
213 #[test]
214 fn test_execution_creation() {
215 let plan = Plan::new(PlanId::generate(), "test-plan");
216 let execution = PlanExecution::new(ExecutionId::generate(), &plan);
217 assert_eq!(execution.state, ExecutionState::NotStarted);
218 assert!(!execution.is_complete());
219 }
220
221 #[test]
222 fn test_step_result_success() {
223 let result = StepResult::success(HashMap::new());
224 assert_eq!(result.outcome, StepOutcome::Success);
225 }
226
227 #[test]
228 fn test_step_result_failure() {
229 let result = StepResult::failure("test error");
230 assert!(matches!(result.outcome, StepOutcome::Failure { .. }));
231 }
232
233 #[test]
234 fn test_is_complete_for_all_terminal_states() {
235 let plan = Plan::new(PlanId::generate(), "test-plan");
236 let mut execution = PlanExecution::new(ExecutionId::generate(), &plan);
237
238 assert!(!execution.is_complete());
240
241 execution.state = ExecutionState::Completed;
243 assert!(execution.is_complete());
244
245 execution.state = ExecutionState::Failed {
247 reason: "test".to_string(),
248 failed_step: None,
249 };
250 assert!(execution.is_complete());
251
252 execution.state = ExecutionState::Cancelled {
254 reason: "test".to_string(),
255 };
256 assert!(execution.is_complete());
257 }
258}