Skip to main content

forge_reasoning/verification/
check.rs

1//! Verification check types
2//!
3//! Provides types for defining verification checks with commands, timeouts, and actions.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::time::Duration;
8use uuid::Uuid;
9
10use crate::hypothesis::types::{HypothesisId, HypothesisStatus};
11
12/// Unique identifier for a verification check
13#[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/// Command to execute for verification
35#[derive(Clone, Debug, Serialize, Deserialize)]
36pub enum VerificationCommand {
37    /// Execute a shell command
38    ShellCommand(String),
39    /// Custom assertion for future extensibility
40    CustomAssertion {
41        description: String,
42        // check_fn will be added in future when function storage is needed
43    },
44}
45
46/// Action to take when a check passes
47#[derive(Clone, Debug, Serialize, Deserialize)]
48pub enum PassAction {
49    /// Update hypothesis confidence by given amount (-1.0 to 1.0)
50    UpdateConfidence(f64),
51    /// Set hypothesis to specific status
52    SetStatus(HypothesisStatus),
53}
54
55/// Action to take when a check fails
56#[derive(Clone, Debug, Serialize, Deserialize)]
57pub enum FailAction {
58    /// Update hypothesis confidence by given amount (-1.0 to 1.0)
59    UpdateConfidence(f64),
60    /// Set hypothesis to specific status
61    SetStatus(HypothesisStatus),
62}
63
64/// Current status of a verification check
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub enum CheckStatus {
67    Pending,
68    Running,
69    Completed,
70    Failed,
71}
72
73/// Result of a verification check execution
74#[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    /// Check if result indicates success
94    pub fn is_success(&self) -> bool {
95        matches!(self, Self::Passed { .. })
96    }
97
98    /// Get the output string (if available)
99    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    /// Get duration (only available for Passed result)
109    pub fn duration(&self) -> Option<Duration> {
110        match self {
111            Self::Passed { duration, .. } => Some(*duration),
112            _ => None,
113        }
114    }
115}
116
117/// A verification check to execute
118#[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    /// Update the status of this check
154    pub fn with_status(mut self, status: CheckStatus) -> Self {
155        self.status = status;
156        self
157    }
158
159    /// Check if this check is retryable based on current status
160    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}