forge_reasoning/verification/
check.rs1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::time::Duration;
8use uuid::Uuid;
9
10use crate::hypothesis::types::{HypothesisId, HypothesisStatus};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct CheckId(pub Uuid);
15
16impl CheckId {
17 pub fn new() -> Self {
18 Self(Uuid::new_v4())
19 }
20}
21
22impl Default for CheckId {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl std::fmt::Display for CheckId {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(f, "{}", self.0)
31 }
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize)]
36pub enum VerificationCommand {
37 ShellCommand(String),
39 CustomAssertion {
41 description: String,
42 },
44}
45
46#[derive(Clone, Debug, Serialize, Deserialize)]
48pub enum PassAction {
49 UpdateConfidence(f64),
51 SetStatus(HypothesisStatus),
53}
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
57pub enum FailAction {
58 UpdateConfidence(f64),
60 SetStatus(HypothesisStatus),
62}
63
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub enum CheckStatus {
67 Pending,
68 Running,
69 Completed,
70 Failed,
71}
72
73#[derive(Clone, Debug, Serialize, Deserialize)]
75pub enum CheckResult {
76 Passed {
77 output: String,
78 duration: Duration,
79 },
80 Failed {
81 output: String,
82 error: String,
83 },
84 Timeout {
85 output: String,
86 },
87 Panic {
88 message: String,
89 },
90}
91
92impl CheckResult {
93 pub fn is_success(&self) -> bool {
95 matches!(self, Self::Passed { .. })
96 }
97
98 pub fn output(&self) -> Option<&str> {
100 match self {
101 Self::Passed { output, .. } => Some(output),
102 Self::Failed { output, .. } => Some(output),
103 Self::Timeout { output } => Some(output),
104 Self::Panic { .. } => None,
105 }
106 }
107
108 pub fn duration(&self) -> Option<Duration> {
110 match self {
111 Self::Passed { duration, .. } => Some(*duration),
112 _ => None,
113 }
114 }
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct VerificationCheck {
120 pub id: CheckId,
121 pub name: String,
122 pub hypothesis_id: HypothesisId,
123 pub timeout: Duration,
124 pub command: VerificationCommand,
125 pub on_pass: Option<PassAction>,
126 pub on_fail: Option<FailAction>,
127 pub status: CheckStatus,
128 pub created_at: DateTime<Utc>,
129}
130
131impl VerificationCheck {
132 pub fn new(
133 name: String,
134 hypothesis_id: HypothesisId,
135 timeout: Duration,
136 command: VerificationCommand,
137 on_pass: Option<PassAction>,
138 on_fail: Option<FailAction>,
139 ) -> Self {
140 Self {
141 id: CheckId::new(),
142 name,
143 hypothesis_id,
144 timeout,
145 command,
146 on_pass,
147 on_fail,
148 status: CheckStatus::Pending,
149 created_at: Utc::now(),
150 }
151 }
152
153 pub fn with_status(mut self, status: CheckStatus) -> Self {
155 self.status = status;
156 self
157 }
158
159 pub fn is_retryable(&self) -> bool {
161 matches!(self.status, CheckStatus::Failed)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_check_id_generation() {
171 let id1 = CheckId::new();
172 let id2 = CheckId::new();
173 assert_ne!(id1, id2);
174 }
175
176 #[test]
177 fn test_check_result_is_success() {
178 let passed = CheckResult::Passed {
179 output: "test".to_string(),
180 duration: Duration::from_millis(100),
181 };
182 assert!(passed.is_success());
183
184 let failed = CheckResult::Failed {
185 output: "test".to_string(),
186 error: "error".to_string(),
187 };
188 assert!(!failed.is_success());
189 }
190
191 #[test]
192 fn test_check_result_output() {
193 let passed = CheckResult::Passed {
194 output: "output".to_string(),
195 duration: Duration::from_millis(100),
196 };
197 assert_eq!(passed.output(), Some("output"));
198
199 let timeout = CheckResult::Timeout {
200 output: "timeout output".to_string(),
201 };
202 assert_eq!(timeout.output(), Some("timeout output"));
203
204 let panic = CheckResult::Panic {
205 message: "panic".to_string(),
206 };
207 assert_eq!(panic.output(), None);
208 }
209
210 #[test]
211 fn test_verification_check_creation() {
212 let hypothesis_id = HypothesisId::new();
213 let check = VerificationCheck::new(
214 "test check".to_string(),
215 hypothesis_id,
216 Duration::from_secs(5),
217 VerificationCommand::ShellCommand("echo test".to_string()),
218 Some(PassAction::SetStatus(HypothesisStatus::Confirmed)),
219 Some(FailAction::SetStatus(HypothesisStatus::Rejected)),
220 );
221
222 assert_eq!(check.name, "test check");
223 assert_eq!(check.hypothesis_id, hypothesis_id);
224 assert_eq!(check.status, CheckStatus::Pending);
225 }
226
227 #[test]
228 fn test_verification_check_with_status() {
229 let hypothesis_id = HypothesisId::new();
230 let check = VerificationCheck::new(
231 "test".to_string(),
232 hypothesis_id,
233 Duration::from_secs(1),
234 VerificationCommand::ShellCommand("test".to_string()),
235 None,
236 None,
237 );
238
239 assert_eq!(check.status, CheckStatus::Pending);
240
241 let running_check = check.with_status(CheckStatus::Running);
242 assert_eq!(running_check.status, CheckStatus::Running);
243 }
244
245 #[test]
246 fn test_is_retryable() {
247 let hypothesis_id = HypothesisId::new();
248
249 let pending_check = VerificationCheck::new(
250 "test".to_string(),
251 hypothesis_id,
252 Duration::from_secs(1),
253 VerificationCommand::ShellCommand("test".to_string()),
254 None,
255 None,
256 );
257 assert!(!pending_check.is_retryable());
258
259 let failed_check = pending_check.clone().with_status(CheckStatus::Failed);
260 assert!(failed_check.is_retryable());
261
262 let failed_check = pending_check.clone().with_status(CheckStatus::Failed);
263 assert!(failed_check.is_retryable());
264 }
265}