Skip to main content

coil_ops/recovery/
catalog.rs

1use std::collections::BTreeSet;
2use std::time::Duration;
3
4use coil_auth::Capability;
5use coil_jobs::RetryPolicy;
6
7use crate::error::OpsModelError;
8use crate::identifiers::RecoveryWorkflowId;
9
10use super::{RecoveryStage, RecoveryWorkflowDefinition};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RecoveryCatalog {
14    definitions: Vec<RecoveryWorkflowDefinition>,
15}
16
17impl RecoveryCatalog {
18    pub fn new(definitions: Vec<RecoveryWorkflowDefinition>) -> Self {
19        Self { definitions }
20    }
21
22    pub fn standard() -> Self {
23        Self::new(vec![
24            RecoveryWorkflowDefinition::new(
25                RecoveryWorkflowId::new("recovery.customer-app.full-restore")
26                    .expect("constant workflow id is valid"),
27                "Full customer-app restore",
28                Some(
29                    "Restores source-of-truth state first, then rebuilds disposable layers and redeploys assets."
30                        .to_string(),
31                ),
32                Capability::SystemModuleManage,
33                default_recovery_retry_policy(),
34                true,
35                true,
36                vec![
37                    RecoveryStage::RestoreDatabase,
38                    RecoveryStage::ReattachManagedObjectStore,
39                    RecoveryStage::RestoreLocalOnlySensitive,
40                    RecoveryStage::RebuildDerivedState,
41                    RecoveryStage::RedeployStaticAssets,
42                    RecoveryStage::ValidateReadiness,
43                ],
44            )
45            .expect("constant recovery workflow is valid"),
46            RecoveryWorkflowDefinition::new(
47                RecoveryWorkflowId::new("recovery.customer-app.derived-state")
48                    .expect("constant workflow id is valid"),
49                "Derived-state rebuild",
50                Some(
51                    "Rebuilds caches, search indexes, report snapshots, and redeploys static assets without restoring source-of-truth storage."
52                        .to_string(),
53                ),
54                Capability::SystemModuleManage,
55                default_recovery_retry_policy(),
56                true,
57                false,
58                vec![
59                    RecoveryStage::RebuildDerivedState,
60                    RecoveryStage::RedeployStaticAssets,
61                    RecoveryStage::ValidateReadiness,
62                ],
63            )
64            .expect("constant recovery workflow is valid"),
65        ])
66    }
67
68    pub fn definitions(&self) -> &[RecoveryWorkflowDefinition] {
69        &self.definitions
70    }
71
72    pub fn definition(&self, id: &RecoveryWorkflowId) -> Option<&RecoveryWorkflowDefinition> {
73        self.definitions
74            .iter()
75            .find(|definition| &definition.id == id)
76    }
77
78    pub fn validate(&self) -> Result<(), OpsModelError> {
79        let mut ids = BTreeSet::new();
80        for definition in &self.definitions {
81            if !ids.insert(definition.id.as_str().to_string()) {
82                return Err(OpsModelError::DuplicateIdentifier {
83                    kind: "recovery workflow",
84                    id: definition.id.to_string(),
85                });
86            }
87        }
88        Ok(())
89    }
90}
91
92impl Default for RecoveryCatalog {
93    fn default() -> Self {
94        Self::standard()
95    }
96}
97
98fn default_recovery_retry_policy() -> RetryPolicy {
99    RetryPolicy::new(3, Duration::from_secs(30), Duration::from_secs(900))
100        .expect("constant recovery retry policy is valid")
101}