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 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#[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 (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}