Skip to main content

canic_host/deployment_truth/model/inventory/
mod.rs

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