Skip to main content

canic_host/deployment_truth/model/inventory/
mod.rs

1use super::artifact::ObservedArtifactV1;
2use super::plan::DeploymentIdentityV1;
3use serde::{Deserialize, Serialize};
4
5///
6/// DeploymentInventoryV1
7///
8#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
9pub struct DeploymentInventoryV1 {
10    pub schema_version: u32,
11    pub inventory_id: String,
12    pub observed_at: String,
13    pub observed_identity: Option<DeploymentIdentityV1>,
14    pub observed_root: Option<DeploymentRootObservationV1>,
15    pub local_config: LocalDeploymentConfigV1,
16    pub observed_canisters: Vec<ObservedCanisterV1>,
17    pub observed_pool: Vec<ObservedPoolCanisterV1>,
18    pub observed_artifacts: Vec<ObservedArtifactV1>,
19    pub observed_verifier_readiness: VerifierReadinessObservationV1,
20    pub unresolved_observations: Vec<DeploymentObservationGapV1>,
21}
22
23///
24/// DeploymentRootObservationV1
25///
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub struct DeploymentRootObservationV1 {
28    pub deployment_name: String,
29    pub network: String,
30    pub fleet_template: String,
31    pub root_principal: String,
32    pub observed_canister_id: String,
33    pub observation_source: DeploymentRootObservationSourceV1,
34    pub control_class: CanisterControlClassV1,
35    pub controllers: Vec<String>,
36    pub module_hash: Option<String>,
37    pub status: Option<String>,
38    pub role_assignment_source: Option<String>,
39}
40
41///
42/// DeploymentRootObservationSourceV1
43///
44#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
45pub enum DeploymentRootObservationSourceV1 {
46    IcpCanisterStatus,
47    LocalDeploymentState,
48}
49
50impl DeploymentRootObservationSourceV1 {
51    #[must_use]
52    pub const fn label(self) -> &'static str {
53        match self {
54            Self::IcpCanisterStatus => "IcpCanisterStatus",
55            Self::LocalDeploymentState => "LocalDeploymentState",
56        }
57    }
58}
59
60///
61/// ExpectedCanisterV1
62///
63#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
64pub struct ExpectedCanisterV1 {
65    pub role: String,
66    pub canister_id: Option<String>,
67    pub control_class: CanisterControlClassV1,
68}
69
70///
71/// ObservedCanisterV1
72///
73#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct ObservedCanisterV1 {
75    pub canister_id: String,
76    pub role: Option<String>,
77    pub control_class: CanisterControlClassV1,
78    pub controllers: Vec<String>,
79    pub module_hash: Option<String>,
80    pub status: Option<String>,
81    pub root_trust_anchor: Option<String>,
82    pub canonical_embedded_config_digest: Option<String>,
83    pub role_assignment_source: Option<String>,
84}
85
86///
87/// RoleAssignmentSourceV1
88///
89/// Machine-readable provenance for one observed role assignment.
90///
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93pub enum RoleAssignmentSourceV1 {
94    IcpCanisterStatus,
95    LocalInstallState,
96    SubnetRegistry,
97    SubnetRegistryAndIcpCanisterStatus,
98}
99
100impl RoleAssignmentSourceV1 {
101    #[must_use]
102    pub const fn label(self) -> &'static str {
103        match self {
104            Self::IcpCanisterStatus => "icp_canister_status",
105            Self::LocalInstallState => "local_install_state",
106            Self::SubnetRegistry => "subnet_registry",
107            Self::SubnetRegistryAndIcpCanisterStatus => "subnet_registry+icp_canister_status",
108        }
109    }
110
111    #[must_use]
112    pub fn label_includes_live_status(label: &str) -> bool {
113        label == Self::IcpCanisterStatus.label()
114            || label == Self::SubnetRegistryAndIcpCanisterStatus.label()
115    }
116}
117
118///
119/// CanisterControlClassV1
120///
121#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
122pub enum CanisterControlClassV1 {
123    DeploymentControlled,
124    CanicManagedPool,
125    ExternallyImported,
126    JointlyControlled,
127    UserControlled,
128    UnknownUnsafe,
129}
130
131impl CanisterControlClassV1 {
132    #[must_use]
133    pub const fn label(self) -> &'static str {
134        match self {
135            Self::DeploymentControlled => "DeploymentControlled",
136            Self::CanicManagedPool => "CanicManagedPool",
137            Self::ExternallyImported => "ExternallyImported",
138            Self::JointlyControlled => "JointlyControlled",
139            Self::UserControlled => "UserControlled",
140            Self::UnknownUnsafe => "UnknownUnsafe",
141        }
142    }
143}
144
145///
146/// ExpectedPoolCanisterV1
147///
148#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
149pub struct ExpectedPoolCanisterV1 {
150    pub pool: String,
151    pub canister_id: Option<String>,
152    pub role: Option<String>,
153}
154
155///
156/// ObservedPoolCanisterV1
157///
158#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
159pub struct ObservedPoolCanisterV1 {
160    pub pool: String,
161    pub canister_id: String,
162    pub role: Option<String>,
163    pub control_class: CanisterControlClassV1,
164}
165
166///
167/// LocalDeploymentConfigV1
168///
169#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
170pub struct LocalDeploymentConfigV1 {
171    pub config_path: Option<String>,
172    pub raw_config_sha256: Option<String>,
173    pub canonical_embedded_config_sha256: Option<String>,
174}
175
176///
177/// VerifierReadinessExpectationV1
178///
179#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
180pub struct VerifierReadinessExpectationV1 {
181    pub required: bool,
182    pub expected_role_epochs: Vec<RoleEpochExpectationV1>,
183}
184
185///
186/// VerifierReadinessObservationV1
187///
188#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
189pub struct VerifierReadinessObservationV1 {
190    pub status: ObservationStatusV1,
191    pub role_epochs: Vec<RoleEpochObservationV1>,
192}
193
194///
195/// RoleEpochExpectationV1
196///
197#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
198pub struct RoleEpochExpectationV1 {
199    pub role: String,
200    pub minimum_epoch: u64,
201}
202
203///
204/// RoleEpochObservationV1
205///
206#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
207pub struct RoleEpochObservationV1 {
208    pub role: String,
209    pub observed_epoch: Option<u64>,
210    pub status: ObservationStatusV1,
211}
212
213///
214/// DeploymentObservationGapV1
215///
216#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub struct DeploymentObservationGapV1 {
218    pub key: String,
219    pub description: String,
220}
221
222///
223/// ObservationStatusV1
224///
225#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
226pub enum ObservationStatusV1 {
227    NotObserved,
228    Observed,
229    Missing,
230    Inconclusive,
231}
232
233impl ObservationStatusV1 {
234    #[must_use]
235    pub const fn label(self) -> &'static str {
236        match self {
237            Self::NotObserved => "NotObserved",
238            Self::Observed => "Observed",
239            Self::Missing => "Missing",
240            Self::Inconclusive => "Inconclusive",
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn deployment_root_observation_source_owns_text_labels() {
251        assert_eq!(
252            DeploymentRootObservationSourceV1::IcpCanisterStatus.label(),
253            "IcpCanisterStatus"
254        );
255        assert_eq!(
256            DeploymentRootObservationSourceV1::LocalDeploymentState.label(),
257            "LocalDeploymentState"
258        );
259    }
260
261    #[test]
262    fn role_assignment_sources_own_wire_labels() {
263        let cases = [
264            (
265                RoleAssignmentSourceV1::IcpCanisterStatus,
266                "icp_canister_status",
267            ),
268            (
269                RoleAssignmentSourceV1::LocalInstallState,
270                "local_install_state",
271            ),
272            (RoleAssignmentSourceV1::SubnetRegistry, "subnet_registry"),
273            (
274                RoleAssignmentSourceV1::SubnetRegistryAndIcpCanisterStatus,
275                "subnet_registry+icp_canister_status",
276            ),
277        ];
278
279        for (source, expected) in cases {
280            assert_eq!(source.label(), expected);
281        }
282        assert!(RoleAssignmentSourceV1::label_includes_live_status(
283            RoleAssignmentSourceV1::IcpCanisterStatus.label()
284        ));
285        assert!(RoleAssignmentSourceV1::label_includes_live_status(
286            RoleAssignmentSourceV1::SubnetRegistryAndIcpCanisterStatus.label()
287        ));
288        assert!(!RoleAssignmentSourceV1::label_includes_live_status(
289            "custom_status_source"
290        ));
291    }
292
293    #[test]
294    fn canister_control_class_owns_text_labels() {
295        assert_eq!(
296            CanisterControlClassV1::DeploymentControlled.label(),
297            "DeploymentControlled"
298        );
299        assert_eq!(
300            CanisterControlClassV1::CanicManagedPool.label(),
301            "CanicManagedPool"
302        );
303        assert_eq!(
304            CanisterControlClassV1::ExternallyImported.label(),
305            "ExternallyImported"
306        );
307        assert_eq!(
308            CanisterControlClassV1::JointlyControlled.label(),
309            "JointlyControlled"
310        );
311        assert_eq!(
312            CanisterControlClassV1::UserControlled.label(),
313            "UserControlled"
314        );
315        assert_eq!(
316            CanisterControlClassV1::UnknownUnsafe.label(),
317            "UnknownUnsafe"
318        );
319    }
320
321    #[test]
322    fn observation_status_owns_text_labels() {
323        assert_eq!(ObservationStatusV1::NotObserved.label(), "NotObserved");
324        assert_eq!(ObservationStatusV1::Observed.label(), "Observed");
325        assert_eq!(ObservationStatusV1::Missing.label(), "Missing");
326        assert_eq!(ObservationStatusV1::Inconclusive.label(), "Inconclusive");
327    }
328}