1use super::artifact::ObservedArtifactV1;
2use super::plan::DeploymentIdentityV1;
3use serde::{Deserialize, Serialize};
4
5#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
180pub struct VerifierReadinessExpectationV1 {
181 pub required: bool,
182 pub expected_role_epochs: Vec<RoleEpochExpectationV1>,
183}
184
185#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
189pub struct VerifierReadinessObservationV1 {
190 pub status: ObservationStatusV1,
191 pub role_epochs: Vec<RoleEpochObservationV1>,
192}
193
194#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
198pub struct RoleEpochExpectationV1 {
199 pub role: String,
200 pub minimum_epoch: u64,
201}
202
203#[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#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub struct DeploymentObservationGapV1 {
218 pub key: String,
219 pub description: String,
220}
221
222#[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}