Skip to main content

canic_host/deployment_truth/model/execution/
mod.rs

1use super::inventory::ObservationStatusV1;
2use super::safety::SafetyFindingV1;
3use serde::{Deserialize, Serialize};
4
5///
6/// DeploymentReceiptV1
7///
8#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
9pub struct DeploymentReceiptV1 {
10    pub schema_version: u32,
11    pub operation_id: String,
12    pub plan_id: String,
13    pub execution_context: Option<DeploymentExecutionContextV1>,
14    pub operation_status: DeploymentExecutionStatusV1,
15    pub started_at: String,
16    pub finished_at: Option<String>,
17    pub operator_principal: Option<String>,
18    pub root_principal: Option<String>,
19    pub previous_observed_deployment_epoch: Option<u64>,
20    pub phase_receipts: Vec<PhaseReceiptV1>,
21    pub role_phase_receipts: Vec<RolePhaseReceiptV1>,
22    pub final_inventory_id: Option<String>,
23    pub command_result: DeploymentCommandResultV1,
24}
25
26///
27/// DeploymentExecutionContextV1
28///
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct DeploymentExecutionContextV1 {
31    pub workspace_root: Option<String>,
32    pub icp_root: Option<String>,
33    pub artifact_roots: Vec<String>,
34    pub backend: DeploymentExecutorBackendV1,
35    pub backend_capabilities: Vec<DeploymentExecutorCapabilityV1>,
36}
37
38///
39/// DeploymentExecutionPreflightV1
40///
41#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct DeploymentExecutionPreflightV1 {
43    pub schema_version: u32,
44    pub plan_id: String,
45    pub safety_report_id: String,
46    pub authority_plan_id: String,
47    pub backend: DeploymentExecutorBackendV1,
48    pub status: DeploymentExecutionPreflightStatusV1,
49    pub planned_phases: Vec<String>,
50    pub required_capabilities: Vec<DeploymentExecutorCapabilityV1>,
51    pub missing_capabilities: Vec<DeploymentExecutorCapabilityV1>,
52    pub blockers: Vec<SafetyFindingV1>,
53}
54
55///
56/// DeploymentExecutionPreflightStatusV1
57///
58#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
59pub enum DeploymentExecutionPreflightStatusV1 {
60    Ready,
61    Blocked,
62}
63
64impl DeploymentExecutionPreflightStatusV1 {
65    #[must_use]
66    pub const fn label(self) -> &'static str {
67        match self {
68            Self::Ready => "ready",
69            Self::Blocked => "blocked",
70        }
71    }
72
73    #[must_use]
74    pub const fn variant_label(self) -> &'static str {
75        match self {
76            Self::Ready => "Ready",
77            Self::Blocked => "Blocked",
78        }
79    }
80}
81
82///
83/// DeploymentExecutorBackendV1
84///
85#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
86pub enum DeploymentExecutorBackendV1 {
87    CurrentCli,
88    PocketIc,
89    DirectAgent,
90    Other { name: String },
91}
92
93impl DeploymentExecutorBackendV1 {
94    #[must_use]
95    pub fn variant_label(&self) -> String {
96        match self {
97            Self::CurrentCli => "CurrentCli".to_string(),
98            Self::PocketIc => "PocketIc".to_string(),
99            Self::DirectAgent => "DirectAgent".to_string(),
100            Self::Other { name } => format!("Other {{ name: {name:?} }}"),
101        }
102    }
103}
104
105///
106/// DeploymentExecutorCapabilityV1
107///
108#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
109pub enum DeploymentExecutorCapabilityV1 {
110    CreateCanister,
111    CanisterStatus,
112    UpdateSettings,
113    InstallCode,
114    Call,
115    Query,
116    StageArtifact,
117}
118
119///
120/// PhaseReceiptV1
121///
122#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
123pub struct PhaseReceiptV1 {
124    pub phase: String,
125    pub started_at: String,
126    pub finished_at: Option<String>,
127    pub attempted_action: String,
128    pub verified_postcondition: VerifiedPostconditionV1,
129}
130
131///
132/// VerifiedPostconditionV1
133///
134#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
135pub struct VerifiedPostconditionV1 {
136    pub status: ObservationStatusV1,
137    pub evidence: Vec<String>,
138}
139
140///
141/// DeploymentExecutionStatusV1
142///
143#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
144pub enum DeploymentExecutionStatusV1 {
145    NotStarted,
146    InProgress,
147    FailedBeforeMutation,
148    PartiallyApplied,
149    FailedAfterMutation,
150    Complete,
151}
152
153impl DeploymentExecutionStatusV1 {
154    #[must_use]
155    pub const fn label(self) -> &'static str {
156        match self {
157            Self::NotStarted => "not_started",
158            Self::InProgress => "in_progress",
159            Self::FailedBeforeMutation => "failed_before_mutation",
160            Self::PartiallyApplied => "partially_applied",
161            Self::FailedAfterMutation => "failed_after_mutation",
162            Self::Complete => "complete",
163        }
164    }
165
166    #[must_use]
167    pub const fn variant_label(self) -> &'static str {
168        match self {
169            Self::NotStarted => "NotStarted",
170            Self::InProgress => "InProgress",
171            Self::FailedBeforeMutation => "FailedBeforeMutation",
172            Self::PartiallyApplied => "PartiallyApplied",
173            Self::FailedAfterMutation => "FailedAfterMutation",
174            Self::Complete => "Complete",
175        }
176    }
177}
178
179///
180/// DeploymentCommandResultV1
181///
182#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
183pub enum DeploymentCommandResultV1 {
184    NotFinished,
185    Succeeded,
186    Failed { code: String, message: String },
187}
188
189impl DeploymentCommandResultV1 {
190    #[must_use]
191    pub fn variant_label(&self) -> String {
192        match self {
193            Self::NotFinished => "NotFinished".to_string(),
194            Self::Succeeded => "Succeeded".to_string(),
195            Self::Failed { code, message } => {
196                format!("Failed {{ code: {code:?}, message: {message:?} }}")
197            }
198        }
199    }
200}
201
202///
203/// RolePhaseReceiptV1
204///
205#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
206pub struct RolePhaseReceiptV1 {
207    pub role: String,
208    pub phase: String,
209    pub result: RolePhaseResultV1,
210    pub previous_module_hash: Option<String>,
211    pub target_module_hash: Option<String>,
212    pub observed_module_hash_after: Option<String>,
213    pub artifact_digest: Option<String>,
214    pub canonical_embedded_config_sha256: Option<String>,
215    pub error: Option<String>,
216}
217
218///
219/// RolePhaseResultV1
220///
221#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
222pub enum RolePhaseResultV1 {
223    Applied,
224    Failed,
225    Skipped,
226    NotAttempted,
227    VerifiedAlreadyApplied,
228}
229
230impl RolePhaseResultV1 {
231    #[must_use]
232    pub const fn label(self) -> &'static str {
233        match self {
234            Self::Applied => "Applied",
235            Self::Failed => "Failed",
236            Self::Skipped => "Skipped",
237            Self::NotAttempted => "NotAttempted",
238            Self::VerifiedAlreadyApplied => "VerifiedAlreadyApplied",
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn deployment_execution_preflight_status_owns_text_labels() {
249        assert_eq!(DeploymentExecutionPreflightStatusV1::Ready.label(), "ready");
250        assert_eq!(
251            DeploymentExecutionPreflightStatusV1::Blocked.label(),
252            "blocked"
253        );
254        assert_eq!(
255            DeploymentExecutionPreflightStatusV1::Ready.variant_label(),
256            "Ready"
257        );
258        assert_eq!(
259            DeploymentExecutionPreflightStatusV1::Blocked.variant_label(),
260            "Blocked"
261        );
262    }
263
264    #[test]
265    fn deployment_executor_backend_owns_variant_labels() {
266        assert_eq!(
267            DeploymentExecutorBackendV1::CurrentCli.variant_label(),
268            "CurrentCli"
269        );
270        assert_eq!(
271            DeploymentExecutorBackendV1::PocketIc.variant_label(),
272            "PocketIc"
273        );
274        assert_eq!(
275            DeploymentExecutorBackendV1::DirectAgent.variant_label(),
276            "DirectAgent"
277        );
278        assert_eq!(
279            DeploymentExecutorBackendV1::Other {
280                name: "fixture".to_string()
281            }
282            .variant_label(),
283            "Other { name: \"fixture\" }"
284        );
285    }
286
287    #[test]
288    fn deployment_execution_status_owns_text_labels() {
289        assert_eq!(
290            DeploymentExecutionStatusV1::NotStarted.label(),
291            "not_started"
292        );
293        assert_eq!(
294            DeploymentExecutionStatusV1::InProgress.label(),
295            "in_progress"
296        );
297        assert_eq!(
298            DeploymentExecutionStatusV1::FailedBeforeMutation.label(),
299            "failed_before_mutation"
300        );
301        assert_eq!(
302            DeploymentExecutionStatusV1::PartiallyApplied.label(),
303            "partially_applied"
304        );
305        assert_eq!(
306            DeploymentExecutionStatusV1::FailedAfterMutation.label(),
307            "failed_after_mutation"
308        );
309        assert_eq!(DeploymentExecutionStatusV1::Complete.label(), "complete");
310        assert_eq!(
311            DeploymentExecutionStatusV1::NotStarted.variant_label(),
312            "NotStarted"
313        );
314        assert_eq!(
315            DeploymentExecutionStatusV1::InProgress.variant_label(),
316            "InProgress"
317        );
318        assert_eq!(
319            DeploymentExecutionStatusV1::FailedBeforeMutation.variant_label(),
320            "FailedBeforeMutation"
321        );
322        assert_eq!(
323            DeploymentExecutionStatusV1::PartiallyApplied.variant_label(),
324            "PartiallyApplied"
325        );
326        assert_eq!(
327            DeploymentExecutionStatusV1::FailedAfterMutation.variant_label(),
328            "FailedAfterMutation"
329        );
330        assert_eq!(
331            DeploymentExecutionStatusV1::Complete.variant_label(),
332            "Complete"
333        );
334    }
335
336    #[test]
337    fn deployment_command_result_owns_variant_labels() {
338        assert_eq!(
339            DeploymentCommandResultV1::NotFinished.variant_label(),
340            "NotFinished"
341        );
342        assert_eq!(
343            DeploymentCommandResultV1::Succeeded.variant_label(),
344            "Succeeded"
345        );
346        assert_eq!(
347            DeploymentCommandResultV1::Failed {
348                code: "install_failed".to_string(),
349                message: "install \"root\" failed".to_string(),
350            }
351            .variant_label(),
352            "Failed { code: \"install_failed\", message: \"install \\\"root\\\" failed\" }"
353        );
354    }
355
356    #[test]
357    fn role_phase_result_owns_text_labels() {
358        assert_eq!(RolePhaseResultV1::Applied.label(), "Applied");
359        assert_eq!(RolePhaseResultV1::Failed.label(), "Failed");
360        assert_eq!(RolePhaseResultV1::Skipped.label(), "Skipped");
361        assert_eq!(RolePhaseResultV1::NotAttempted.label(), "NotAttempted");
362        assert_eq!(
363            RolePhaseResultV1::VerifiedAlreadyApplied.label(),
364            "VerifiedAlreadyApplied"
365        );
366    }
367}