1use crate::{
8 cdk::types::Cycles,
9 config::{FleetServiceMemberPurpose, schema::ComponentChildKind},
10 dto::{
11 component_deployment::ProtectedComponentDeployment,
12 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
13 root_store::RootStoreBootstrapRequest,
14 },
15 ids::{
16 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentGroupMemberPath,
17 ComponentGroupPlacementId, ComponentInstanceId, ComponentSpecId, ComponentTopologyDigest,
18 FleetServiceId, FleetSubnetRootReleaseSet, ManagedCanisterBinding,
19 },
20 role_contract::ProtocolProfileDigest,
21};
22use candid::{CandidType, Principal};
23use serde::{Deserialize, Serialize};
24
25#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
32pub struct RootComponentRegistryPreparationRequest {
33 pub store_bootstrap: RootStoreBootstrapRequest,
34 pub expected_fleet_registry: FleetRegistryVersion,
35}
36
37#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
44pub struct RootComponentInitialInventoryStatus {
45 pub fleet_activation_operation_id: [u8; 32],
46 pub component_count: u32,
47 pub inventory_hash: [u8; 32],
48 pub sealed_at_ns: u64,
49 pub directories_converged: bool,
50 pub root_runtime_activated: bool,
51}
52
53#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
60pub struct RootComponentRegistryStatusResponse {
61 pub fleet_subnet_root: Principal,
62 pub prepared_against_registry: FleetRegistryVersion,
63 pub release_set: FleetSubnetRootReleaseSet,
64 pub component_topology_digest: ComponentTopologyDigest,
65 pub next_allocation_sequence: u64,
66 pub reserved_component_instances: u32,
67 pub committed_component_instances: u32,
68 pub managed_descendants: u32,
69 pub known_created_component_canisters: u32,
70 pub encoded_bytes: u64,
71 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
72}
73
74#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
81pub struct RootComponentAllocationRequest {
82 pub operation_id: [u8; 32],
83 pub component_spec: ComponentSpecId,
84}
85
86#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub enum PeerComponentRequester {
94 SameRoot,
95 FleetService {
96 service: FleetServiceId,
97 expected_registry: Box<FleetRegistryVersion>,
98 },
99}
100
101#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
108pub struct RootPeerComponentAllocationRequest {
109 pub operation_id: [u8; 32],
110 pub component_spec: ComponentSpecId,
111 pub requester: PeerComponentRequester,
112}
113
114#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
121pub struct RootComponentAllocationStatusRequest {
122 pub operation_id: [u8; 32],
123}
124
125#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
132pub struct RootComponentChildAllocationRequest {
133 pub operation_id: [u8; 32],
134 pub component: ComponentInstanceId,
135 pub expected_registry: ComponentRegistryHead,
136 pub child_role: CanisterRole,
137 pub application_init_args: Option<Vec<u8>>,
138}
139
140#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
147pub struct RootComponentChildAllocationStatusRequest {
148 pub operation_id: [u8; 32],
149 pub component: ComponentInstanceId,
150}
151
152#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
159pub struct RootComponentSubtreeRemovalRequest {
160 pub operation_id: [u8; 32],
161 pub component: ComponentInstanceId,
162 pub target_canister_id: Principal,
163 pub expected_registry: ComponentRegistryHead,
164}
165
166#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
173pub struct RootComponentSubtreeRemovalAdvanceRequest {
174 pub operation_id: [u8; 32],
175 pub component: ComponentInstanceId,
176 pub expected_traversal_steps: u32,
177}
178
179#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
186pub struct RootComponentSubtreeRemovalStopPreparationRequest {
187 pub operation_id: [u8; 32],
188 pub component: ComponentInstanceId,
189 pub expected_traversal_steps: u32,
190 pub expected_leaf_canister_id: Principal,
191 pub expected_leaf_parent_canister_id: Principal,
192}
193
194#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
201pub struct RootComponentSubtreeRemovalStopRequest {
202 pub operation_id: [u8; 32],
203 pub component: ComponentInstanceId,
204 pub expected_traversal_steps: u32,
205 pub expected_leaf_canister_id: Principal,
206 pub expected_leaf_parent_canister_id: Principal,
207}
208
209#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
216pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
217 pub operation_id: [u8; 32],
218 pub component: ComponentInstanceId,
219 pub expected_traversal_steps: u32,
220 pub expected_leaf_canister_id: Principal,
221 pub expected_leaf_parent_canister_id: Principal,
222}
223
224#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231pub struct RootComponentSubtreeRemovalDeleteRequest {
232 pub operation_id: [u8; 32],
233 pub component: ComponentInstanceId,
234 pub expected_traversal_steps: u32,
235 pub expected_leaf_canister_id: Principal,
236 pub expected_leaf_parent_canister_id: Principal,
237}
238
239#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
246pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
247 pub operation_id: [u8; 32],
248 pub component: ComponentInstanceId,
249 pub expected_traversal_steps: u32,
250 pub expected_leaf_canister_id: Principal,
251 pub expected_leaf_parent_canister_id: Principal,
252}
253
254#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
261pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
262 pub operation_id: [u8; 32],
263 pub component: ComponentInstanceId,
264 pub expected_traversal_steps: u32,
265 pub expected_leaf_canister_id: Principal,
266 pub expected_leaf_parent_canister_id: Principal,
267}
268
269#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
276pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
277 pub operation_id: [u8; 32],
278 pub component: ComponentInstanceId,
279 pub expected_traversal_steps: u32,
280 pub expected_leaf_canister_id: Principal,
281 pub expected_leaf_parent_canister_id: Principal,
282}
283
284#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
291pub struct RootComponentSubtreeRemovalStatusRequest {
292 pub operation_id: [u8; 32],
293 pub component: ComponentInstanceId,
294}
295
296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
303pub struct RootComponentDrainingRequest {
304 pub operation_id: [u8; 32],
305 pub component: ComponentInstanceId,
306 pub expected_registry: ComponentRegistryHead,
307}
308
309#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
316pub struct RootComponentDrainingStatusRequest {
317 pub operation_id: [u8; 32],
318 pub component: ComponentInstanceId,
319}
320
321#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
328pub struct RootComponentQuiescenceRequest {
329 pub operation_id: [u8; 32],
330 pub component: ComponentInstanceId,
331 pub expected_registry: ComponentRegistryHead,
332}
333
334#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
341pub struct RootComponentQuiescenceStatusRequest {
342 pub operation_id: [u8; 32],
343 pub component: ComponentInstanceId,
344}
345
346#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
353pub struct RootComponentDrainingAdvanceRequest {
354 pub operation_id: [u8; 32],
355 pub component: ComponentInstanceId,
356}
357
358#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct RootComponentFinalInventoryRequest {
366 pub operation_id: [u8; 32],
367 pub component: ComponentInstanceId,
368 pub expected_registry: ComponentRegistryHead,
369}
370
371#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
378pub struct RootComponentDeletionRequest {
379 pub operation_id: [u8; 32],
380 pub component: ComponentInstanceId,
381 pub expected_inventory_hash: [u8; 32],
382}
383
384#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
391pub struct RootComponentDeletionStatusRequest {
392 pub operation_id: [u8; 32],
393 pub component: ComponentInstanceId,
394}
395
396#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
403pub struct RootComponentChildCreationRequest {
404 pub operation_id: [u8; 32],
405 pub component: ComponentInstanceId,
406}
407
408#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
415pub struct RootComponentChildInstallRequest {
416 pub operation_id: [u8; 32],
417 pub component: ComponentInstanceId,
418}
419
420#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
427pub struct RootComponentChildCommitRequest {
428 pub operation_id: [u8; 32],
429 pub component: ComponentInstanceId,
430}
431
432#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
439pub struct RootComponentChildDirectoryPreparationRequest {
440 pub operation_id: [u8; 32],
441 pub component: ComponentInstanceId,
442}
443
444#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
451pub struct RootComponentChildRuntimeActivationRequest {
452 pub operation_id: [u8; 32],
453 pub component: ComponentInstanceId,
454}
455
456#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
463pub struct RootComponentChildMembershipActivationRequest {
464 pub operation_id: [u8; 32],
465 pub component: ComponentInstanceId,
466}
467
468#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
475pub struct RootComponentCreationRequest {
476 pub operation_id: [u8; 32],
477}
478
479#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
486pub struct RootComponentInstallRequest {
487 pub operation_id: [u8; 32],
488}
489
490#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
497pub struct RootComponentCommitRequest {
498 pub operation_id: [u8; 32],
499}
500
501#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
508pub struct RootComponentDirectoryPreparationRequest {
509 pub operation_id: [u8; 32],
510}
511
512#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
519pub struct RootComponentRuntimeActivationRequest {
520 pub operation_id: [u8; 32],
521}
522
523#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
530pub struct RootComponentMembershipActivationRequest {
531 pub operation_id: [u8; 32],
532}
533
534#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
541pub enum ComponentProvisioningOrigin {
542 FleetAdministrator {
543 caller: Principal,
544 },
545 Component {
546 requester: Box<ComponentBinding>,
547 grant: Box<crate::config::ComponentProvisioningGrant>,
548 },
549 FleetServiceComponent {
550 requester: Box<FleetServiceComponentRequester>,
551 registry: Box<FleetRegistryVersion>,
552 grant: Box<crate::config::ComponentProvisioningGrant>,
553 },
554 ComponentGroup {
555 operation_id: [u8; 32],
556 plan_hash: [u8; 32],
557 group_placement: ComponentGroupPlacementId,
558 member_path: ComponentGroupMemberPath,
559 },
560}
561
562#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
569pub struct FleetServiceComponentRequester {
570 pub service: FleetServiceId,
571 pub member_purpose: FleetServiceMemberPurpose,
572 pub group_placement: ComponentGroupPlacementId,
573 pub member_path: ComponentGroupMemberPath,
574 pub component: ComponentBinding,
575}
576
577#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
584pub enum RootComponentAllocationPhase {
585 Reserved,
586 CreationIntent,
587 Created,
588 InstallIntent,
589 Installed,
590 Verified,
591 Committed,
592 Removed,
593}
594
595#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
602#[expect(
603 clippy::large_enum_variant,
604 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
605)]
606pub enum RootComponentSubtreeRemovalPhase {
607 Fenced,
608 Traversing(RootComponentSubtreeRemovalNode),
609 LeafSelected(RootComponentSubtreeRemovalNode),
610 StopIntent(RootComponentSubtreeRemovalStopIntent),
611 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
612 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
613 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
614 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
615 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
616 Completed(RootComponentSubtreeRemovalCompletedReceipt),
617}
618
619#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
626pub struct RootComponentSubtreeRemovalNode {
627 pub canister_id: Principal,
628 pub parent_canister_id: Principal,
629 pub role: CanisterRole,
630 pub kind: ComponentChildKind,
631 pub installed_artifact_hash: [u8; 32],
632 pub status: ComponentLifecycleStatus,
633}
634
635#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub struct RootComponentSubtreeRemovalStopIntent {
643 pub leaf: RootComponentSubtreeRemovalNode,
644 pub controller: Principal,
645}
646
647#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
654pub struct RootComponentSubtreeRemovalStoppedReceipt {
655 pub stop: RootComponentSubtreeRemovalStopIntent,
656 pub observed_module_hash: [u8; 32],
657}
658
659#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
666pub struct RootComponentSubtreeRemovalDeleteIntent {
667 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
668}
669
670#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
677pub struct RootComponentSubtreeRemovalDeletedReceipt {
678 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
679}
680
681#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
688pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
689 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
690 pub removed_from_registry: ComponentRegistryHead,
691 pub previous_descendant_content_hash: [u8; 32],
692 pub previous_committed_descendants: u32,
693 pub registry: ComponentRegistryHead,
694 pub descendant_content_hash: [u8; 32],
695 pub registry_encoded_bytes: u64,
696 pub reserved_descendants: u32,
697 pub committed_descendants: u32,
698 pub directory_synchronized_at_ns: u64,
699 pub directory_authority_hash: [u8; 32],
700 pub parent_role_instances: u32,
701 pub root_managed_descendants: u32,
702 pub root_known_created_component_canisters: u32,
703}
704
705#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
712pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
713 pub operation_id: [u8; 32],
714 pub canister_id: Principal,
715 pub activation: ComponentRuntimeActivationEvidence,
716}
717
718#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
727pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
728 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
729 pub covered_fleet_registry_revision: u64,
730 pub covered_fleet_registry_content_hash: [u8; 32],
731 pub covered_component_registry: ComponentRegistryHead,
732 pub covered_authority_hash: [u8; 32],
733 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
734 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
735}
736
737#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
744pub struct RootComponentSubtreeRemovalCompletedReceipt {
745 pub registry: ComponentRegistryHead,
746 pub directory_authority_hash: [u8; 32],
747}
748
749#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
756pub enum ComponentLifecycleStatus {
757 Prepared,
758 Active,
759 Draining,
760 Removed,
761}
762
763#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
770pub struct ComponentRegistryHead {
771 pub component: ComponentInstanceId,
772 pub revision: u64,
773 pub content_hash: [u8; 32],
774}
775
776#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
783pub struct ComponentRegistryPartitionRequest {
784 pub component: ComponentInstanceId,
785}
786
787#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
794pub struct ComponentRegistryPartitionResponse {
795 pub head: ComponentRegistryHead,
796 pub binding: ComponentBinding,
797 pub protocol_profile_digest: ProtocolProfileDigest,
798 pub provisioning_origin: ComponentProvisioningOrigin,
799 pub release_set: FleetSubnetRootReleaseSet,
800 pub status: ComponentLifecycleStatus,
801 pub reserved_descendants: u32,
802 pub committed_descendants: u32,
803 pub encoded_bytes: u64,
804}
805
806#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
814pub struct ComponentRegistryActivePartitionRequest {
815 pub component: ComponentInstanceId,
816 pub provisioning_operation_id: [u8; 32],
817 pub plan_hash: [u8; 32],
818 pub group_placement: ComponentGroupPlacementId,
819 pub member_path: ComponentGroupMemberPath,
820}
821
822#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
830pub struct ComponentRegistryActivePartitionResponse {
831 pub allocation_operation_id: [u8; 32],
832 pub prepared: ComponentRegistryPartitionResponse,
833 pub activation: ComponentRegistryPartitionResponse,
834 pub current: ComponentRegistryPartitionResponse,
835}
836
837#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
844pub struct ComponentDirectoryProvenance {
845 pub component: ComponentBinding,
846 pub source_fleet_subnet_root: Principal,
847 pub component_registry_revision: u64,
848 pub component_registry_content_hash: [u8; 32],
849 pub synchronized_at_ns: u64,
850}
851
852#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
859pub struct ComponentDirectoryHead {
860 pub provenance: ComponentDirectoryProvenance,
861 pub descendant_count: u32,
862}
863
864#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
871pub struct ComponentDirectoryHeadRequest {
872 pub component: ComponentInstanceId,
873}
874
875#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
882pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
883
884#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
891pub struct ComponentDirectoryPageRequest {
892 pub directory: ComponentDirectoryHead,
893 pub parent_canister_id: Option<Principal>,
894 pub role: Option<CanisterRole>,
895 pub status: Option<ComponentLifecycleStatus>,
896 pub cursor: Option<ComponentDirectoryPageCursor>,
897 pub limit: u16,
898}
899
900#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
907pub struct ComponentDirectoryChildEntry {
908 pub binding: ComponentChildBinding,
909 pub kind: ComponentChildKind,
910 pub installed_artifact_hash: [u8; 32],
911 pub protocol_profile_digest: ProtocolProfileDigest,
912 pub status: ComponentLifecycleStatus,
913}
914
915#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
922pub struct ComponentDirectoryPageResponse {
923 pub directory: ComponentDirectoryHead,
924 pub entries: Vec<ComponentDirectoryChildEntry>,
925 pub next_cursor: Option<ComponentDirectoryPageCursor>,
926}
927
928#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
935pub struct ComponentRuntimeDirectoryAuthority {
936 pub fleet: FleetDirectorySnapshot,
937 pub component: ComponentDirectoryHead,
938 pub component_group: Option<crate::dto::component_provisioning::ComponentGroupDirectory>,
939}
940
941#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
948pub struct ComponentRuntimeDirectChild {
949 pub canister_id: Principal,
950 pub role: CanisterRole,
951 pub protocol_profile_digest: ProtocolProfileDigest,
952}
953
954#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
961pub struct ComponentRuntimeDirectoryPreparationRequest {
962 pub operation_id: [u8; 32],
963 pub authority: ComponentRuntimeDirectoryAuthority,
964 pub direct_children: Vec<ComponentRuntimeDirectChild>,
965}
966
967#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
974pub struct ComponentRuntimeDirectorySynchronizationRequest {
975 pub operation_id: [u8; 32],
976 pub authority: ComponentRuntimeDirectoryAuthority,
977 pub direct_children: Vec<ComponentRuntimeDirectChild>,
978}
979
980#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
987pub enum ComponentRuntimePhase {
988 AwaitingDirectory,
989 DirectoryPrepared,
990 Active,
991}
992
993#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1000pub struct ComponentRuntimeActivationEvidence {
1001 pub directory_authority_hash: [u8; 32],
1002 pub activated_at_ns: u64,
1003}
1004
1005#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1012pub struct ComponentRuntimeActivationRequest {
1013 pub operation_id: [u8; 32],
1014 pub directory_authority_hash: [u8; 32],
1015}
1016
1017#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1024pub struct ComponentRuntimeStatusResponse {
1025 pub operation_id: [u8; 32],
1026 pub binding: ManagedCanisterBinding,
1027 pub deployment: Box<ProtectedComponentDeployment>,
1028 pub phase: ComponentRuntimePhase,
1029 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
1030 pub authority_hash: Option<[u8; 32]>,
1031 pub direct_children_hash: Option<[u8; 32]>,
1032 pub activation: Option<ComponentRuntimeActivationEvidence>,
1033}
1034
1035#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1042pub struct ComponentRuntimeDirectoryConvergenceEvidence {
1043 pub operation_id: [u8; 32],
1044 pub binding: ManagedCanisterBinding,
1045 pub covered_authority: ComponentRuntimeDirectoryAuthority,
1046 pub covered_authority_hash: [u8; 32],
1047 pub activation: ComponentRuntimeActivationEvidence,
1048}
1049
1050#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1057pub struct RootComponentCreationEvidence {
1058 pub wasm_store: Principal,
1059 pub payload_hash: [u8; 32],
1060 pub payload_size_bytes: u64,
1061 pub initial_cycles: Cycles,
1062 pub controller: Principal,
1063 pub canister: Option<Principal>,
1064}
1065
1066#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1073pub struct RootComponentInstallEvidence {
1074 pub raw_module_hash: [u8; 32],
1075 pub chunk_hashes: Vec<Vec<u8>>,
1076 pub binding: ComponentBinding,
1077}
1078
1079#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1086pub struct RootComponentChildInstallEvidence {
1087 pub raw_module_hash: [u8; 32],
1088 pub chunk_hashes: Vec<Vec<u8>>,
1089 pub binding: ComponentChildBinding,
1090}
1091
1092#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1099pub struct RootComponentAllocationResponse {
1100 pub operation_id: [u8; 32],
1101 pub allocation_sequence: u64,
1102 pub component: ComponentInstanceId,
1103 pub component_spec: ComponentSpecId,
1104 pub spec_hash: [u8; 32],
1105 pub role: CanisterRole,
1106 pub provisioning_origin: ComponentProvisioningOrigin,
1107 pub release_set: FleetSubnetRootReleaseSet,
1108 pub phase: RootComponentAllocationPhase,
1109 pub creation: Option<RootComponentCreationEvidence>,
1110 pub installation: Option<RootComponentInstallEvidence>,
1111}
1112
1113#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1120pub struct RootComponentChildAllocationResponse {
1121 pub operation_id: [u8; 32],
1122 pub component: ComponentInstanceId,
1123 pub parent_canister_id: Principal,
1124 pub parent_role: CanisterRole,
1125 pub child_role: CanisterRole,
1126 pub child_kind: ComponentChildKind,
1127 pub maximum_instances_per_parent: u32,
1128 pub maximum_descendants: u32,
1129 pub maximum_registry_bytes: u64,
1130 pub reserved_against_registry: ComponentRegistryHead,
1131 pub release_set: FleetSubnetRootReleaseSet,
1132 pub phase: RootComponentAllocationPhase,
1133 pub creation: Option<RootComponentCreationEvidence>,
1134 pub installation: Option<RootComponentChildInstallEvidence>,
1135}
1136
1137#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1144pub struct RootComponentSubtreeRemovalResponse {
1145 pub operation_id: [u8; 32],
1146 pub component: ComponentInstanceId,
1147 pub target_canister_id: Principal,
1148 pub target_parent_canister_id: Principal,
1149 pub target_role: CanisterRole,
1150 pub target_status: ComponentLifecycleStatus,
1151 pub reserved_against_registry: ComponentRegistryHead,
1152 pub maximum_completed_leaves: u32,
1153 pub completed_leaves: u32,
1154 pub traversal_steps: u32,
1155 pub phase: RootComponentSubtreeRemovalPhase,
1156}
1157
1158#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1165pub struct RootComponentDrainingResponse {
1166 pub operation_id: [u8; 32],
1167 pub component: ComponentInstanceId,
1168 pub previous_registry: ComponentRegistryHead,
1169 pub registry: ComponentRegistryHead,
1170 pub descendant_count: u32,
1171 pub descendant_content_hash: [u8; 32],
1172 pub directory_authority_hash: [u8; 32],
1173 pub started_at_ns: u64,
1174}
1175
1176#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1183pub struct RootComponentQuiescenceStopIntent {
1184 pub registry: ComponentRegistryHead,
1185 pub descendant_count: u32,
1186 pub descendant_content_hash: [u8; 32],
1187 pub canister_id: Principal,
1188 pub controller: Principal,
1189 pub expected_module_hash: [u8; 32],
1190 pub covered_fleet_registry_revision: u64,
1191 pub covered_fleet_registry_content_hash: [u8; 32],
1192 pub covered_authority_hash: [u8; 32],
1193 pub runtime_operation_id: [u8; 32],
1194 pub activation: ComponentRuntimeActivationEvidence,
1195 pub prepared_at_ns: u64,
1196}
1197
1198#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1205pub struct RootComponentQuiescentReceipt {
1206 pub stop: RootComponentQuiescenceStopIntent,
1207 pub observed_module_hash: [u8; 32],
1208 pub quiesced_at_ns: u64,
1209}
1210
1211#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1218pub enum RootComponentQuiescencePhase {
1219 StopIntent(RootComponentQuiescenceStopIntent),
1220 Quiescent(RootComponentQuiescentReceipt),
1221}
1222
1223#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1230pub struct RootComponentQuiescenceResponse {
1231 pub operation_id: [u8; 32],
1232 pub component: ComponentInstanceId,
1233 pub phase: RootComponentQuiescencePhase,
1234}
1235
1236#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1243pub struct RootComponentDrainingDescendantsEmpty {
1244 pub registry: ComponentRegistryHead,
1245 pub descendant_content_hash: [u8; 32],
1246}
1247
1248#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1255#[expect(
1256 clippy::large_enum_variant,
1257 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1258)]
1259pub enum RootComponentDrainingAdvancePhase {
1260 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1261 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1262}
1263
1264#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1271pub struct RootComponentDrainingAdvanceResponse {
1272 pub operation_id: [u8; 32],
1273 pub component: ComponentInstanceId,
1274 pub phase: RootComponentDrainingAdvancePhase,
1275}
1276
1277#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1284pub struct RootComponentFinalInventory {
1285 pub registry: ComponentRegistryHead,
1286 pub descendant_content_hash: [u8; 32],
1287 pub registry_encoded_bytes: u64,
1288 pub directory_synchronized_at_ns: u64,
1289 pub covered_fleet_registry_revision: u64,
1290 pub covered_fleet_registry_content_hash: [u8; 32],
1291 pub directory_authority_hash: [u8; 32],
1292 pub inventory_hash: [u8; 32],
1293 pub finalized_at_ns: u64,
1294}
1295
1296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1303pub struct RootComponentFinalInventoryResponse {
1304 pub operation_id: [u8; 32],
1305 pub component: ComponentInstanceId,
1306 pub inventory: RootComponentFinalInventory,
1307}
1308
1309#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1316pub struct RootComponentDeletionIntent {
1317 pub final_inventory: RootComponentFinalInventory,
1318 pub quiescence: RootComponentQuiescentReceipt,
1319 pub prepared_at_ns: u64,
1320}
1321
1322#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1329pub struct RootComponentDeletedReceipt {
1330 pub deletion: RootComponentDeletionIntent,
1331 pub deleted_at_ns: u64,
1332}
1333
1334#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1341pub struct RootComponentMembershipRemovedReceipt {
1342 pub deleted: RootComponentDeletedReceipt,
1343 pub allocation_operation_id: [u8; 32],
1344 pub remaining_spec_committed_instances: u32,
1345 pub root_committed_component_instances: u32,
1346 pub root_known_created_component_canisters: u32,
1347 pub root_registry_encoded_bytes: u64,
1348 pub removed_at_ns: u64,
1349 pub removal_hash: [u8; 32],
1350}
1351
1352#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1359pub enum RootComponentDeletionPhase {
1360 DeleteIntent(RootComponentDeletionIntent),
1361 Deleted(RootComponentDeletedReceipt),
1362 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1363}
1364
1365#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1372pub struct RootComponentDeletionResponse {
1373 pub operation_id: [u8; 32],
1374 pub component: ComponentInstanceId,
1375 pub phase: RootComponentDeletionPhase,
1376}
1377
1378#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1385pub struct RootComponentChildCommitResponse {
1386 pub allocation: RootComponentChildAllocationResponse,
1387 pub registry: ComponentRegistryPartitionResponse,
1388 pub directory: ComponentDirectoryHead,
1389}
1390
1391#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1398pub struct RootComponentChildDirectoryPreparationResponse {
1399 pub committed: RootComponentChildCommitResponse,
1400 pub child: ComponentRuntimeStatusResponse,
1401 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1402 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1403}
1404
1405#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1412pub struct RootComponentChildRuntimeActivationResponse {
1413 pub committed: RootComponentChildCommitResponse,
1414 pub child: ComponentRuntimeStatusResponse,
1415}
1416
1417#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1424pub struct RootComponentChildMembershipActivationResponse {
1425 pub committed: RootComponentChildCommitResponse,
1426 pub registry: ComponentRegistryPartitionResponse,
1427 pub directory: ComponentDirectoryHead,
1428 pub child: ComponentRuntimeStatusResponse,
1429}
1430
1431#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1438pub struct RootComponentCommitResponse {
1439 pub allocation: RootComponentAllocationResponse,
1440 pub registry: ComponentRegistryPartitionResponse,
1441 pub directory: ComponentDirectoryHead,
1442}
1443
1444#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1451pub struct RootComponentDirectoryPreparationResponse {
1452 pub committed: RootComponentCommitResponse,
1453 pub target: ComponentRuntimeStatusResponse,
1454}
1455
1456#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1463pub struct RootComponentRuntimeActivationResponse {
1464 pub committed: RootComponentCommitResponse,
1465 pub target: ComponentRuntimeStatusResponse,
1466}
1467
1468#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1475pub struct RootComponentMembershipActivationResponse {
1476 pub allocation: RootComponentAllocationResponse,
1477 pub registry: ComponentRegistryPartitionResponse,
1478 pub directory: ComponentDirectoryHead,
1479 pub target: ComponentRuntimeStatusResponse,
1480}
1481
1482#[cfg(test)]
1483mod tests {
1484 use super::*;
1485 use crate::{
1486 dto::root_store::RootStoreBootstrapRequest,
1487 ids::{
1488 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1489 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1490 },
1491 };
1492
1493 #[test]
1494 fn component_registry_contracts_round_trip_through_candid() {
1495 let request = RootComponentRegistryPreparationRequest {
1496 store_bootstrap: RootStoreBootstrapRequest {
1497 operation_id: [8; 32],
1498 manifest_payload_size_bytes: 128,
1499 },
1500 expected_fleet_registry: FleetRegistryVersion {
1501 authority: fleet_registry_authority(),
1502 revision: 4,
1503 content_hash: [5; 32],
1504 },
1505 };
1506 let response = RootComponentRegistryStatusResponse {
1507 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1508 prepared_against_registry: request.expected_fleet_registry.clone(),
1509 release_set: FleetSubnetRootReleaseSet {
1510 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1511 [7; 32],
1512 )),
1513 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1514 },
1515 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1516 next_allocation_sequence: 1,
1517 reserved_component_instances: 0,
1518 committed_component_instances: 0,
1519 managed_descendants: 0,
1520 known_created_component_canisters: 0,
1521 encoded_bytes: 0,
1522 initial_inventory: Some(RootComponentInitialInventoryStatus {
1523 fleet_activation_operation_id: [10; 32],
1524 component_count: 0,
1525 inventory_hash: [11; 32],
1526 sealed_at_ns: 12,
1527 directories_converged: true,
1528 root_runtime_activated: true,
1529 }),
1530 };
1531 let allocation = RootComponentAllocationResponse {
1532 operation_id: [10; 32],
1533 allocation_sequence: 1,
1534 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1535 component_spec: "projects".parse().expect("Component Spec ID"),
1536 spec_hash: [12; 32],
1537 role: CanisterRole::new("project_hub"),
1538 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1539 caller: Principal::from_slice(&[13; 29]),
1540 },
1541 release_set: response.release_set,
1542 phase: RootComponentAllocationPhase::Reserved,
1543 creation: None,
1544 installation: None,
1545 };
1546 let created = RootComponentAllocationResponse {
1547 phase: RootComponentAllocationPhase::Created,
1548 creation: Some(RootComponentCreationEvidence {
1549 wasm_store: Principal::from_slice(&[14; 29]),
1550 payload_hash: [15; 32],
1551 payload_size_bytes: 4_096,
1552 initial_cycles: Cycles::new(5_000_000_000_000),
1553 controller: Principal::from_slice(&[6; 29]),
1554 canister: Some(Principal::from_slice(&[16; 29])),
1555 }),
1556 installation: None,
1557 ..allocation.clone()
1558 };
1559 let request_bytes = candid::encode_one(&request).expect("encode request");
1560 let response_bytes = candid::encode_one(&response).expect("encode response");
1561 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1562 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1563
1564 assert_eq!(
1565 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1566 .expect("decode request"),
1567 request
1568 );
1569 assert_eq!(
1570 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1571 .expect("decode response"),
1572 response
1573 );
1574 assert_eq!(
1575 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1576 .expect("decode allocation"),
1577 allocation
1578 );
1579 assert_eq!(
1580 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1581 .expect("decode created allocation"),
1582 created
1583 );
1584 }
1585
1586 #[test]
1587 fn component_commit_response_round_trips_through_candid() {
1588 let root = Principal::from_slice(&[6; 29]);
1589 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1590 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1591 let release_set = FleetSubnetRootReleaseSet {
1592 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1593 [7; 32],
1594 )),
1595 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1596 };
1597 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1598 caller: Principal::from_slice(&[13; 29]),
1599 };
1600 let binding = ComponentBinding {
1601 authority: fleet_registry_authority(),
1602 component,
1603 component_spec: component_spec.clone(),
1604 spec_hash: [12; 32],
1605 role: CanisterRole::new("project_hub"),
1606 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1607 fleet_subnet_root: root,
1608 canister_id: Principal::from_slice(&[16; 29]),
1609 };
1610 let head = ComponentRegistryHead {
1611 component,
1612 revision: 1,
1613 content_hash: [18; 32],
1614 };
1615 let committed = RootComponentCommitResponse {
1616 allocation: RootComponentAllocationResponse {
1617 operation_id: [10; 32],
1618 allocation_sequence: 1,
1619 component,
1620 component_spec,
1621 spec_hash: binding.spec_hash,
1622 role: binding.role.clone(),
1623 provisioning_origin: provisioning_origin.clone(),
1624 release_set,
1625 phase: RootComponentAllocationPhase::Committed,
1626 creation: Some(RootComponentCreationEvidence {
1627 wasm_store: Principal::from_slice(&[14; 29]),
1628 payload_hash: [15; 32],
1629 payload_size_bytes: 4_096,
1630 initial_cycles: Cycles::new(5_000_000_000_000),
1631 controller: root,
1632 canister: Some(binding.canister_id),
1633 }),
1634 installation: Some(RootComponentInstallEvidence {
1635 raw_module_hash: [20; 32],
1636 chunk_hashes: vec![vec![21; 32]],
1637 binding: binding.clone(),
1638 }),
1639 },
1640 registry: ComponentRegistryPartitionResponse {
1641 head: head.clone(),
1642 binding: binding.clone(),
1643 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1644 provisioning_origin,
1645 release_set,
1646 status: ComponentLifecycleStatus::Prepared,
1647 reserved_descendants: 0,
1648 committed_descendants: 0,
1649 encoded_bytes: 2_048,
1650 },
1651 directory: ComponentDirectoryHead {
1652 provenance: ComponentDirectoryProvenance {
1653 component: binding,
1654 source_fleet_subnet_root: root,
1655 component_registry_revision: head.revision,
1656 component_registry_content_hash: head.content_hash,
1657 synchronized_at_ns: 19,
1658 },
1659 descendant_count: 0,
1660 },
1661 };
1662 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1663
1664 assert_eq!(
1665 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1666 .expect("decode committed allocation"),
1667 committed
1668 );
1669 }
1670
1671 #[test]
1672 fn component_directory_page_contracts_round_trip_through_candid() {
1673 let root = Principal::from_slice(&[6; 29]);
1674 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1675 let binding = ComponentBinding {
1676 authority: fleet_registry_authority(),
1677 component,
1678 component_spec: "projects".parse().expect("Component Spec ID"),
1679 spec_hash: [12; 32],
1680 role: CanisterRole::new("project_hub"),
1681 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1682 fleet_subnet_root: root,
1683 canister_id: Principal::from_slice(&[16; 29]),
1684 };
1685 let directory = ComponentDirectoryHead {
1686 provenance: ComponentDirectoryProvenance {
1687 component: binding.clone(),
1688 source_fleet_subnet_root: root,
1689 component_registry_revision: 3,
1690 component_registry_content_hash: [18; 32],
1691 synchronized_at_ns: 19,
1692 },
1693 descendant_count: 1,
1694 };
1695 let request = ComponentDirectoryPageRequest {
1696 directory: directory.clone(),
1697 parent_canister_id: Some(binding.canister_id),
1698 role: Some(CanisterRole::new("project_instance")),
1699 status: Some(ComponentLifecycleStatus::Active),
1700 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1701 limit: 50,
1702 };
1703 let response = ComponentDirectoryPageResponse {
1704 directory,
1705 entries: vec![ComponentDirectoryChildEntry {
1706 binding: ComponentChildBinding {
1707 component: binding.clone(),
1708 parent_canister_id: binding.canister_id,
1709 role: CanisterRole::new("project_instance"),
1710 canister_id: Principal::from_slice(&[21; 29]),
1711 },
1712 kind: ComponentChildKind::Instance,
1713 installed_artifact_hash: [22; 32],
1714 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1715 status: ComponentLifecycleStatus::Active,
1716 }],
1717 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1718 };
1719 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1720 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1721
1722 assert_eq!(
1723 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1724 .expect("decode Directory page request"),
1725 request
1726 );
1727 assert_eq!(
1728 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1729 .expect("decode Directory page response"),
1730 response
1731 );
1732 }
1733
1734 fn fleet_registry_authority() -> FleetRegistryAuthority {
1735 FleetRegistryAuthority {
1736 binding: FleetCoordinatorBinding {
1737 fleet: crate::ids::FleetBinding {
1738 fleet: FleetKey {
1739 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1740 fleet_id: FleetId::from_generated_bytes([1; 32]),
1741 },
1742 app: AppId::from("toko"),
1743 },
1744 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1745 coordinator: Principal::from_slice(&[3; 29]),
1746 },
1747 epoch: 1,
1748 }
1749 }
1750
1751 #[test]
1752 fn component_creation_request_round_trips_through_candid() {
1753 let request = RootComponentCreationRequest {
1754 operation_id: [10; 32],
1755 };
1756 let bytes = candid::encode_one(request).expect("encode creation request");
1757
1758 assert_eq!(
1759 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1760 .expect("decode creation request"),
1761 request
1762 );
1763 }
1764
1765 #[test]
1766 fn peer_component_provisioning_origin_round_trips_through_candid() {
1767 let authority = fleet_registry_authority();
1768 let requester_spec: ComponentSpecId =
1769 "projects".parse().expect("requester Component Spec ID");
1770 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1771 let origin = ComponentProvisioningOrigin::Component {
1772 requester: Box::new(ComponentBinding {
1773 authority,
1774 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1775 component_spec: requester_spec.clone(),
1776 spec_hash: [21; 32],
1777 role: CanisterRole::new("project_hub"),
1778 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1779 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1780 canister_id: Principal::from_slice(&[24; 29]),
1781 }),
1782 grant: Box::new(crate::config::ComponentProvisioningGrant {
1783 requester_component_spec: requester_spec,
1784 target_component_spec: target_spec,
1785 maximum_instances_per_requester_per_root: 3,
1786 }),
1787 };
1788 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1789
1790 assert_eq!(
1791 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1792 .expect("decode peer provisioning origin"),
1793 origin
1794 );
1795 }
1796
1797 #[test]
1798 fn cross_root_peer_request_and_origin_round_trip_through_candid() {
1799 let authority = fleet_registry_authority();
1800 let requester_spec: ComponentSpecId =
1801 "projects".parse().expect("requester Component Spec ID");
1802 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1803 let registry = FleetRegistryVersion {
1804 authority: authority.clone(),
1805 revision: 7,
1806 content_hash: [30; 32],
1807 };
1808 let request = RootPeerComponentAllocationRequest {
1809 operation_id: [31; 32],
1810 component_spec: target_spec.clone(),
1811 requester: PeerComponentRequester::FleetService {
1812 service: "projects".parse().expect("Fleet service ID"),
1813 expected_registry: Box::new(registry.clone()),
1814 },
1815 };
1816 let origin = ComponentProvisioningOrigin::FleetServiceComponent {
1817 requester: Box::new(FleetServiceComponentRequester {
1818 service: "projects".parse().expect("Fleet service ID"),
1819 member_purpose: FleetServiceMemberPurpose::PoolMember,
1820 group_placement: ComponentGroupPlacementId {
1821 deployment: "project_hubs".parse().expect("deployment ID"),
1822 ordinal: 2,
1823 },
1824 member_path: ComponentGroupMemberPath::try_from(vec![
1825 "hub".parse().expect("member ID"),
1826 ])
1827 .expect("member path"),
1828 component: ComponentBinding {
1829 authority,
1830 component: ComponentInstanceId::from_generated_bytes([32; 32]),
1831 component_spec: requester_spec.clone(),
1832 spec_hash: [33; 32],
1833 role: CanisterRole::new("project_hub"),
1834 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[34; 29])),
1835 fleet_subnet_root: Principal::from_slice(&[35; 29]),
1836 canister_id: Principal::from_slice(&[36; 29]),
1837 },
1838 }),
1839 registry: Box::new(registry),
1840 grant: Box::new(crate::config::ComponentProvisioningGrant {
1841 requester_component_spec: requester_spec,
1842 target_component_spec: target_spec,
1843 maximum_instances_per_requester_per_root: 3,
1844 }),
1845 };
1846 let request_bytes = candid::encode_one(&request).expect("encode cross-root request");
1847 let origin_bytes = candid::encode_one(&origin).expect("encode cross-root origin");
1848
1849 assert_eq!(
1850 candid::decode_one::<RootPeerComponentAllocationRequest>(&request_bytes)
1851 .expect("decode cross-root request"),
1852 request
1853 );
1854 assert_eq!(
1855 candid::decode_one::<ComponentProvisioningOrigin>(&origin_bytes)
1856 .expect("decode cross-root origin"),
1857 origin
1858 );
1859 }
1860
1861 #[test]
1862 fn component_group_provisioning_origin_round_trips_through_candid() {
1863 let origin = ComponentProvisioningOrigin::ComponentGroup {
1864 operation_id: [25; 32],
1865 plan_hash: [26; 32],
1866 group_placement: ComponentGroupPlacementId {
1867 deployment: "cells".parse().expect("deployment ID"),
1868 ordinal: 3,
1869 },
1870 member_path: ComponentGroupMemberPath::try_from(vec![
1871 "api".parse().expect("member ID"),
1872 ])
1873 .expect("member path"),
1874 };
1875 let bytes = candid::encode_one(&origin).expect("encode group provisioning origin");
1876
1877 assert_eq!(
1878 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1879 .expect("decode group provisioning origin"),
1880 origin
1881 );
1882 }
1883
1884 #[test]
1885 #[expect(
1886 clippy::too_many_lines,
1887 reason = "one Candid contract test covers every subtree-removal phase receipt"
1888 )]
1889 fn component_subtree_removal_contracts_round_trip_through_candid() {
1890 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1891 let registry = ComponentRegistryHead {
1892 component,
1893 revision: 7,
1894 content_hash: [42; 32],
1895 };
1896 let request = RootComponentSubtreeRemovalRequest {
1897 operation_id: [43; 32],
1898 component,
1899 target_canister_id: Principal::from_slice(&[44; 29]),
1900 expected_registry: registry.clone(),
1901 };
1902 let status_request = RootComponentSubtreeRemovalStatusRequest {
1903 operation_id: request.operation_id,
1904 component,
1905 };
1906 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1907 operation_id: request.operation_id,
1908 component,
1909 expected_traversal_steps: 1,
1910 };
1911 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1912 operation_id: request.operation_id,
1913 component,
1914 expected_traversal_steps: 2,
1915 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1916 expected_leaf_parent_canister_id: request.target_canister_id,
1917 };
1918 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1919 observed_module_hash: [49; 32],
1920 stop: RootComponentSubtreeRemovalStopIntent {
1921 controller: Principal::from_slice(&[48; 29]),
1922 leaf: RootComponentSubtreeRemovalNode {
1923 canister_id: Principal::from_slice(&[46; 29]),
1924 parent_canister_id: request.target_canister_id,
1925 role: CanisterRole::new("project_ledger"),
1926 kind: ComponentChildKind::Singleton,
1927 installed_artifact_hash: [47; 32],
1928 status: ComponentLifecycleStatus::Active,
1929 },
1930 },
1931 };
1932 let response = RootComponentSubtreeRemovalResponse {
1933 operation_id: request.operation_id,
1934 component,
1935 target_canister_id: request.target_canister_id,
1936 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1937 target_role: CanisterRole::new("project_instance"),
1938 target_status: ComponentLifecycleStatus::Active,
1939 reserved_against_registry: registry,
1940 maximum_completed_leaves: 4,
1941 completed_leaves: 1,
1942 traversal_steps: 2,
1943 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1944 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1945 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1946 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1947 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1948 },
1949 removed_from_registry: ComponentRegistryHead {
1950 component,
1951 revision: 8,
1952 content_hash: [50; 32],
1953 },
1954 previous_descendant_content_hash: [51; 32],
1955 previous_committed_descendants: 4,
1956 registry: ComponentRegistryHead {
1957 component,
1958 revision: 9,
1959 content_hash: [52; 32],
1960 },
1961 descendant_content_hash: [53; 32],
1962 registry_encoded_bytes: 4_096,
1963 reserved_descendants: 1,
1964 committed_descendants: 3,
1965 directory_synchronized_at_ns: 54,
1966 directory_authority_hash: [55; 32],
1967 parent_role_instances: 0,
1968 root_managed_descendants: 4,
1969 root_known_created_component_canisters: 4,
1970 },
1971 covered_fleet_registry_revision: 6,
1972 covered_fleet_registry_content_hash: [56; 32],
1973 covered_component_registry: ComponentRegistryHead {
1974 component,
1975 revision: 9,
1976 content_hash: [52; 32],
1977 },
1978 covered_authority_hash: [55; 32],
1979 owning_component: Some(
1980 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1981 operation_id: [57; 32],
1982 canister_id: Principal::from_slice(&[58; 29]),
1983 activation: ComponentRuntimeActivationEvidence {
1984 directory_authority_hash: [59; 32],
1985 activated_at_ns: 60,
1986 },
1987 },
1988 ),
1989 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1990 operation_id: [61; 32],
1991 canister_id: request.target_canister_id,
1992 activation: ComponentRuntimeActivationEvidence {
1993 directory_authority_hash: [62; 32],
1994 activated_at_ns: 63,
1995 },
1996 }),
1997 },
1998 ),
1999 };
2000
2001 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
2002 let advance_bytes =
2003 candid::encode_one(advance_request).expect("encode subtree removal advance request");
2004 let stop_bytes =
2005 candid::encode_one(stop_request).expect("encode subtree removal stop request");
2006 let status_bytes =
2007 candid::encode_one(status_request).expect("encode subtree removal status request");
2008 let response_bytes =
2009 candid::encode_one(&response).expect("encode subtree removal response");
2010
2011 assert_eq!(
2012 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
2013 .expect("decode subtree removal request"),
2014 request
2015 );
2016 assert_eq!(
2017 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
2018 .expect("decode subtree removal advance request"),
2019 advance_request
2020 );
2021 assert_eq!(
2022 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
2023 .expect("decode subtree removal stop request"),
2024 stop_request
2025 );
2026 assert_eq!(
2027 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
2028 .expect("decode subtree removal status request"),
2029 status_request
2030 );
2031 assert_eq!(
2032 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
2033 .expect("decode subtree removal response"),
2034 response
2035 );
2036
2037 let mut quiescent_owner_response = response;
2038 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
2039 &mut quiescent_owner_response.phase
2040 else {
2041 panic!("Directory-synchronized response");
2042 };
2043 receipt.owning_component = None;
2044 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
2045 .expect("encode quiescent-owner subtree response");
2046 assert_eq!(
2047 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
2048 .expect("decode quiescent-owner subtree response"),
2049 quiescent_owner_response
2050 );
2051 }
2052
2053 #[test]
2054 fn component_draining_contracts_round_trip_through_candid() {
2055 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
2056 let previous_registry = ComponentRegistryHead {
2057 component,
2058 revision: 7,
2059 content_hash: [61; 32],
2060 };
2061 let request = RootComponentDrainingRequest {
2062 operation_id: [62; 32],
2063 component,
2064 expected_registry: previous_registry.clone(),
2065 };
2066 let status_request = RootComponentDrainingStatusRequest {
2067 operation_id: request.operation_id,
2068 component,
2069 };
2070 let response = RootComponentDrainingResponse {
2071 operation_id: request.operation_id,
2072 component,
2073 previous_registry,
2074 registry: ComponentRegistryHead {
2075 component,
2076 revision: 8,
2077 content_hash: [63; 32],
2078 },
2079 descendant_count: 20_000,
2080 descendant_content_hash: [64; 32],
2081 directory_authority_hash: [65; 32],
2082 started_at_ns: 66,
2083 };
2084
2085 let request_bytes =
2086 candid::encode_one(&request).expect("encode Component draining request");
2087 let status_bytes =
2088 candid::encode_one(status_request).expect("encode Component draining status request");
2089 let response_bytes =
2090 candid::encode_one(&response).expect("encode Component draining response");
2091
2092 assert_eq!(
2093 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
2094 .expect("decode Component draining request"),
2095 request
2096 );
2097 assert_eq!(
2098 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
2099 .expect("decode Component draining status request"),
2100 status_request
2101 );
2102 assert_eq!(
2103 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
2104 .expect("decode Component draining response"),
2105 response
2106 );
2107 }
2108
2109 #[test]
2110 fn component_quiescence_contracts_round_trip_through_candid() {
2111 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
2112 let registry = ComponentRegistryHead {
2113 component,
2114 revision: 9,
2115 content_hash: [68; 32],
2116 };
2117 let request = RootComponentQuiescenceRequest {
2118 operation_id: [69; 32],
2119 component,
2120 expected_registry: registry.clone(),
2121 };
2122 let status_request = RootComponentQuiescenceStatusRequest {
2123 operation_id: request.operation_id,
2124 component,
2125 };
2126 let stop = RootComponentQuiescenceStopIntent {
2127 registry,
2128 descendant_count: 20_000,
2129 descendant_content_hash: [70; 32],
2130 canister_id: Principal::from_slice(&[71; 29]),
2131 controller: Principal::from_slice(&[72; 29]),
2132 expected_module_hash: [73; 32],
2133 covered_fleet_registry_revision: 10,
2134 covered_fleet_registry_content_hash: [74; 32],
2135 covered_authority_hash: [75; 32],
2136 runtime_operation_id: [76; 32],
2137 activation: ComponentRuntimeActivationEvidence {
2138 directory_authority_hash: [77; 32],
2139 activated_at_ns: 78,
2140 },
2141 prepared_at_ns: 79,
2142 };
2143 let response = RootComponentQuiescenceResponse {
2144 operation_id: request.operation_id,
2145 component,
2146 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
2147 stop,
2148 observed_module_hash: [73; 32],
2149 quiesced_at_ns: 80,
2150 }),
2151 };
2152
2153 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
2154 let status_bytes =
2155 candid::encode_one(status_request).expect("encode quiescence status request");
2156 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
2157 assert_eq!(
2158 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
2159 .expect("decode quiescence request"),
2160 request
2161 );
2162 assert_eq!(
2163 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
2164 .expect("decode quiescence status request"),
2165 status_request
2166 );
2167 assert_eq!(
2168 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
2169 .expect("decode quiescence response"),
2170 response
2171 );
2172 }
2173
2174 #[test]
2175 fn component_draining_advance_contracts_round_trip_through_candid() {
2176 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
2177 let registry = ComponentRegistryHead {
2178 component,
2179 revision: 12,
2180 content_hash: [82; 32],
2181 };
2182 let request = RootComponentDrainingAdvanceRequest {
2183 operation_id: [83; 32],
2184 component,
2185 };
2186 let descendant_removal = RootComponentDrainingAdvanceResponse {
2187 operation_id: request.operation_id,
2188 component,
2189 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2190 RootComponentSubtreeRemovalResponse {
2191 operation_id: [84; 32],
2192 component,
2193 target_canister_id: Principal::from_slice(&[85; 29]),
2194 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2195 target_role: CanisterRole::new("project_instance"),
2196 target_status: ComponentLifecycleStatus::Active,
2197 reserved_against_registry: registry.clone(),
2198 maximum_completed_leaves: 20_000,
2199 completed_leaves: 0,
2200 traversal_steps: 0,
2201 phase: RootComponentSubtreeRemovalPhase::Fenced,
2202 },
2203 ),
2204 };
2205 let descendants_empty = RootComponentDrainingAdvanceResponse {
2206 operation_id: request.operation_id,
2207 component,
2208 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2209 RootComponentDrainingDescendantsEmpty {
2210 registry,
2211 descendant_content_hash: [87; 32],
2212 },
2213 ),
2214 };
2215
2216 let request_bytes =
2217 candid::encode_one(request).expect("encode Component draining advance request");
2218 let removal_bytes = candid::encode_one(&descendant_removal)
2219 .expect("encode Component draining removal response");
2220 let empty_bytes = candid::encode_one(&descendants_empty)
2221 .expect("encode Component draining empty response");
2222
2223 assert_eq!(
2224 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2225 .expect("decode Component draining advance request"),
2226 request
2227 );
2228 assert_eq!(
2229 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2230 .expect("decode Component draining removal response"),
2231 descendant_removal
2232 );
2233 assert_eq!(
2234 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2235 .expect("decode Component draining empty response"),
2236 descendants_empty
2237 );
2238 }
2239
2240 #[test]
2241 #[expect(
2242 clippy::too_many_lines,
2243 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2244 )]
2245 fn component_final_inventory_contracts_round_trip_through_candid() {
2246 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2247 let registry = ComponentRegistryHead {
2248 component,
2249 revision: 21,
2250 content_hash: [89; 32],
2251 };
2252 let request = RootComponentFinalInventoryRequest {
2253 operation_id: [90; 32],
2254 component,
2255 expected_registry: registry.clone(),
2256 };
2257 let inventory = RootComponentFinalInventory {
2258 registry,
2259 descendant_content_hash: [91; 32],
2260 registry_encoded_bytes: 4_096,
2261 directory_synchronized_at_ns: 92,
2262 covered_fleet_registry_revision: 93,
2263 covered_fleet_registry_content_hash: [94; 32],
2264 directory_authority_hash: [95; 32],
2265 inventory_hash: [96; 32],
2266 finalized_at_ns: 97,
2267 };
2268 let response = RootComponentFinalInventoryResponse {
2269 operation_id: request.operation_id,
2270 component,
2271 inventory: inventory.clone(),
2272 };
2273 let deletion_request = RootComponentDeletionRequest {
2274 operation_id: request.operation_id,
2275 component,
2276 expected_inventory_hash: inventory.inventory_hash,
2277 };
2278 let deletion_status_request = RootComponentDeletionStatusRequest {
2279 operation_id: request.operation_id,
2280 component,
2281 };
2282 let deletion = RootComponentDeletionIntent {
2283 final_inventory: inventory,
2284 quiescence: RootComponentQuiescentReceipt {
2285 stop: RootComponentQuiescenceStopIntent {
2286 registry: response.inventory.registry.clone(),
2287 descendant_count: 0,
2288 descendant_content_hash: response.inventory.descendant_content_hash,
2289 canister_id: Principal::from_slice(&[98; 29]),
2290 controller: Principal::from_slice(&[99; 29]),
2291 expected_module_hash: [100; 32],
2292 covered_fleet_registry_revision: 93,
2293 covered_fleet_registry_content_hash: [94; 32],
2294 covered_authority_hash: [101; 32],
2295 runtime_operation_id: [102; 32],
2296 activation: ComponentRuntimeActivationEvidence {
2297 directory_authority_hash: [103; 32],
2298 activated_at_ns: 104,
2299 },
2300 prepared_at_ns: 105,
2301 },
2302 observed_module_hash: [100; 32],
2303 quiesced_at_ns: 106,
2304 },
2305 prepared_at_ns: 107,
2306 };
2307 let deleted_receipt = RootComponentDeletedReceipt {
2308 deletion,
2309 deleted_at_ns: 108,
2310 };
2311 let deletion_response = RootComponentDeletionResponse {
2312 operation_id: request.operation_id,
2313 component,
2314 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2315 };
2316 let membership_removed_response = RootComponentDeletionResponse {
2317 operation_id: request.operation_id,
2318 component,
2319 phase: RootComponentDeletionPhase::MembershipRemoved(
2320 RootComponentMembershipRemovedReceipt {
2321 deleted: deleted_receipt,
2322 allocation_operation_id: [109; 32],
2323 remaining_spec_committed_instances: 2,
2324 root_committed_component_instances: 3,
2325 root_known_created_component_canisters: 4,
2326 root_registry_encoded_bytes: 5_000,
2327 removed_at_ns: 110,
2328 removal_hash: [111; 32],
2329 },
2330 ),
2331 };
2332
2333 let request_bytes =
2334 candid::encode_one(&request).expect("encode Component final inventory request");
2335 let response_bytes =
2336 candid::encode_one(&response).expect("encode Component final inventory response");
2337 let deletion_request_bytes =
2338 candid::encode_one(deletion_request).expect("encode Component deletion request");
2339 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2340 .expect("encode Component deletion status request");
2341 let deletion_response_bytes =
2342 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2343 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2344 .expect("encode Component membership-removal response");
2345 assert_eq!(
2346 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2347 .expect("decode Component final inventory request"),
2348 request
2349 );
2350 assert_eq!(
2351 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2352 .expect("decode Component final inventory response"),
2353 response
2354 );
2355 assert_eq!(
2356 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2357 .expect("decode Component deletion request"),
2358 deletion_request
2359 );
2360 assert_eq!(
2361 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2362 .expect("decode Component deletion status request"),
2363 deletion_status_request
2364 );
2365 assert_eq!(
2366 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2367 .expect("decode Component deletion response"),
2368 deletion_response
2369 );
2370 assert_eq!(
2371 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2372 .expect("decode Component membership-removal response"),
2373 membership_removed_response
2374 );
2375 }
2376
2377 #[test]
2378 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2379 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2380 operation_id: [50; 32],
2381 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2382 expected_traversal_steps: 3,
2383 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2384 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2385 };
2386 let request = RootComponentSubtreeRemovalDeleteRequest {
2387 operation_id: prepare.operation_id,
2388 component: prepare.component,
2389 expected_traversal_steps: prepare.expected_traversal_steps,
2390 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2391 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2392 };
2393 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2394 operation_id: prepare.operation_id,
2395 component: prepare.component,
2396 expected_traversal_steps: prepare.expected_traversal_steps,
2397 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2398 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2399 };
2400 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2401 operation_id: prepare.operation_id,
2402 component: prepare.component,
2403 expected_traversal_steps: prepare.expected_traversal_steps,
2404 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2405 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2406 };
2407 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2408 operation_id: prepare.operation_id,
2409 component: prepare.component,
2410 expected_traversal_steps: prepare.expected_traversal_steps,
2411 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2412 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2413 };
2414 let prepare_bytes = candid::encode_one(prepare)
2415 .expect("encode subtree removal deletion preparation request");
2416 let request_bytes =
2417 candid::encode_one(request).expect("encode subtree removal deletion request");
2418 let membership_request_bytes = candid::encode_one(membership_request)
2419 .expect("encode subtree removal membership-removal request");
2420 let directory_request_bytes = candid::encode_one(directory_request)
2421 .expect("encode subtree removal Directory synchronization request");
2422 let finalization_request_bytes = candid::encode_one(finalization_request)
2423 .expect("encode subtree removal leaf-finalization request");
2424
2425 assert_eq!(
2426 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2427 &prepare_bytes
2428 )
2429 .expect("decode subtree removal deletion preparation request"),
2430 prepare
2431 );
2432 assert_eq!(
2433 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2434 .expect("decode subtree removal deletion request"),
2435 request
2436 );
2437 assert_eq!(
2438 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2439 &membership_request_bytes
2440 )
2441 .expect("decode subtree removal membership-removal request"),
2442 membership_request
2443 );
2444 assert_eq!(
2445 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2446 &directory_request_bytes
2447 )
2448 .expect("decode subtree removal Directory synchronization request"),
2449 directory_request
2450 );
2451 assert_eq!(
2452 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2453 &finalization_request_bytes
2454 )
2455 .expect("decode subtree removal leaf-finalization request"),
2456 finalization_request
2457 );
2458 }
2459
2460 #[test]
2461 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2462 let request = RootComponentSubtreeRemovalStopRequest {
2463 operation_id: [50; 32],
2464 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2465 expected_traversal_steps: 3,
2466 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2467 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2468 };
2469 let bytes =
2470 candid::encode_one(request).expect("encode subtree removal stop execution request");
2471
2472 assert_eq!(
2473 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2474 .expect("decode subtree removal stop execution request"),
2475 request
2476 );
2477 }
2478
2479 #[test]
2480 #[expect(
2481 clippy::too_many_lines,
2482 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2483 )]
2484 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2485 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2486 let registry = ComponentRegistryHead {
2487 component,
2488 revision: 2,
2489 content_hash: [12; 32],
2490 };
2491 let request = RootComponentChildAllocationRequest {
2492 operation_id: [13; 32],
2493 component,
2494 expected_registry: registry.clone(),
2495 child_role: CanisterRole::new("project_instance"),
2496 application_init_args: Some(vec![9, 8, 7]),
2497 };
2498 let status_request = RootComponentChildAllocationStatusRequest {
2499 operation_id: request.operation_id,
2500 component,
2501 };
2502 let creation_request = RootComponentChildCreationRequest {
2503 operation_id: request.operation_id,
2504 component,
2505 };
2506 let install_request = RootComponentChildInstallRequest {
2507 operation_id: request.operation_id,
2508 component,
2509 };
2510 let commit_request = RootComponentChildCommitRequest {
2511 operation_id: request.operation_id,
2512 component,
2513 };
2514 let directory_request = RootComponentChildDirectoryPreparationRequest {
2515 operation_id: request.operation_id,
2516 component,
2517 };
2518 let activation_request = RootComponentChildRuntimeActivationRequest {
2519 operation_id: request.operation_id,
2520 component,
2521 };
2522 let membership_request = RootComponentChildMembershipActivationRequest {
2523 operation_id: request.operation_id,
2524 component,
2525 };
2526 let root = Principal::from_slice(&[17; 29]);
2527 let parent = Principal::from_slice(&[14; 29]);
2528 let child = Principal::from_slice(&[18; 29]);
2529 let child_binding = ComponentChildBinding {
2530 component: ComponentBinding {
2531 authority: fleet_registry_authority(),
2532 component,
2533 component_spec: "projects".parse().expect("Component Spec"),
2534 spec_hash: [19; 32],
2535 role: CanisterRole::new("project_hub"),
2536 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2537 fleet_subnet_root: root,
2538 canister_id: parent,
2539 },
2540 parent_canister_id: parent,
2541 role: request.child_role.clone(),
2542 canister_id: child,
2543 };
2544 let response = RootComponentChildAllocationResponse {
2545 operation_id: request.operation_id,
2546 component,
2547 parent_canister_id: parent,
2548 parent_role: CanisterRole::new("project_hub"),
2549 child_role: request.child_role.clone(),
2550 child_kind: ComponentChildKind::Instance,
2551 maximum_instances_per_parent: 10_000,
2552 maximum_descendants: 20_000,
2553 maximum_registry_bytes: 16_777_216,
2554 reserved_against_registry: registry,
2555 release_set: FleetSubnetRootReleaseSet {
2556 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2557 [15; 32],
2558 )),
2559 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2560 },
2561 phase: RootComponentAllocationPhase::Verified,
2562 creation: Some(RootComponentCreationEvidence {
2563 wasm_store: Principal::from_slice(&[21; 29]),
2564 payload_hash: [22; 32],
2565 payload_size_bytes: 4_096,
2566 initial_cycles: Cycles::new(5_000_000_000_000),
2567 controller: root,
2568 canister: Some(child),
2569 }),
2570 installation: Some(RootComponentChildInstallEvidence {
2571 raw_module_hash: [23; 32],
2572 chunk_hashes: vec![vec![24; 32]],
2573 binding: child_binding.clone(),
2574 }),
2575 };
2576 let commit_response = RootComponentChildCommitResponse {
2577 allocation: response.clone(),
2578 registry: ComponentRegistryPartitionResponse {
2579 head: ComponentRegistryHead {
2580 component,
2581 revision: 3,
2582 content_hash: [25; 32],
2583 },
2584 binding: child_binding.component.clone(),
2585 protocol_profile_digest: ProtocolProfileDigest::from_bytes([27; 32]),
2586 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2587 caller: Principal::from_slice(&[26; 29]),
2588 },
2589 release_set: response.release_set,
2590 status: ComponentLifecycleStatus::Active,
2591 reserved_descendants: 0,
2592 committed_descendants: 1,
2593 encoded_bytes: 8_192,
2594 },
2595 directory: ComponentDirectoryHead {
2596 provenance: ComponentDirectoryProvenance {
2597 component: child_binding.component.clone(),
2598 source_fleet_subnet_root: root,
2599 component_registry_revision: 3,
2600 component_registry_content_hash: [25; 32],
2601 synchronized_at_ns: 27,
2602 },
2603 descendant_count: 1,
2604 },
2605 };
2606 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2607 fleet: FleetDirectorySnapshot {
2608 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2609 registry: FleetRegistryVersion {
2610 authority: fleet_registry_authority(),
2611 revision: 4,
2612 content_hash: [28; 32],
2613 },
2614 source_fleet_subnet_root: root,
2615 },
2616 fleet_subnet_roots: vec![
2617 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2618 placement_subnet: commit_response.registry.binding.placement_subnet,
2619 fleet_subnet_root: root,
2620 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2621 },
2622 ],
2623 services: vec![],
2624 },
2625 component: commit_response.directory.clone(),
2626 component_group: None,
2627 };
2628 let activation = ComponentRuntimeActivationEvidence {
2629 directory_authority_hash: [29; 32],
2630 activated_at_ns: 30,
2631 };
2632 let directory_response = RootComponentChildDirectoryPreparationResponse {
2633 committed: commit_response.clone(),
2634 child: ComponentRuntimeStatusResponse {
2635 operation_id: request.operation_id,
2636 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2637 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2638 binding: child_binding.component.clone(),
2639 }),
2640 phase: ComponentRuntimePhase::DirectoryPrepared,
2641 authority: Some(runtime_authority.clone()),
2642 authority_hash: Some([31; 32]),
2643 direct_children_hash: Some([37; 32]),
2644 activation: None,
2645 },
2646 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2647 operation_id: [32; 32],
2648 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2649 covered_authority: runtime_authority.clone(),
2650 covered_authority_hash: [31; 32],
2651 activation,
2652 },
2653 parent: None,
2654 };
2655 let activation_response = RootComponentChildRuntimeActivationResponse {
2656 committed: commit_response.clone(),
2657 child: ComponentRuntimeStatusResponse {
2658 operation_id: request.operation_id,
2659 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2660 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2661 binding: child_binding.component.clone(),
2662 }),
2663 phase: ComponentRuntimePhase::Active,
2664 authority: Some(runtime_authority.clone()),
2665 authority_hash: Some([31; 32]),
2666 direct_children_hash: Some([37; 32]),
2667 activation: Some(ComponentRuntimeActivationEvidence {
2668 directory_authority_hash: [31; 32],
2669 activated_at_ns: 33,
2670 }),
2671 },
2672 };
2673 let active_directory = ComponentDirectoryHead {
2674 provenance: ComponentDirectoryProvenance {
2675 component: child_binding.component.clone(),
2676 source_fleet_subnet_root: root,
2677 component_registry_revision: 4,
2678 component_registry_content_hash: [34; 32],
2679 synchronized_at_ns: 35,
2680 },
2681 descendant_count: 1,
2682 };
2683 let active_authority = ComponentRuntimeDirectoryAuthority {
2684 fleet: runtime_authority.fleet,
2685 component: active_directory.clone(),
2686 component_group: None,
2687 };
2688 let membership_response = RootComponentChildMembershipActivationResponse {
2689 committed: commit_response.clone(),
2690 registry: ComponentRegistryPartitionResponse {
2691 head: ComponentRegistryHead {
2692 component,
2693 revision: 4,
2694 content_hash: [34; 32],
2695 },
2696 binding: child_binding.component.clone(),
2697 protocol_profile_digest: ProtocolProfileDigest::from_bytes([36; 32]),
2698 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2699 release_set: commit_response.registry.release_set,
2700 status: ComponentLifecycleStatus::Active,
2701 reserved_descendants: 0,
2702 committed_descendants: 1,
2703 encoded_bytes: 8_256,
2704 },
2705 directory: active_directory,
2706 child: ComponentRuntimeStatusResponse {
2707 operation_id: request.operation_id,
2708 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2709 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2710 binding: child_binding.component,
2711 }),
2712 phase: ComponentRuntimePhase::Active,
2713 authority: Some(active_authority),
2714 authority_hash: Some([36; 32]),
2715 direct_children_hash: Some([38; 32]),
2716 activation: Some(ComponentRuntimeActivationEvidence {
2717 directory_authority_hash: [31; 32],
2718 activated_at_ns: 33,
2719 }),
2720 },
2721 };
2722
2723 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2724 let status_bytes =
2725 candid::encode_one(status_request).expect("encode child reservation status");
2726 let creation_bytes =
2727 candid::encode_one(creation_request).expect("encode child creation request");
2728 let install_bytes =
2729 candid::encode_one(install_request).expect("encode child install request");
2730 let response_bytes = candid::encode_one(&response).expect("encode child response");
2731 let commit_request_bytes =
2732 candid::encode_one(commit_request).expect("encode child commit request");
2733 let directory_request_bytes =
2734 candid::encode_one(directory_request).expect("encode child Directory request");
2735 let activation_request_bytes =
2736 candid::encode_one(activation_request).expect("encode child activation request");
2737 let membership_request_bytes =
2738 candid::encode_one(membership_request).expect("encode child membership request");
2739 let commit_response_bytes =
2740 candid::encode_one(&commit_response).expect("encode child commit response");
2741 let directory_response_bytes =
2742 candid::encode_one(&directory_response).expect("encode child Directory response");
2743 let activation_response_bytes =
2744 candid::encode_one(&activation_response).expect("encode child activation response");
2745 let membership_response_bytes =
2746 candid::encode_one(&membership_response).expect("encode child membership response");
2747
2748 assert_eq!(
2749 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2750 .expect("decode child reservation"),
2751 request
2752 );
2753 assert_eq!(
2754 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2755 .expect("decode child reservation status"),
2756 status_request
2757 );
2758 assert_eq!(
2759 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2760 .expect("decode child creation request"),
2761 creation_request
2762 );
2763 assert_eq!(
2764 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2765 .expect("decode child install request"),
2766 install_request
2767 );
2768 assert_eq!(
2769 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2770 .expect("decode child response"),
2771 response
2772 );
2773 assert_eq!(
2774 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2775 .expect("decode child commit request"),
2776 commit_request
2777 );
2778 assert_eq!(
2779 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2780 &directory_request_bytes
2781 )
2782 .expect("decode child Directory request"),
2783 directory_request
2784 );
2785 assert_eq!(
2786 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2787 .expect("decode child commit response"),
2788 commit_response
2789 );
2790 assert_eq!(
2791 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2792 &directory_response_bytes
2793 )
2794 .expect("decode child Directory response"),
2795 directory_response
2796 );
2797 assert_eq!(
2798 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2799 &activation_request_bytes
2800 )
2801 .expect("decode child activation request"),
2802 activation_request
2803 );
2804 assert_eq!(
2805 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2806 &activation_response_bytes
2807 )
2808 .expect("decode child activation response"),
2809 activation_response
2810 );
2811 assert_eq!(
2812 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2813 &membership_request_bytes
2814 )
2815 .expect("decode child membership request"),
2816 membership_request
2817 );
2818 assert_eq!(
2819 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2820 &membership_response_bytes
2821 )
2822 .expect("decode child membership response"),
2823 membership_response
2824 );
2825 }
2826
2827 #[test]
2828 fn component_install_request_round_trips_through_candid() {
2829 let request = RootComponentInstallRequest {
2830 operation_id: [10; 32],
2831 };
2832 let bytes = candid::encode_one(request).expect("encode install request");
2833
2834 assert_eq!(
2835 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2836 .expect("decode install request"),
2837 request
2838 );
2839 }
2840
2841 #[test]
2842 fn component_commit_request_round_trips_through_candid() {
2843 let request = RootComponentCommitRequest {
2844 operation_id: [10; 32],
2845 };
2846 let bytes = candid::encode_one(request).expect("encode commit request");
2847
2848 assert_eq!(
2849 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2850 .expect("decode commit request"),
2851 request
2852 );
2853 }
2854
2855 #[test]
2856 fn component_runtime_activation_requests_round_trip_through_candid() {
2857 let root_request = RootComponentRuntimeActivationRequest {
2858 operation_id: [22; 32],
2859 };
2860 let target_request = ComponentRuntimeActivationRequest {
2861 operation_id: root_request.operation_id,
2862 directory_authority_hash: [23; 32],
2863 };
2864 let membership_request = RootComponentMembershipActivationRequest {
2865 operation_id: root_request.operation_id,
2866 };
2867 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2868 let target_bytes =
2869 candid::encode_one(target_request).expect("encode target activation request");
2870 let membership_bytes =
2871 candid::encode_one(membership_request).expect("encode membership activation request");
2872
2873 assert_eq!(
2874 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2875 .expect("decode root activation request"),
2876 root_request
2877 );
2878 assert_eq!(
2879 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2880 .expect("decode target activation request"),
2881 target_request
2882 );
2883 assert_eq!(
2884 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2885 .expect("decode membership activation request"),
2886 membership_request
2887 );
2888 }
2889}