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