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    SubnetRegistry,
102    SubnetRegistryAndIcpCanisterStatus,
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::SubnetRegistry => "subnet_registry",
112            Self::SubnetRegistryAndIcpCanisterStatus => "subnet_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::SubnetRegistryAndIcpCanisterStatus.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            (RoleAssignmentSourceV1::SubnetRegistry, "subnet_registry"),
275            (
276                RoleAssignmentSourceV1::SubnetRegistryAndIcpCanisterStatus,
277                "subnet_registry+icp_canister_status",
278            ),
279        ];
280
281        for (source, expected) in cases {
282            assert_eq!(source.label(), expected);
283        }
284        assert!(RoleAssignmentSourceV1::label_includes_live_status(
285            RoleAssignmentSourceV1::IcpCanisterStatus.label()
286        ));
287        assert!(RoleAssignmentSourceV1::label_includes_live_status(
288            RoleAssignmentSourceV1::SubnetRegistryAndIcpCanisterStatus.label()
289        ));
290        assert!(!RoleAssignmentSourceV1::label_includes_live_status(
291            "custom_status_source"
292        ));
293    }
294
295    #[test]
296    fn canister_control_class_owns_text_labels() {
297        assert_eq!(
298            CanisterControlClassV1::DeploymentControlled.label(),
299            "DeploymentControlled"
300        );
301        assert_eq!(
302            CanisterControlClassV1::CanicManagedPool.label(),
303            "CanicManagedPool"
304        );
305        assert_eq!(
306            CanisterControlClassV1::ExternallyImported.label(),
307            "ExternallyImported"
308        );
309        assert_eq!(
310            CanisterControlClassV1::JointlyControlled.label(),
311            "JointlyControlled"
312        );
313        assert_eq!(
314            CanisterControlClassV1::UserControlled.label(),
315            "UserControlled"
316        );
317        assert_eq!(
318            CanisterControlClassV1::UnknownUnsafe.label(),
319            "UnknownUnsafe"
320        );
321    }
322
323    #[test]
324    fn observation_status_owns_text_labels() {
325        assert_eq!(ObservationStatusV1::NotObserved.label(), "NotObserved");
326        assert_eq!(ObservationStatusV1::Observed.label(), "Observed");
327        assert_eq!(ObservationStatusV1::Missing.label(), "Missing");
328        assert_eq!(ObservationStatusV1::Inconclusive.label(), "Inconclusive");
329    }
330}