1use crate::{
8 cdk::types::Cycles,
9 config::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 FleetSubnetRootReleaseSet, ManagedCanisterBinding,
19 },
20};
21use candid::{CandidType, Principal};
22use serde::{Deserialize, Serialize};
23
24#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct RootComponentRegistryPreparationRequest {
32 pub store_bootstrap: RootStoreBootstrapRequest,
33 pub expected_fleet_registry: FleetRegistryVersion,
34}
35
36#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
43pub struct RootComponentInitialInventoryStatus {
44 pub fleet_activation_operation_id: [u8; 32],
45 pub component_count: u32,
46 pub inventory_hash: [u8; 32],
47 pub sealed_at_ns: u64,
48 pub directories_converged: bool,
49 pub root_runtime_activated: bool,
50}
51
52#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
59pub struct RootComponentRegistryStatusResponse {
60 pub fleet_subnet_root: Principal,
61 pub prepared_against_registry: FleetRegistryVersion,
62 pub release_set: FleetSubnetRootReleaseSet,
63 pub component_topology_digest: ComponentTopologyDigest,
64 pub next_allocation_sequence: u64,
65 pub reserved_component_instances: u32,
66 pub committed_component_instances: u32,
67 pub managed_descendants: u32,
68 pub known_created_component_canisters: u32,
69 pub encoded_bytes: u64,
70 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
71}
72
73#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
80pub struct RootComponentAllocationRequest {
81 pub operation_id: [u8; 32],
82 pub component_spec: ComponentSpecId,
83}
84
85#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
92pub struct RootComponentAllocationStatusRequest {
93 pub operation_id: [u8; 32],
94}
95
96#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103pub struct RootComponentChildAllocationRequest {
104 pub operation_id: [u8; 32],
105 pub component: ComponentInstanceId,
106 pub expected_registry: ComponentRegistryHead,
107 pub child_role: CanisterRole,
108 pub application_init_args: Option<Vec<u8>>,
109}
110
111#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
118pub struct RootComponentChildAllocationStatusRequest {
119 pub operation_id: [u8; 32],
120 pub component: ComponentInstanceId,
121}
122
123#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
130pub struct RootComponentSubtreeRemovalRequest {
131 pub operation_id: [u8; 32],
132 pub component: ComponentInstanceId,
133 pub target_canister_id: Principal,
134 pub expected_registry: ComponentRegistryHead,
135}
136
137#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
144pub struct RootComponentSubtreeRemovalAdvanceRequest {
145 pub operation_id: [u8; 32],
146 pub component: ComponentInstanceId,
147 pub expected_traversal_steps: u32,
148}
149
150#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
157pub struct RootComponentSubtreeRemovalStopPreparationRequest {
158 pub operation_id: [u8; 32],
159 pub component: ComponentInstanceId,
160 pub expected_traversal_steps: u32,
161 pub expected_leaf_canister_id: Principal,
162 pub expected_leaf_parent_canister_id: Principal,
163}
164
165#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
172pub struct RootComponentSubtreeRemovalStopRequest {
173 pub operation_id: [u8; 32],
174 pub component: ComponentInstanceId,
175 pub expected_traversal_steps: u32,
176 pub expected_leaf_canister_id: Principal,
177 pub expected_leaf_parent_canister_id: Principal,
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
188 pub operation_id: [u8; 32],
189 pub component: ComponentInstanceId,
190 pub expected_traversal_steps: u32,
191 pub expected_leaf_canister_id: Principal,
192 pub expected_leaf_parent_canister_id: Principal,
193}
194
195#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
202pub struct RootComponentSubtreeRemovalDeleteRequest {
203 pub operation_id: [u8; 32],
204 pub component: ComponentInstanceId,
205 pub expected_traversal_steps: u32,
206 pub expected_leaf_canister_id: Principal,
207 pub expected_leaf_parent_canister_id: Principal,
208}
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
218 pub operation_id: [u8; 32],
219 pub component: ComponentInstanceId,
220 pub expected_traversal_steps: u32,
221 pub expected_leaf_canister_id: Principal,
222 pub expected_leaf_parent_canister_id: Principal,
223}
224
225#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
232pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
233 pub operation_id: [u8; 32],
234 pub component: ComponentInstanceId,
235 pub expected_traversal_steps: u32,
236 pub expected_leaf_canister_id: Principal,
237 pub expected_leaf_parent_canister_id: Principal,
238}
239
240#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
247pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
248 pub operation_id: [u8; 32],
249 pub component: ComponentInstanceId,
250 pub expected_traversal_steps: u32,
251 pub expected_leaf_canister_id: Principal,
252 pub expected_leaf_parent_canister_id: Principal,
253}
254
255#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
262pub struct RootComponentSubtreeRemovalStatusRequest {
263 pub operation_id: [u8; 32],
264 pub component: ComponentInstanceId,
265}
266
267#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
274pub struct RootComponentDrainingRequest {
275 pub operation_id: [u8; 32],
276 pub component: ComponentInstanceId,
277 pub expected_registry: ComponentRegistryHead,
278}
279
280#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
287pub struct RootComponentDrainingStatusRequest {
288 pub operation_id: [u8; 32],
289 pub component: ComponentInstanceId,
290}
291
292#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
299pub struct RootComponentQuiescenceRequest {
300 pub operation_id: [u8; 32],
301 pub component: ComponentInstanceId,
302 pub expected_registry: ComponentRegistryHead,
303}
304
305#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
312pub struct RootComponentQuiescenceStatusRequest {
313 pub operation_id: [u8; 32],
314 pub component: ComponentInstanceId,
315}
316
317#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
324pub struct RootComponentDrainingAdvanceRequest {
325 pub operation_id: [u8; 32],
326 pub component: ComponentInstanceId,
327}
328
329#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
336pub struct RootComponentFinalInventoryRequest {
337 pub operation_id: [u8; 32],
338 pub component: ComponentInstanceId,
339 pub expected_registry: ComponentRegistryHead,
340}
341
342#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
349pub struct RootComponentDeletionRequest {
350 pub operation_id: [u8; 32],
351 pub component: ComponentInstanceId,
352 pub expected_inventory_hash: [u8; 32],
353}
354
355#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
362pub struct RootComponentDeletionStatusRequest {
363 pub operation_id: [u8; 32],
364 pub component: ComponentInstanceId,
365}
366
367#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
374pub struct RootComponentChildCreationRequest {
375 pub operation_id: [u8; 32],
376 pub component: ComponentInstanceId,
377}
378
379#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
386pub struct RootComponentChildInstallRequest {
387 pub operation_id: [u8; 32],
388 pub component: ComponentInstanceId,
389}
390
391#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
398pub struct RootComponentChildCommitRequest {
399 pub operation_id: [u8; 32],
400 pub component: ComponentInstanceId,
401}
402
403#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
410pub struct RootComponentChildDirectoryPreparationRequest {
411 pub operation_id: [u8; 32],
412 pub component: ComponentInstanceId,
413}
414
415#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
422pub struct RootComponentChildRuntimeActivationRequest {
423 pub operation_id: [u8; 32],
424 pub component: ComponentInstanceId,
425}
426
427#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
434pub struct RootComponentChildMembershipActivationRequest {
435 pub operation_id: [u8; 32],
436 pub component: ComponentInstanceId,
437}
438
439#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub struct RootComponentCreationRequest {
447 pub operation_id: [u8; 32],
448}
449
450#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
457pub struct RootComponentInstallRequest {
458 pub operation_id: [u8; 32],
459}
460
461#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
468pub struct RootComponentCommitRequest {
469 pub operation_id: [u8; 32],
470}
471
472#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
479pub struct RootComponentDirectoryPreparationRequest {
480 pub operation_id: [u8; 32],
481}
482
483#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
490pub struct RootComponentRuntimeActivationRequest {
491 pub operation_id: [u8; 32],
492}
493
494#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
501pub struct RootComponentMembershipActivationRequest {
502 pub operation_id: [u8; 32],
503}
504
505#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
512pub enum ComponentProvisioningOrigin {
513 FleetAdministrator {
514 caller: Principal,
515 },
516 Component {
517 requester: Box<ComponentBinding>,
518 grant: Box<crate::config::ComponentProvisioningGrant>,
519 },
520 ComponentGroup {
521 operation_id: [u8; 32],
522 plan_hash: [u8; 32],
523 group_placement: ComponentGroupPlacementId,
524 member_path: ComponentGroupMemberPath,
525 },
526}
527
528#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
535pub enum RootComponentAllocationPhase {
536 Reserved,
537 CreationIntent,
538 Created,
539 InstallIntent,
540 Installed,
541 Verified,
542 Committed,
543 Removed,
544}
545
546#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
553#[expect(
554 clippy::large_enum_variant,
555 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
556)]
557pub enum RootComponentSubtreeRemovalPhase {
558 Fenced,
559 Traversing(RootComponentSubtreeRemovalNode),
560 LeafSelected(RootComponentSubtreeRemovalNode),
561 StopIntent(RootComponentSubtreeRemovalStopIntent),
562 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
563 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
564 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
565 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
566 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
567 Completed(RootComponentSubtreeRemovalCompletedReceipt),
568}
569
570#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
577pub struct RootComponentSubtreeRemovalNode {
578 pub canister_id: Principal,
579 pub parent_canister_id: Principal,
580 pub role: CanisterRole,
581 pub kind: ComponentChildKind,
582 pub installed_artifact_hash: [u8; 32],
583 pub status: ComponentLifecycleStatus,
584}
585
586#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
593pub struct RootComponentSubtreeRemovalStopIntent {
594 pub leaf: RootComponentSubtreeRemovalNode,
595 pub controller: Principal,
596}
597
598#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
605pub struct RootComponentSubtreeRemovalStoppedReceipt {
606 pub stop: RootComponentSubtreeRemovalStopIntent,
607 pub observed_module_hash: [u8; 32],
608}
609
610#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
617pub struct RootComponentSubtreeRemovalDeleteIntent {
618 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
619}
620
621#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
628pub struct RootComponentSubtreeRemovalDeletedReceipt {
629 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
630}
631
632#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
639pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
640 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
641 pub removed_from_registry: ComponentRegistryHead,
642 pub previous_descendant_content_hash: [u8; 32],
643 pub previous_committed_descendants: u32,
644 pub registry: ComponentRegistryHead,
645 pub descendant_content_hash: [u8; 32],
646 pub registry_encoded_bytes: u64,
647 pub reserved_descendants: u32,
648 pub committed_descendants: u32,
649 pub directory_synchronized_at_ns: u64,
650 pub directory_authority_hash: [u8; 32],
651 pub parent_role_instances: u32,
652 pub root_managed_descendants: u32,
653 pub root_known_created_component_canisters: u32,
654}
655
656#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
663pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
664 pub operation_id: [u8; 32],
665 pub canister_id: Principal,
666 pub activation: ComponentRuntimeActivationEvidence,
667}
668
669#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
678pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
679 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
680 pub covered_fleet_registry_revision: u64,
681 pub covered_fleet_registry_content_hash: [u8; 32],
682 pub covered_component_registry: ComponentRegistryHead,
683 pub covered_authority_hash: [u8; 32],
684 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
685 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
686}
687
688#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
695pub struct RootComponentSubtreeRemovalCompletedReceipt {
696 pub registry: ComponentRegistryHead,
697 pub directory_authority_hash: [u8; 32],
698}
699
700#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
707pub enum ComponentLifecycleStatus {
708 Prepared,
709 Active,
710 Draining,
711 Removed,
712}
713
714#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
721pub struct ComponentRegistryHead {
722 pub component: ComponentInstanceId,
723 pub revision: u64,
724 pub content_hash: [u8; 32],
725}
726
727#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
734pub struct ComponentRegistryPartitionRequest {
735 pub component: ComponentInstanceId,
736}
737
738#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
745pub struct ComponentRegistryPartitionResponse {
746 pub head: ComponentRegistryHead,
747 pub binding: ComponentBinding,
748 pub provisioning_origin: ComponentProvisioningOrigin,
749 pub release_set: FleetSubnetRootReleaseSet,
750 pub status: ComponentLifecycleStatus,
751 pub reserved_descendants: u32,
752 pub committed_descendants: u32,
753 pub encoded_bytes: u64,
754}
755
756#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
763pub struct ComponentDirectoryProvenance {
764 pub component: ComponentBinding,
765 pub source_fleet_subnet_root: Principal,
766 pub component_registry_revision: u64,
767 pub component_registry_content_hash: [u8; 32],
768 pub synchronized_at_ns: u64,
769}
770
771#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
778pub struct ComponentDirectoryHead {
779 pub provenance: ComponentDirectoryProvenance,
780 pub descendant_count: u32,
781}
782
783#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
790pub struct ComponentDirectoryHeadRequest {
791 pub component: ComponentInstanceId,
792}
793
794#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
801pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
802
803#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
810pub struct ComponentDirectoryPageRequest {
811 pub directory: ComponentDirectoryHead,
812 pub parent_canister_id: Option<Principal>,
813 pub role: Option<CanisterRole>,
814 pub status: Option<ComponentLifecycleStatus>,
815 pub cursor: Option<ComponentDirectoryPageCursor>,
816 pub limit: u16,
817}
818
819#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
826pub struct ComponentDirectoryChildEntry {
827 pub binding: ComponentChildBinding,
828 pub kind: ComponentChildKind,
829 pub installed_artifact_hash: [u8; 32],
830 pub status: ComponentLifecycleStatus,
831}
832
833#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
840pub struct ComponentDirectoryPageResponse {
841 pub directory: ComponentDirectoryHead,
842 pub entries: Vec<ComponentDirectoryChildEntry>,
843 pub next_cursor: Option<ComponentDirectoryPageCursor>,
844}
845
846#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
853pub struct ComponentRuntimeDirectoryAuthority {
854 pub fleet: FleetDirectorySnapshot,
855 pub component: ComponentDirectoryHead,
856}
857
858#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
865pub struct ComponentRuntimeDirectChild {
866 pub canister_id: Principal,
867 pub role: CanisterRole,
868}
869
870#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
877pub struct ComponentRuntimeDirectoryPreparationRequest {
878 pub operation_id: [u8; 32],
879 pub authority: ComponentRuntimeDirectoryAuthority,
880 pub direct_children: Vec<ComponentRuntimeDirectChild>,
881}
882
883#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
890pub struct ComponentRuntimeDirectorySynchronizationRequest {
891 pub operation_id: [u8; 32],
892 pub authority: ComponentRuntimeDirectoryAuthority,
893 pub direct_children: Vec<ComponentRuntimeDirectChild>,
894}
895
896#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
903pub enum ComponentRuntimePhase {
904 AwaitingDirectory,
905 DirectoryPrepared,
906 Active,
907}
908
909#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
916pub struct ComponentRuntimeActivationEvidence {
917 pub directory_authority_hash: [u8; 32],
918 pub activated_at_ns: u64,
919}
920
921#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
928pub struct ComponentRuntimeActivationRequest {
929 pub operation_id: [u8; 32],
930 pub directory_authority_hash: [u8; 32],
931}
932
933#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
940pub struct ComponentRuntimeStatusResponse {
941 pub operation_id: [u8; 32],
942 pub binding: ManagedCanisterBinding,
943 pub deployment: Box<ProtectedComponentDeployment>,
944 pub phase: ComponentRuntimePhase,
945 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
946 pub authority_hash: Option<[u8; 32]>,
947 pub direct_children_hash: Option<[u8; 32]>,
948 pub activation: Option<ComponentRuntimeActivationEvidence>,
949}
950
951#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
958pub struct ComponentRuntimeDirectoryConvergenceEvidence {
959 pub operation_id: [u8; 32],
960 pub binding: ManagedCanisterBinding,
961 pub covered_authority: ComponentRuntimeDirectoryAuthority,
962 pub covered_authority_hash: [u8; 32],
963 pub activation: ComponentRuntimeActivationEvidence,
964}
965
966#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
973pub struct RootComponentCreationEvidence {
974 pub wasm_store: Principal,
975 pub payload_hash: [u8; 32],
976 pub payload_size_bytes: u64,
977 pub initial_cycles: Cycles,
978 pub controller: Principal,
979 pub canister: Option<Principal>,
980}
981
982#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
989pub struct RootComponentInstallEvidence {
990 pub raw_module_hash: [u8; 32],
991 pub chunk_hashes: Vec<Vec<u8>>,
992 pub binding: ComponentBinding,
993}
994
995#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1002pub struct RootComponentChildInstallEvidence {
1003 pub raw_module_hash: [u8; 32],
1004 pub chunk_hashes: Vec<Vec<u8>>,
1005 pub binding: ComponentChildBinding,
1006}
1007
1008#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1015pub struct RootComponentAllocationResponse {
1016 pub operation_id: [u8; 32],
1017 pub allocation_sequence: u64,
1018 pub component: ComponentInstanceId,
1019 pub component_spec: ComponentSpecId,
1020 pub spec_hash: [u8; 32],
1021 pub role: CanisterRole,
1022 pub provisioning_origin: ComponentProvisioningOrigin,
1023 pub release_set: FleetSubnetRootReleaseSet,
1024 pub phase: RootComponentAllocationPhase,
1025 pub creation: Option<RootComponentCreationEvidence>,
1026 pub installation: Option<RootComponentInstallEvidence>,
1027}
1028
1029#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1036pub struct RootComponentChildAllocationResponse {
1037 pub operation_id: [u8; 32],
1038 pub component: ComponentInstanceId,
1039 pub parent_canister_id: Principal,
1040 pub parent_role: CanisterRole,
1041 pub child_role: CanisterRole,
1042 pub child_kind: ComponentChildKind,
1043 pub maximum_instances_per_parent: u32,
1044 pub maximum_descendants: u32,
1045 pub maximum_registry_bytes: u64,
1046 pub reserved_against_registry: ComponentRegistryHead,
1047 pub release_set: FleetSubnetRootReleaseSet,
1048 pub phase: RootComponentAllocationPhase,
1049 pub creation: Option<RootComponentCreationEvidence>,
1050 pub installation: Option<RootComponentChildInstallEvidence>,
1051}
1052
1053#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1060pub struct RootComponentSubtreeRemovalResponse {
1061 pub operation_id: [u8; 32],
1062 pub component: ComponentInstanceId,
1063 pub target_canister_id: Principal,
1064 pub target_parent_canister_id: Principal,
1065 pub target_role: CanisterRole,
1066 pub target_status: ComponentLifecycleStatus,
1067 pub reserved_against_registry: ComponentRegistryHead,
1068 pub maximum_completed_leaves: u32,
1069 pub completed_leaves: u32,
1070 pub traversal_steps: u32,
1071 pub phase: RootComponentSubtreeRemovalPhase,
1072}
1073
1074#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1081pub struct RootComponentDrainingResponse {
1082 pub operation_id: [u8; 32],
1083 pub component: ComponentInstanceId,
1084 pub previous_registry: ComponentRegistryHead,
1085 pub registry: ComponentRegistryHead,
1086 pub descendant_count: u32,
1087 pub descendant_content_hash: [u8; 32],
1088 pub directory_authority_hash: [u8; 32],
1089 pub started_at_ns: u64,
1090}
1091
1092#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1099pub struct RootComponentQuiescenceStopIntent {
1100 pub registry: ComponentRegistryHead,
1101 pub descendant_count: u32,
1102 pub descendant_content_hash: [u8; 32],
1103 pub canister_id: Principal,
1104 pub controller: Principal,
1105 pub expected_module_hash: [u8; 32],
1106 pub covered_fleet_registry_revision: u64,
1107 pub covered_fleet_registry_content_hash: [u8; 32],
1108 pub covered_authority_hash: [u8; 32],
1109 pub runtime_operation_id: [u8; 32],
1110 pub activation: ComponentRuntimeActivationEvidence,
1111 pub prepared_at_ns: u64,
1112}
1113
1114#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1121pub struct RootComponentQuiescentReceipt {
1122 pub stop: RootComponentQuiescenceStopIntent,
1123 pub observed_module_hash: [u8; 32],
1124 pub quiesced_at_ns: u64,
1125}
1126
1127#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1134pub enum RootComponentQuiescencePhase {
1135 StopIntent(RootComponentQuiescenceStopIntent),
1136 Quiescent(RootComponentQuiescentReceipt),
1137}
1138
1139#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1146pub struct RootComponentQuiescenceResponse {
1147 pub operation_id: [u8; 32],
1148 pub component: ComponentInstanceId,
1149 pub phase: RootComponentQuiescencePhase,
1150}
1151
1152#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1159pub struct RootComponentDrainingDescendantsEmpty {
1160 pub registry: ComponentRegistryHead,
1161 pub descendant_content_hash: [u8; 32],
1162}
1163
1164#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1171#[expect(
1172 clippy::large_enum_variant,
1173 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1174)]
1175pub enum RootComponentDrainingAdvancePhase {
1176 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1177 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1178}
1179
1180#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1187pub struct RootComponentDrainingAdvanceResponse {
1188 pub operation_id: [u8; 32],
1189 pub component: ComponentInstanceId,
1190 pub phase: RootComponentDrainingAdvancePhase,
1191}
1192
1193#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1200pub struct RootComponentFinalInventory {
1201 pub registry: ComponentRegistryHead,
1202 pub descendant_content_hash: [u8; 32],
1203 pub registry_encoded_bytes: u64,
1204 pub directory_synchronized_at_ns: u64,
1205 pub covered_fleet_registry_revision: u64,
1206 pub covered_fleet_registry_content_hash: [u8; 32],
1207 pub directory_authority_hash: [u8; 32],
1208 pub inventory_hash: [u8; 32],
1209 pub finalized_at_ns: u64,
1210}
1211
1212#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1219pub struct RootComponentFinalInventoryResponse {
1220 pub operation_id: [u8; 32],
1221 pub component: ComponentInstanceId,
1222 pub inventory: RootComponentFinalInventory,
1223}
1224
1225#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1232pub struct RootComponentDeletionIntent {
1233 pub final_inventory: RootComponentFinalInventory,
1234 pub quiescence: RootComponentQuiescentReceipt,
1235 pub prepared_at_ns: u64,
1236}
1237
1238#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1245pub struct RootComponentDeletedReceipt {
1246 pub deletion: RootComponentDeletionIntent,
1247 pub deleted_at_ns: u64,
1248}
1249
1250#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1257pub struct RootComponentMembershipRemovedReceipt {
1258 pub deleted: RootComponentDeletedReceipt,
1259 pub allocation_operation_id: [u8; 32],
1260 pub remaining_spec_committed_instances: u32,
1261 pub root_committed_component_instances: u32,
1262 pub root_known_created_component_canisters: u32,
1263 pub root_registry_encoded_bytes: u64,
1264 pub removed_at_ns: u64,
1265 pub removal_hash: [u8; 32],
1266}
1267
1268#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1275pub enum RootComponentDeletionPhase {
1276 DeleteIntent(RootComponentDeletionIntent),
1277 Deleted(RootComponentDeletedReceipt),
1278 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1279}
1280
1281#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1288pub struct RootComponentDeletionResponse {
1289 pub operation_id: [u8; 32],
1290 pub component: ComponentInstanceId,
1291 pub phase: RootComponentDeletionPhase,
1292}
1293
1294#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1301pub struct RootComponentChildCommitResponse {
1302 pub allocation: RootComponentChildAllocationResponse,
1303 pub registry: ComponentRegistryPartitionResponse,
1304 pub directory: ComponentDirectoryHead,
1305}
1306
1307#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1314pub struct RootComponentChildDirectoryPreparationResponse {
1315 pub committed: RootComponentChildCommitResponse,
1316 pub child: ComponentRuntimeStatusResponse,
1317 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1318 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1319}
1320
1321#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1328pub struct RootComponentChildRuntimeActivationResponse {
1329 pub committed: RootComponentChildCommitResponse,
1330 pub child: ComponentRuntimeStatusResponse,
1331}
1332
1333#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1340pub struct RootComponentChildMembershipActivationResponse {
1341 pub committed: RootComponentChildCommitResponse,
1342 pub registry: ComponentRegistryPartitionResponse,
1343 pub directory: ComponentDirectoryHead,
1344 pub child: ComponentRuntimeStatusResponse,
1345}
1346
1347#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1354pub struct RootComponentCommitResponse {
1355 pub allocation: RootComponentAllocationResponse,
1356 pub registry: ComponentRegistryPartitionResponse,
1357 pub directory: ComponentDirectoryHead,
1358}
1359
1360#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1367pub struct RootComponentDirectoryPreparationResponse {
1368 pub committed: RootComponentCommitResponse,
1369 pub target: ComponentRuntimeStatusResponse,
1370}
1371
1372#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1379pub struct RootComponentRuntimeActivationResponse {
1380 pub committed: RootComponentCommitResponse,
1381 pub target: ComponentRuntimeStatusResponse,
1382}
1383
1384#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1391pub struct RootComponentMembershipActivationResponse {
1392 pub allocation: RootComponentAllocationResponse,
1393 pub registry: ComponentRegistryPartitionResponse,
1394 pub directory: ComponentDirectoryHead,
1395 pub target: ComponentRuntimeStatusResponse,
1396}
1397
1398#[cfg(test)]
1399mod tests {
1400 use super::*;
1401 use crate::{
1402 dto::root_store::RootStoreBootstrapRequest,
1403 ids::{
1404 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1405 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1406 },
1407 };
1408
1409 #[test]
1410 fn component_registry_contracts_round_trip_through_candid() {
1411 let request = RootComponentRegistryPreparationRequest {
1412 store_bootstrap: RootStoreBootstrapRequest {
1413 manifest_payload_size_bytes: 128,
1414 },
1415 expected_fleet_registry: FleetRegistryVersion {
1416 authority: fleet_registry_authority(),
1417 revision: 4,
1418 content_hash: [5; 32],
1419 },
1420 };
1421 let response = RootComponentRegistryStatusResponse {
1422 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1423 prepared_against_registry: request.expected_fleet_registry.clone(),
1424 release_set: FleetSubnetRootReleaseSet {
1425 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1426 [7; 32],
1427 )),
1428 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1429 },
1430 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1431 next_allocation_sequence: 1,
1432 reserved_component_instances: 0,
1433 committed_component_instances: 0,
1434 managed_descendants: 0,
1435 known_created_component_canisters: 0,
1436 encoded_bytes: 0,
1437 initial_inventory: Some(RootComponentInitialInventoryStatus {
1438 fleet_activation_operation_id: [10; 32],
1439 component_count: 0,
1440 inventory_hash: [11; 32],
1441 sealed_at_ns: 12,
1442 directories_converged: true,
1443 root_runtime_activated: true,
1444 }),
1445 };
1446 let allocation = RootComponentAllocationResponse {
1447 operation_id: [10; 32],
1448 allocation_sequence: 1,
1449 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1450 component_spec: "projects".parse().expect("Component Spec ID"),
1451 spec_hash: [12; 32],
1452 role: CanisterRole::new("project_hub"),
1453 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1454 caller: Principal::from_slice(&[13; 29]),
1455 },
1456 release_set: response.release_set,
1457 phase: RootComponentAllocationPhase::Reserved,
1458 creation: None,
1459 installation: None,
1460 };
1461 let created = RootComponentAllocationResponse {
1462 phase: RootComponentAllocationPhase::Created,
1463 creation: Some(RootComponentCreationEvidence {
1464 wasm_store: Principal::from_slice(&[14; 29]),
1465 payload_hash: [15; 32],
1466 payload_size_bytes: 4_096,
1467 initial_cycles: Cycles::new(5_000_000_000_000),
1468 controller: Principal::from_slice(&[6; 29]),
1469 canister: Some(Principal::from_slice(&[16; 29])),
1470 }),
1471 installation: None,
1472 ..allocation.clone()
1473 };
1474 let request_bytes = candid::encode_one(&request).expect("encode request");
1475 let response_bytes = candid::encode_one(&response).expect("encode response");
1476 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1477 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1478
1479 assert_eq!(
1480 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1481 .expect("decode request"),
1482 request
1483 );
1484 assert_eq!(
1485 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1486 .expect("decode response"),
1487 response
1488 );
1489 assert_eq!(
1490 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1491 .expect("decode allocation"),
1492 allocation
1493 );
1494 assert_eq!(
1495 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1496 .expect("decode created allocation"),
1497 created
1498 );
1499 }
1500
1501 #[test]
1502 fn component_commit_response_round_trips_through_candid() {
1503 let root = Principal::from_slice(&[6; 29]);
1504 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1505 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1506 let release_set = FleetSubnetRootReleaseSet {
1507 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1508 [7; 32],
1509 )),
1510 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1511 };
1512 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1513 caller: Principal::from_slice(&[13; 29]),
1514 };
1515 let binding = ComponentBinding {
1516 authority: fleet_registry_authority(),
1517 component,
1518 component_spec: component_spec.clone(),
1519 spec_hash: [12; 32],
1520 role: CanisterRole::new("project_hub"),
1521 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1522 fleet_subnet_root: root,
1523 canister_id: Principal::from_slice(&[16; 29]),
1524 };
1525 let head = ComponentRegistryHead {
1526 component,
1527 revision: 1,
1528 content_hash: [18; 32],
1529 };
1530 let committed = RootComponentCommitResponse {
1531 allocation: RootComponentAllocationResponse {
1532 operation_id: [10; 32],
1533 allocation_sequence: 1,
1534 component,
1535 component_spec,
1536 spec_hash: binding.spec_hash,
1537 role: binding.role.clone(),
1538 provisioning_origin: provisioning_origin.clone(),
1539 release_set,
1540 phase: RootComponentAllocationPhase::Committed,
1541 creation: Some(RootComponentCreationEvidence {
1542 wasm_store: Principal::from_slice(&[14; 29]),
1543 payload_hash: [15; 32],
1544 payload_size_bytes: 4_096,
1545 initial_cycles: Cycles::new(5_000_000_000_000),
1546 controller: root,
1547 canister: Some(binding.canister_id),
1548 }),
1549 installation: Some(RootComponentInstallEvidence {
1550 raw_module_hash: [20; 32],
1551 chunk_hashes: vec![vec![21; 32]],
1552 binding: binding.clone(),
1553 }),
1554 },
1555 registry: ComponentRegistryPartitionResponse {
1556 head: head.clone(),
1557 binding: binding.clone(),
1558 provisioning_origin,
1559 release_set,
1560 status: ComponentLifecycleStatus::Prepared,
1561 reserved_descendants: 0,
1562 committed_descendants: 0,
1563 encoded_bytes: 2_048,
1564 },
1565 directory: ComponentDirectoryHead {
1566 provenance: ComponentDirectoryProvenance {
1567 component: binding,
1568 source_fleet_subnet_root: root,
1569 component_registry_revision: head.revision,
1570 component_registry_content_hash: head.content_hash,
1571 synchronized_at_ns: 19,
1572 },
1573 descendant_count: 0,
1574 },
1575 };
1576 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1577
1578 assert_eq!(
1579 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1580 .expect("decode committed allocation"),
1581 committed
1582 );
1583 }
1584
1585 #[test]
1586 fn component_directory_page_contracts_round_trip_through_candid() {
1587 let root = Principal::from_slice(&[6; 29]);
1588 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1589 let binding = ComponentBinding {
1590 authority: fleet_registry_authority(),
1591 component,
1592 component_spec: "projects".parse().expect("Component Spec ID"),
1593 spec_hash: [12; 32],
1594 role: CanisterRole::new("project_hub"),
1595 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1596 fleet_subnet_root: root,
1597 canister_id: Principal::from_slice(&[16; 29]),
1598 };
1599 let directory = ComponentDirectoryHead {
1600 provenance: ComponentDirectoryProvenance {
1601 component: binding.clone(),
1602 source_fleet_subnet_root: root,
1603 component_registry_revision: 3,
1604 component_registry_content_hash: [18; 32],
1605 synchronized_at_ns: 19,
1606 },
1607 descendant_count: 1,
1608 };
1609 let request = ComponentDirectoryPageRequest {
1610 directory: directory.clone(),
1611 parent_canister_id: Some(binding.canister_id),
1612 role: Some(CanisterRole::new("project_instance")),
1613 status: Some(ComponentLifecycleStatus::Active),
1614 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1615 limit: 50,
1616 };
1617 let response = ComponentDirectoryPageResponse {
1618 directory,
1619 entries: vec![ComponentDirectoryChildEntry {
1620 binding: ComponentChildBinding {
1621 component: binding.clone(),
1622 parent_canister_id: binding.canister_id,
1623 role: CanisterRole::new("project_instance"),
1624 canister_id: Principal::from_slice(&[21; 29]),
1625 },
1626 kind: ComponentChildKind::Instance,
1627 installed_artifact_hash: [22; 32],
1628 status: ComponentLifecycleStatus::Active,
1629 }],
1630 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1631 };
1632 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1633 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1634
1635 assert_eq!(
1636 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1637 .expect("decode Directory page request"),
1638 request
1639 );
1640 assert_eq!(
1641 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1642 .expect("decode Directory page response"),
1643 response
1644 );
1645 }
1646
1647 fn fleet_registry_authority() -> FleetRegistryAuthority {
1648 FleetRegistryAuthority {
1649 binding: FleetCoordinatorBinding {
1650 fleet: crate::ids::FleetBinding {
1651 fleet: FleetKey {
1652 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1653 fleet_id: FleetId::from_generated_bytes([1; 32]),
1654 },
1655 app: AppId::from("toko"),
1656 },
1657 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1658 coordinator: Principal::from_slice(&[3; 29]),
1659 },
1660 epoch: 1,
1661 }
1662 }
1663
1664 #[test]
1665 fn component_creation_request_round_trips_through_candid() {
1666 let request = RootComponentCreationRequest {
1667 operation_id: [10; 32],
1668 };
1669 let bytes = candid::encode_one(request).expect("encode creation request");
1670
1671 assert_eq!(
1672 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1673 .expect("decode creation request"),
1674 request
1675 );
1676 }
1677
1678 #[test]
1679 fn peer_component_provisioning_origin_round_trips_through_candid() {
1680 let authority = fleet_registry_authority();
1681 let requester_spec: ComponentSpecId =
1682 "projects".parse().expect("requester Component Spec ID");
1683 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1684 let origin = ComponentProvisioningOrigin::Component {
1685 requester: Box::new(ComponentBinding {
1686 authority,
1687 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1688 component_spec: requester_spec.clone(),
1689 spec_hash: [21; 32],
1690 role: CanisterRole::new("project_hub"),
1691 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1692 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1693 canister_id: Principal::from_slice(&[24; 29]),
1694 }),
1695 grant: Box::new(crate::config::ComponentProvisioningGrant {
1696 requester_component_spec: requester_spec,
1697 target_component_spec: target_spec,
1698 maximum_instances_per_requester_per_root: 3,
1699 }),
1700 };
1701 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1702
1703 assert_eq!(
1704 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1705 .expect("decode peer provisioning origin"),
1706 origin
1707 );
1708 }
1709
1710 #[test]
1711 fn component_group_provisioning_origin_round_trips_through_candid() {
1712 let origin = ComponentProvisioningOrigin::ComponentGroup {
1713 operation_id: [25; 32],
1714 plan_hash: [26; 32],
1715 group_placement: ComponentGroupPlacementId {
1716 deployment: "cells".parse().expect("deployment ID"),
1717 ordinal: 3,
1718 },
1719 member_path: ComponentGroupMemberPath::try_from(vec![
1720 "api".parse().expect("member ID"),
1721 ])
1722 .expect("member path"),
1723 };
1724 let bytes = candid::encode_one(&origin).expect("encode group provisioning origin");
1725
1726 assert_eq!(
1727 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1728 .expect("decode group provisioning origin"),
1729 origin
1730 );
1731 }
1732
1733 #[test]
1734 #[expect(
1735 clippy::too_many_lines,
1736 reason = "one Candid contract test covers every subtree-removal phase receipt"
1737 )]
1738 fn component_subtree_removal_contracts_round_trip_through_candid() {
1739 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1740 let registry = ComponentRegistryHead {
1741 component,
1742 revision: 7,
1743 content_hash: [42; 32],
1744 };
1745 let request = RootComponentSubtreeRemovalRequest {
1746 operation_id: [43; 32],
1747 component,
1748 target_canister_id: Principal::from_slice(&[44; 29]),
1749 expected_registry: registry.clone(),
1750 };
1751 let status_request = RootComponentSubtreeRemovalStatusRequest {
1752 operation_id: request.operation_id,
1753 component,
1754 };
1755 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1756 operation_id: request.operation_id,
1757 component,
1758 expected_traversal_steps: 1,
1759 };
1760 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1761 operation_id: request.operation_id,
1762 component,
1763 expected_traversal_steps: 2,
1764 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1765 expected_leaf_parent_canister_id: request.target_canister_id,
1766 };
1767 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1768 observed_module_hash: [49; 32],
1769 stop: RootComponentSubtreeRemovalStopIntent {
1770 controller: Principal::from_slice(&[48; 29]),
1771 leaf: RootComponentSubtreeRemovalNode {
1772 canister_id: Principal::from_slice(&[46; 29]),
1773 parent_canister_id: request.target_canister_id,
1774 role: CanisterRole::new("project_ledger"),
1775 kind: ComponentChildKind::Singleton,
1776 installed_artifact_hash: [47; 32],
1777 status: ComponentLifecycleStatus::Active,
1778 },
1779 },
1780 };
1781 let response = RootComponentSubtreeRemovalResponse {
1782 operation_id: request.operation_id,
1783 component,
1784 target_canister_id: request.target_canister_id,
1785 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1786 target_role: CanisterRole::new("project_instance"),
1787 target_status: ComponentLifecycleStatus::Active,
1788 reserved_against_registry: registry,
1789 maximum_completed_leaves: 4,
1790 completed_leaves: 1,
1791 traversal_steps: 2,
1792 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1793 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1794 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1795 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1796 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1797 },
1798 removed_from_registry: ComponentRegistryHead {
1799 component,
1800 revision: 8,
1801 content_hash: [50; 32],
1802 },
1803 previous_descendant_content_hash: [51; 32],
1804 previous_committed_descendants: 4,
1805 registry: ComponentRegistryHead {
1806 component,
1807 revision: 9,
1808 content_hash: [52; 32],
1809 },
1810 descendant_content_hash: [53; 32],
1811 registry_encoded_bytes: 4_096,
1812 reserved_descendants: 1,
1813 committed_descendants: 3,
1814 directory_synchronized_at_ns: 54,
1815 directory_authority_hash: [55; 32],
1816 parent_role_instances: 0,
1817 root_managed_descendants: 4,
1818 root_known_created_component_canisters: 4,
1819 },
1820 covered_fleet_registry_revision: 6,
1821 covered_fleet_registry_content_hash: [56; 32],
1822 covered_component_registry: ComponentRegistryHead {
1823 component,
1824 revision: 9,
1825 content_hash: [52; 32],
1826 },
1827 covered_authority_hash: [55; 32],
1828 owning_component: Some(
1829 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1830 operation_id: [57; 32],
1831 canister_id: Principal::from_slice(&[58; 29]),
1832 activation: ComponentRuntimeActivationEvidence {
1833 directory_authority_hash: [59; 32],
1834 activated_at_ns: 60,
1835 },
1836 },
1837 ),
1838 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1839 operation_id: [61; 32],
1840 canister_id: request.target_canister_id,
1841 activation: ComponentRuntimeActivationEvidence {
1842 directory_authority_hash: [62; 32],
1843 activated_at_ns: 63,
1844 },
1845 }),
1846 },
1847 ),
1848 };
1849
1850 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1851 let advance_bytes =
1852 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1853 let stop_bytes =
1854 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1855 let status_bytes =
1856 candid::encode_one(status_request).expect("encode subtree removal status request");
1857 let response_bytes =
1858 candid::encode_one(&response).expect("encode subtree removal response");
1859
1860 assert_eq!(
1861 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1862 .expect("decode subtree removal request"),
1863 request
1864 );
1865 assert_eq!(
1866 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1867 .expect("decode subtree removal advance request"),
1868 advance_request
1869 );
1870 assert_eq!(
1871 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1872 .expect("decode subtree removal stop request"),
1873 stop_request
1874 );
1875 assert_eq!(
1876 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1877 .expect("decode subtree removal status request"),
1878 status_request
1879 );
1880 assert_eq!(
1881 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1882 .expect("decode subtree removal response"),
1883 response
1884 );
1885
1886 let mut quiescent_owner_response = response;
1887 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
1888 &mut quiescent_owner_response.phase
1889 else {
1890 panic!("Directory-synchronized response");
1891 };
1892 receipt.owning_component = None;
1893 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
1894 .expect("encode quiescent-owner subtree response");
1895 assert_eq!(
1896 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
1897 .expect("decode quiescent-owner subtree response"),
1898 quiescent_owner_response
1899 );
1900 }
1901
1902 #[test]
1903 fn component_draining_contracts_round_trip_through_candid() {
1904 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1905 let previous_registry = ComponentRegistryHead {
1906 component,
1907 revision: 7,
1908 content_hash: [61; 32],
1909 };
1910 let request = RootComponentDrainingRequest {
1911 operation_id: [62; 32],
1912 component,
1913 expected_registry: previous_registry.clone(),
1914 };
1915 let status_request = RootComponentDrainingStatusRequest {
1916 operation_id: request.operation_id,
1917 component,
1918 };
1919 let response = RootComponentDrainingResponse {
1920 operation_id: request.operation_id,
1921 component,
1922 previous_registry,
1923 registry: ComponentRegistryHead {
1924 component,
1925 revision: 8,
1926 content_hash: [63; 32],
1927 },
1928 descendant_count: 20_000,
1929 descendant_content_hash: [64; 32],
1930 directory_authority_hash: [65; 32],
1931 started_at_ns: 66,
1932 };
1933
1934 let request_bytes =
1935 candid::encode_one(&request).expect("encode Component draining request");
1936 let status_bytes =
1937 candid::encode_one(status_request).expect("encode Component draining status request");
1938 let response_bytes =
1939 candid::encode_one(&response).expect("encode Component draining response");
1940
1941 assert_eq!(
1942 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1943 .expect("decode Component draining request"),
1944 request
1945 );
1946 assert_eq!(
1947 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1948 .expect("decode Component draining status request"),
1949 status_request
1950 );
1951 assert_eq!(
1952 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1953 .expect("decode Component draining response"),
1954 response
1955 );
1956 }
1957
1958 #[test]
1959 fn component_quiescence_contracts_round_trip_through_candid() {
1960 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
1961 let registry = ComponentRegistryHead {
1962 component,
1963 revision: 9,
1964 content_hash: [68; 32],
1965 };
1966 let request = RootComponentQuiescenceRequest {
1967 operation_id: [69; 32],
1968 component,
1969 expected_registry: registry.clone(),
1970 };
1971 let status_request = RootComponentQuiescenceStatusRequest {
1972 operation_id: request.operation_id,
1973 component,
1974 };
1975 let stop = RootComponentQuiescenceStopIntent {
1976 registry,
1977 descendant_count: 20_000,
1978 descendant_content_hash: [70; 32],
1979 canister_id: Principal::from_slice(&[71; 29]),
1980 controller: Principal::from_slice(&[72; 29]),
1981 expected_module_hash: [73; 32],
1982 covered_fleet_registry_revision: 10,
1983 covered_fleet_registry_content_hash: [74; 32],
1984 covered_authority_hash: [75; 32],
1985 runtime_operation_id: [76; 32],
1986 activation: ComponentRuntimeActivationEvidence {
1987 directory_authority_hash: [77; 32],
1988 activated_at_ns: 78,
1989 },
1990 prepared_at_ns: 79,
1991 };
1992 let response = RootComponentQuiescenceResponse {
1993 operation_id: request.operation_id,
1994 component,
1995 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
1996 stop,
1997 observed_module_hash: [73; 32],
1998 quiesced_at_ns: 80,
1999 }),
2000 };
2001
2002 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
2003 let status_bytes =
2004 candid::encode_one(status_request).expect("encode quiescence status request");
2005 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
2006 assert_eq!(
2007 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
2008 .expect("decode quiescence request"),
2009 request
2010 );
2011 assert_eq!(
2012 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
2013 .expect("decode quiescence status request"),
2014 status_request
2015 );
2016 assert_eq!(
2017 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
2018 .expect("decode quiescence response"),
2019 response
2020 );
2021 }
2022
2023 #[test]
2024 fn component_draining_advance_contracts_round_trip_through_candid() {
2025 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
2026 let registry = ComponentRegistryHead {
2027 component,
2028 revision: 12,
2029 content_hash: [82; 32],
2030 };
2031 let request = RootComponentDrainingAdvanceRequest {
2032 operation_id: [83; 32],
2033 component,
2034 };
2035 let descendant_removal = RootComponentDrainingAdvanceResponse {
2036 operation_id: request.operation_id,
2037 component,
2038 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2039 RootComponentSubtreeRemovalResponse {
2040 operation_id: [84; 32],
2041 component,
2042 target_canister_id: Principal::from_slice(&[85; 29]),
2043 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2044 target_role: CanisterRole::new("project_instance"),
2045 target_status: ComponentLifecycleStatus::Active,
2046 reserved_against_registry: registry.clone(),
2047 maximum_completed_leaves: 20_000,
2048 completed_leaves: 0,
2049 traversal_steps: 0,
2050 phase: RootComponentSubtreeRemovalPhase::Fenced,
2051 },
2052 ),
2053 };
2054 let descendants_empty = RootComponentDrainingAdvanceResponse {
2055 operation_id: request.operation_id,
2056 component,
2057 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2058 RootComponentDrainingDescendantsEmpty {
2059 registry,
2060 descendant_content_hash: [87; 32],
2061 },
2062 ),
2063 };
2064
2065 let request_bytes =
2066 candid::encode_one(request).expect("encode Component draining advance request");
2067 let removal_bytes = candid::encode_one(&descendant_removal)
2068 .expect("encode Component draining removal response");
2069 let empty_bytes = candid::encode_one(&descendants_empty)
2070 .expect("encode Component draining empty response");
2071
2072 assert_eq!(
2073 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2074 .expect("decode Component draining advance request"),
2075 request
2076 );
2077 assert_eq!(
2078 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2079 .expect("decode Component draining removal response"),
2080 descendant_removal
2081 );
2082 assert_eq!(
2083 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2084 .expect("decode Component draining empty response"),
2085 descendants_empty
2086 );
2087 }
2088
2089 #[test]
2090 #[expect(
2091 clippy::too_many_lines,
2092 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2093 )]
2094 fn component_final_inventory_contracts_round_trip_through_candid() {
2095 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2096 let registry = ComponentRegistryHead {
2097 component,
2098 revision: 21,
2099 content_hash: [89; 32],
2100 };
2101 let request = RootComponentFinalInventoryRequest {
2102 operation_id: [90; 32],
2103 component,
2104 expected_registry: registry.clone(),
2105 };
2106 let inventory = RootComponentFinalInventory {
2107 registry,
2108 descendant_content_hash: [91; 32],
2109 registry_encoded_bytes: 4_096,
2110 directory_synchronized_at_ns: 92,
2111 covered_fleet_registry_revision: 93,
2112 covered_fleet_registry_content_hash: [94; 32],
2113 directory_authority_hash: [95; 32],
2114 inventory_hash: [96; 32],
2115 finalized_at_ns: 97,
2116 };
2117 let response = RootComponentFinalInventoryResponse {
2118 operation_id: request.operation_id,
2119 component,
2120 inventory: inventory.clone(),
2121 };
2122 let deletion_request = RootComponentDeletionRequest {
2123 operation_id: request.operation_id,
2124 component,
2125 expected_inventory_hash: inventory.inventory_hash,
2126 };
2127 let deletion_status_request = RootComponentDeletionStatusRequest {
2128 operation_id: request.operation_id,
2129 component,
2130 };
2131 let deletion = RootComponentDeletionIntent {
2132 final_inventory: inventory,
2133 quiescence: RootComponentQuiescentReceipt {
2134 stop: RootComponentQuiescenceStopIntent {
2135 registry: response.inventory.registry.clone(),
2136 descendant_count: 0,
2137 descendant_content_hash: response.inventory.descendant_content_hash,
2138 canister_id: Principal::from_slice(&[98; 29]),
2139 controller: Principal::from_slice(&[99; 29]),
2140 expected_module_hash: [100; 32],
2141 covered_fleet_registry_revision: 93,
2142 covered_fleet_registry_content_hash: [94; 32],
2143 covered_authority_hash: [101; 32],
2144 runtime_operation_id: [102; 32],
2145 activation: ComponentRuntimeActivationEvidence {
2146 directory_authority_hash: [103; 32],
2147 activated_at_ns: 104,
2148 },
2149 prepared_at_ns: 105,
2150 },
2151 observed_module_hash: [100; 32],
2152 quiesced_at_ns: 106,
2153 },
2154 prepared_at_ns: 107,
2155 };
2156 let deleted_receipt = RootComponentDeletedReceipt {
2157 deletion,
2158 deleted_at_ns: 108,
2159 };
2160 let deletion_response = RootComponentDeletionResponse {
2161 operation_id: request.operation_id,
2162 component,
2163 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2164 };
2165 let membership_removed_response = RootComponentDeletionResponse {
2166 operation_id: request.operation_id,
2167 component,
2168 phase: RootComponentDeletionPhase::MembershipRemoved(
2169 RootComponentMembershipRemovedReceipt {
2170 deleted: deleted_receipt,
2171 allocation_operation_id: [109; 32],
2172 remaining_spec_committed_instances: 2,
2173 root_committed_component_instances: 3,
2174 root_known_created_component_canisters: 4,
2175 root_registry_encoded_bytes: 5_000,
2176 removed_at_ns: 110,
2177 removal_hash: [111; 32],
2178 },
2179 ),
2180 };
2181
2182 let request_bytes =
2183 candid::encode_one(&request).expect("encode Component final inventory request");
2184 let response_bytes =
2185 candid::encode_one(&response).expect("encode Component final inventory response");
2186 let deletion_request_bytes =
2187 candid::encode_one(deletion_request).expect("encode Component deletion request");
2188 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2189 .expect("encode Component deletion status request");
2190 let deletion_response_bytes =
2191 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2192 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2193 .expect("encode Component membership-removal response");
2194 assert_eq!(
2195 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2196 .expect("decode Component final inventory request"),
2197 request
2198 );
2199 assert_eq!(
2200 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2201 .expect("decode Component final inventory response"),
2202 response
2203 );
2204 assert_eq!(
2205 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2206 .expect("decode Component deletion request"),
2207 deletion_request
2208 );
2209 assert_eq!(
2210 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2211 .expect("decode Component deletion status request"),
2212 deletion_status_request
2213 );
2214 assert_eq!(
2215 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2216 .expect("decode Component deletion response"),
2217 deletion_response
2218 );
2219 assert_eq!(
2220 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2221 .expect("decode Component membership-removal response"),
2222 membership_removed_response
2223 );
2224 }
2225
2226 #[test]
2227 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2228 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2229 operation_id: [50; 32],
2230 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2231 expected_traversal_steps: 3,
2232 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2233 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2234 };
2235 let request = RootComponentSubtreeRemovalDeleteRequest {
2236 operation_id: prepare.operation_id,
2237 component: prepare.component,
2238 expected_traversal_steps: prepare.expected_traversal_steps,
2239 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2240 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2241 };
2242 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2243 operation_id: prepare.operation_id,
2244 component: prepare.component,
2245 expected_traversal_steps: prepare.expected_traversal_steps,
2246 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2247 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2248 };
2249 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2250 operation_id: prepare.operation_id,
2251 component: prepare.component,
2252 expected_traversal_steps: prepare.expected_traversal_steps,
2253 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2254 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2255 };
2256 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2257 operation_id: prepare.operation_id,
2258 component: prepare.component,
2259 expected_traversal_steps: prepare.expected_traversal_steps,
2260 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2261 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2262 };
2263 let prepare_bytes = candid::encode_one(prepare)
2264 .expect("encode subtree removal deletion preparation request");
2265 let request_bytes =
2266 candid::encode_one(request).expect("encode subtree removal deletion request");
2267 let membership_request_bytes = candid::encode_one(membership_request)
2268 .expect("encode subtree removal membership-removal request");
2269 let directory_request_bytes = candid::encode_one(directory_request)
2270 .expect("encode subtree removal Directory synchronization request");
2271 let finalization_request_bytes = candid::encode_one(finalization_request)
2272 .expect("encode subtree removal leaf-finalization request");
2273
2274 assert_eq!(
2275 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2276 &prepare_bytes
2277 )
2278 .expect("decode subtree removal deletion preparation request"),
2279 prepare
2280 );
2281 assert_eq!(
2282 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2283 .expect("decode subtree removal deletion request"),
2284 request
2285 );
2286 assert_eq!(
2287 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2288 &membership_request_bytes
2289 )
2290 .expect("decode subtree removal membership-removal request"),
2291 membership_request
2292 );
2293 assert_eq!(
2294 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2295 &directory_request_bytes
2296 )
2297 .expect("decode subtree removal Directory synchronization request"),
2298 directory_request
2299 );
2300 assert_eq!(
2301 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2302 &finalization_request_bytes
2303 )
2304 .expect("decode subtree removal leaf-finalization request"),
2305 finalization_request
2306 );
2307 }
2308
2309 #[test]
2310 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2311 let request = RootComponentSubtreeRemovalStopRequest {
2312 operation_id: [50; 32],
2313 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2314 expected_traversal_steps: 3,
2315 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2316 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2317 };
2318 let bytes =
2319 candid::encode_one(request).expect("encode subtree removal stop execution request");
2320
2321 assert_eq!(
2322 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2323 .expect("decode subtree removal stop execution request"),
2324 request
2325 );
2326 }
2327
2328 #[test]
2329 #[expect(
2330 clippy::too_many_lines,
2331 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2332 )]
2333 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2334 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2335 let registry = ComponentRegistryHead {
2336 component,
2337 revision: 2,
2338 content_hash: [12; 32],
2339 };
2340 let request = RootComponentChildAllocationRequest {
2341 operation_id: [13; 32],
2342 component,
2343 expected_registry: registry.clone(),
2344 child_role: CanisterRole::new("project_instance"),
2345 application_init_args: Some(vec![9, 8, 7]),
2346 };
2347 let status_request = RootComponentChildAllocationStatusRequest {
2348 operation_id: request.operation_id,
2349 component,
2350 };
2351 let creation_request = RootComponentChildCreationRequest {
2352 operation_id: request.operation_id,
2353 component,
2354 };
2355 let install_request = RootComponentChildInstallRequest {
2356 operation_id: request.operation_id,
2357 component,
2358 };
2359 let commit_request = RootComponentChildCommitRequest {
2360 operation_id: request.operation_id,
2361 component,
2362 };
2363 let directory_request = RootComponentChildDirectoryPreparationRequest {
2364 operation_id: request.operation_id,
2365 component,
2366 };
2367 let activation_request = RootComponentChildRuntimeActivationRequest {
2368 operation_id: request.operation_id,
2369 component,
2370 };
2371 let membership_request = RootComponentChildMembershipActivationRequest {
2372 operation_id: request.operation_id,
2373 component,
2374 };
2375 let root = Principal::from_slice(&[17; 29]);
2376 let parent = Principal::from_slice(&[14; 29]);
2377 let child = Principal::from_slice(&[18; 29]);
2378 let child_binding = ComponentChildBinding {
2379 component: ComponentBinding {
2380 authority: fleet_registry_authority(),
2381 component,
2382 component_spec: "projects".parse().expect("Component Spec"),
2383 spec_hash: [19; 32],
2384 role: CanisterRole::new("project_hub"),
2385 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2386 fleet_subnet_root: root,
2387 canister_id: parent,
2388 },
2389 parent_canister_id: parent,
2390 role: request.child_role.clone(),
2391 canister_id: child,
2392 };
2393 let response = RootComponentChildAllocationResponse {
2394 operation_id: request.operation_id,
2395 component,
2396 parent_canister_id: parent,
2397 parent_role: CanisterRole::new("project_hub"),
2398 child_role: request.child_role.clone(),
2399 child_kind: ComponentChildKind::Instance,
2400 maximum_instances_per_parent: 10_000,
2401 maximum_descendants: 20_000,
2402 maximum_registry_bytes: 16_777_216,
2403 reserved_against_registry: registry,
2404 release_set: FleetSubnetRootReleaseSet {
2405 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2406 [15; 32],
2407 )),
2408 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2409 },
2410 phase: RootComponentAllocationPhase::Verified,
2411 creation: Some(RootComponentCreationEvidence {
2412 wasm_store: Principal::from_slice(&[21; 29]),
2413 payload_hash: [22; 32],
2414 payload_size_bytes: 4_096,
2415 initial_cycles: Cycles::new(5_000_000_000_000),
2416 controller: root,
2417 canister: Some(child),
2418 }),
2419 installation: Some(RootComponentChildInstallEvidence {
2420 raw_module_hash: [23; 32],
2421 chunk_hashes: vec![vec![24; 32]],
2422 binding: child_binding.clone(),
2423 }),
2424 };
2425 let commit_response = RootComponentChildCommitResponse {
2426 allocation: response.clone(),
2427 registry: ComponentRegistryPartitionResponse {
2428 head: ComponentRegistryHead {
2429 component,
2430 revision: 3,
2431 content_hash: [25; 32],
2432 },
2433 binding: child_binding.component.clone(),
2434 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2435 caller: Principal::from_slice(&[26; 29]),
2436 },
2437 release_set: response.release_set,
2438 status: ComponentLifecycleStatus::Active,
2439 reserved_descendants: 0,
2440 committed_descendants: 1,
2441 encoded_bytes: 8_192,
2442 },
2443 directory: ComponentDirectoryHead {
2444 provenance: ComponentDirectoryProvenance {
2445 component: child_binding.component.clone(),
2446 source_fleet_subnet_root: root,
2447 component_registry_revision: 3,
2448 component_registry_content_hash: [25; 32],
2449 synchronized_at_ns: 27,
2450 },
2451 descendant_count: 1,
2452 },
2453 };
2454 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2455 fleet: FleetDirectorySnapshot {
2456 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2457 registry: FleetRegistryVersion {
2458 authority: fleet_registry_authority(),
2459 revision: 4,
2460 content_hash: [28; 32],
2461 },
2462 source_fleet_subnet_root: root,
2463 },
2464 fleet_subnet_roots: vec![
2465 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2466 placement_subnet: commit_response.registry.binding.placement_subnet,
2467 fleet_subnet_root: root,
2468 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2469 },
2470 ],
2471 },
2472 component: commit_response.directory.clone(),
2473 };
2474 let activation = ComponentRuntimeActivationEvidence {
2475 directory_authority_hash: [29; 32],
2476 activated_at_ns: 30,
2477 };
2478 let directory_response = RootComponentChildDirectoryPreparationResponse {
2479 committed: commit_response.clone(),
2480 child: ComponentRuntimeStatusResponse {
2481 operation_id: request.operation_id,
2482 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2483 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2484 binding: child_binding.component.clone(),
2485 }),
2486 phase: ComponentRuntimePhase::DirectoryPrepared,
2487 authority: Some(runtime_authority.clone()),
2488 authority_hash: Some([31; 32]),
2489 direct_children_hash: Some([37; 32]),
2490 activation: None,
2491 },
2492 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2493 operation_id: [32; 32],
2494 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2495 covered_authority: runtime_authority.clone(),
2496 covered_authority_hash: [31; 32],
2497 activation,
2498 },
2499 parent: None,
2500 };
2501 let activation_response = RootComponentChildRuntimeActivationResponse {
2502 committed: commit_response.clone(),
2503 child: ComponentRuntimeStatusResponse {
2504 operation_id: request.operation_id,
2505 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2506 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2507 binding: child_binding.component.clone(),
2508 }),
2509 phase: ComponentRuntimePhase::Active,
2510 authority: Some(runtime_authority.clone()),
2511 authority_hash: Some([31; 32]),
2512 direct_children_hash: Some([37; 32]),
2513 activation: Some(ComponentRuntimeActivationEvidence {
2514 directory_authority_hash: [31; 32],
2515 activated_at_ns: 33,
2516 }),
2517 },
2518 };
2519 let active_directory = ComponentDirectoryHead {
2520 provenance: ComponentDirectoryProvenance {
2521 component: child_binding.component.clone(),
2522 source_fleet_subnet_root: root,
2523 component_registry_revision: 4,
2524 component_registry_content_hash: [34; 32],
2525 synchronized_at_ns: 35,
2526 },
2527 descendant_count: 1,
2528 };
2529 let active_authority = ComponentRuntimeDirectoryAuthority {
2530 fleet: runtime_authority.fleet,
2531 component: active_directory.clone(),
2532 };
2533 let membership_response = RootComponentChildMembershipActivationResponse {
2534 committed: commit_response.clone(),
2535 registry: ComponentRegistryPartitionResponse {
2536 head: ComponentRegistryHead {
2537 component,
2538 revision: 4,
2539 content_hash: [34; 32],
2540 },
2541 binding: child_binding.component.clone(),
2542 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2543 release_set: commit_response.registry.release_set,
2544 status: ComponentLifecycleStatus::Active,
2545 reserved_descendants: 0,
2546 committed_descendants: 1,
2547 encoded_bytes: 8_256,
2548 },
2549 directory: active_directory,
2550 child: ComponentRuntimeStatusResponse {
2551 operation_id: request.operation_id,
2552 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2553 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2554 binding: child_binding.component,
2555 }),
2556 phase: ComponentRuntimePhase::Active,
2557 authority: Some(active_authority),
2558 authority_hash: Some([36; 32]),
2559 direct_children_hash: Some([38; 32]),
2560 activation: Some(ComponentRuntimeActivationEvidence {
2561 directory_authority_hash: [31; 32],
2562 activated_at_ns: 33,
2563 }),
2564 },
2565 };
2566
2567 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2568 let status_bytes =
2569 candid::encode_one(status_request).expect("encode child reservation status");
2570 let creation_bytes =
2571 candid::encode_one(creation_request).expect("encode child creation request");
2572 let install_bytes =
2573 candid::encode_one(install_request).expect("encode child install request");
2574 let response_bytes = candid::encode_one(&response).expect("encode child response");
2575 let commit_request_bytes =
2576 candid::encode_one(commit_request).expect("encode child commit request");
2577 let directory_request_bytes =
2578 candid::encode_one(directory_request).expect("encode child Directory request");
2579 let activation_request_bytes =
2580 candid::encode_one(activation_request).expect("encode child activation request");
2581 let membership_request_bytes =
2582 candid::encode_one(membership_request).expect("encode child membership request");
2583 let commit_response_bytes =
2584 candid::encode_one(&commit_response).expect("encode child commit response");
2585 let directory_response_bytes =
2586 candid::encode_one(&directory_response).expect("encode child Directory response");
2587 let activation_response_bytes =
2588 candid::encode_one(&activation_response).expect("encode child activation response");
2589 let membership_response_bytes =
2590 candid::encode_one(&membership_response).expect("encode child membership response");
2591
2592 assert_eq!(
2593 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2594 .expect("decode child reservation"),
2595 request
2596 );
2597 assert_eq!(
2598 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2599 .expect("decode child reservation status"),
2600 status_request
2601 );
2602 assert_eq!(
2603 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2604 .expect("decode child creation request"),
2605 creation_request
2606 );
2607 assert_eq!(
2608 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2609 .expect("decode child install request"),
2610 install_request
2611 );
2612 assert_eq!(
2613 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2614 .expect("decode child response"),
2615 response
2616 );
2617 assert_eq!(
2618 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2619 .expect("decode child commit request"),
2620 commit_request
2621 );
2622 assert_eq!(
2623 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2624 &directory_request_bytes
2625 )
2626 .expect("decode child Directory request"),
2627 directory_request
2628 );
2629 assert_eq!(
2630 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2631 .expect("decode child commit response"),
2632 commit_response
2633 );
2634 assert_eq!(
2635 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2636 &directory_response_bytes
2637 )
2638 .expect("decode child Directory response"),
2639 directory_response
2640 );
2641 assert_eq!(
2642 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2643 &activation_request_bytes
2644 )
2645 .expect("decode child activation request"),
2646 activation_request
2647 );
2648 assert_eq!(
2649 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2650 &activation_response_bytes
2651 )
2652 .expect("decode child activation response"),
2653 activation_response
2654 );
2655 assert_eq!(
2656 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2657 &membership_request_bytes
2658 )
2659 .expect("decode child membership request"),
2660 membership_request
2661 );
2662 assert_eq!(
2663 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2664 &membership_response_bytes
2665 )
2666 .expect("decode child membership response"),
2667 membership_response
2668 );
2669 }
2670
2671 #[test]
2672 fn component_install_request_round_trips_through_candid() {
2673 let request = RootComponentInstallRequest {
2674 operation_id: [10; 32],
2675 };
2676 let bytes = candid::encode_one(request).expect("encode install request");
2677
2678 assert_eq!(
2679 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2680 .expect("decode install request"),
2681 request
2682 );
2683 }
2684
2685 #[test]
2686 fn component_commit_request_round_trips_through_candid() {
2687 let request = RootComponentCommitRequest {
2688 operation_id: [10; 32],
2689 };
2690 let bytes = candid::encode_one(request).expect("encode commit request");
2691
2692 assert_eq!(
2693 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2694 .expect("decode commit request"),
2695 request
2696 );
2697 }
2698
2699 #[test]
2700 fn component_runtime_activation_requests_round_trip_through_candid() {
2701 let root_request = RootComponentRuntimeActivationRequest {
2702 operation_id: [22; 32],
2703 };
2704 let target_request = ComponentRuntimeActivationRequest {
2705 operation_id: root_request.operation_id,
2706 directory_authority_hash: [23; 32],
2707 };
2708 let membership_request = RootComponentMembershipActivationRequest {
2709 operation_id: root_request.operation_id,
2710 };
2711 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2712 let target_bytes =
2713 candid::encode_one(target_request).expect("encode target activation request");
2714 let membership_bytes =
2715 candid::encode_one(membership_request).expect("encode membership activation request");
2716
2717 assert_eq!(
2718 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2719 .expect("decode root activation request"),
2720 root_request
2721 );
2722 assert_eq!(
2723 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2724 .expect("decode target activation request"),
2725 target_request
2726 );
2727 assert_eq!(
2728 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2729 .expect("decode membership activation request"),
2730 membership_request
2731 );
2732 }
2733}