1use super::artifact::ObservedArtifactV1;
2use super::plan::DeploymentIdentityV1;
3use canic_core::ids::{CanonicalNetworkId, FleetId};
4use serde::{Deserialize, Serialize};
5
6#[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#[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 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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
185pub struct VerifierReadinessExpectationV1 {
186 pub required: bool,
187 pub expected_role_epochs: Vec<RoleEpochExpectationV1>,
188}
189
190#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
194pub struct VerifierReadinessObservationV1 {
195 pub status: ObservationStatusV1,
196 pub role_epochs: Vec<RoleEpochObservationV1>,
197}
198
199#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct RoleEpochExpectationV1 {
204 pub role: String,
205 pub minimum_epoch: u64,
206}
207
208#[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
222pub struct DeploymentObservationGapV1 {
223 pub key: String,
224 pub description: String,
225}
226
227#[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}