1use crate::{
8 cdk::types::Cycles,
9 config::schema::ComponentChildKind,
10 dto::{
11 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
12 root_store::RootStoreBootstrapRequest,
13 },
14 ids::{
15 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
16 ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
17 ManagedCanisterBinding,
18 },
19};
20use candid::{CandidType, Principal};
21use serde::{Deserialize, Serialize};
22
23#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct RootComponentRegistryPreparationRequest {
31 pub store_bootstrap: RootStoreBootstrapRequest,
32 pub expected_fleet_registry: FleetRegistryVersion,
33}
34
35#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct RootComponentInitialInventoryStatus {
43 pub fleet_activation_operation_id: [u8; 32],
44 pub component_count: u32,
45 pub inventory_hash: [u8; 32],
46 pub sealed_at_ns: u64,
47 pub directories_converged: bool,
48 pub root_runtime_activated: bool,
49}
50
51#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct RootComponentRegistryStatusResponse {
59 pub fleet_subnet_root: Principal,
60 pub prepared_against_registry: FleetRegistryVersion,
61 pub release_set: FleetSubnetRootReleaseSet,
62 pub component_topology_digest: ComponentTopologyDigest,
63 pub next_allocation_sequence: u64,
64 pub reserved_component_instances: u32,
65 pub committed_component_instances: u32,
66 pub managed_descendants: u32,
67 pub known_created_component_canisters: u32,
68 pub encoded_bytes: u64,
69 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
70}
71
72#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct RootComponentAllocationRequest {
80 pub operation_id: [u8; 32],
81 pub component_spec: ComponentSpecId,
82}
83
84#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
91pub struct RootComponentAllocationStatusRequest {
92 pub operation_id: [u8; 32],
93}
94
95#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
102pub struct RootComponentChildAllocationRequest {
103 pub operation_id: [u8; 32],
104 pub component: ComponentInstanceId,
105 pub expected_registry: ComponentRegistryHead,
106 pub child_role: CanisterRole,
107}
108
109#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct RootComponentChildAllocationStatusRequest {
117 pub operation_id: [u8; 32],
118 pub component: ComponentInstanceId,
119}
120
121#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentSubtreeRemovalRequest {
129 pub operation_id: [u8; 32],
130 pub component: ComponentInstanceId,
131 pub target_canister_id: Principal,
132 pub expected_registry: ComponentRegistryHead,
133}
134
135#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct RootComponentSubtreeRemovalAdvanceRequest {
143 pub operation_id: [u8; 32],
144 pub component: ComponentInstanceId,
145 pub expected_traversal_steps: u32,
146}
147
148#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentSubtreeRemovalStopPreparationRequest {
156 pub operation_id: [u8; 32],
157 pub component: ComponentInstanceId,
158 pub expected_traversal_steps: u32,
159 pub expected_leaf_canister_id: Principal,
160 pub expected_leaf_parent_canister_id: Principal,
161}
162
163#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
170pub struct RootComponentSubtreeRemovalStopRequest {
171 pub operation_id: [u8; 32],
172 pub component: ComponentInstanceId,
173 pub expected_traversal_steps: u32,
174 pub expected_leaf_canister_id: Principal,
175 pub expected_leaf_parent_canister_id: Principal,
176}
177
178#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
185pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
186 pub operation_id: [u8; 32],
187 pub component: ComponentInstanceId,
188 pub expected_traversal_steps: u32,
189 pub expected_leaf_canister_id: Principal,
190 pub expected_leaf_parent_canister_id: Principal,
191}
192
193#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct RootComponentSubtreeRemovalDeleteRequest {
201 pub operation_id: [u8; 32],
202 pub component: ComponentInstanceId,
203 pub expected_traversal_steps: u32,
204 pub expected_leaf_canister_id: Principal,
205 pub expected_leaf_parent_canister_id: Principal,
206}
207
208#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
216 pub operation_id: [u8; 32],
217 pub component: ComponentInstanceId,
218 pub expected_traversal_steps: u32,
219 pub expected_leaf_canister_id: Principal,
220 pub expected_leaf_parent_canister_id: Principal,
221}
222
223#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
231 pub operation_id: [u8; 32],
232 pub component: ComponentInstanceId,
233 pub expected_traversal_steps: u32,
234 pub expected_leaf_canister_id: Principal,
235 pub expected_leaf_parent_canister_id: Principal,
236}
237
238#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
245pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
246 pub operation_id: [u8; 32],
247 pub component: ComponentInstanceId,
248 pub expected_traversal_steps: u32,
249 pub expected_leaf_canister_id: Principal,
250 pub expected_leaf_parent_canister_id: Principal,
251}
252
253#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
260pub struct RootComponentSubtreeRemovalStatusRequest {
261 pub operation_id: [u8; 32],
262 pub component: ComponentInstanceId,
263}
264
265#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
272pub struct RootComponentDrainingRequest {
273 pub operation_id: [u8; 32],
274 pub component: ComponentInstanceId,
275 pub expected_registry: ComponentRegistryHead,
276}
277
278#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
285pub struct RootComponentDrainingStatusRequest {
286 pub operation_id: [u8; 32],
287 pub component: ComponentInstanceId,
288}
289
290#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct RootComponentQuiescenceRequest {
298 pub operation_id: [u8; 32],
299 pub component: ComponentInstanceId,
300 pub expected_registry: ComponentRegistryHead,
301}
302
303#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
310pub struct RootComponentQuiescenceStatusRequest {
311 pub operation_id: [u8; 32],
312 pub component: ComponentInstanceId,
313}
314
315#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
322pub struct RootComponentChildCreationRequest {
323 pub operation_id: [u8; 32],
324 pub component: ComponentInstanceId,
325}
326
327#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
334pub struct RootComponentChildInstallRequest {
335 pub operation_id: [u8; 32],
336 pub component: ComponentInstanceId,
337}
338
339#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
346pub struct RootComponentChildCommitRequest {
347 pub operation_id: [u8; 32],
348 pub component: ComponentInstanceId,
349}
350
351#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
358pub struct RootComponentChildDirectoryPreparationRequest {
359 pub operation_id: [u8; 32],
360 pub component: ComponentInstanceId,
361}
362
363#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
370pub struct RootComponentChildRuntimeActivationRequest {
371 pub operation_id: [u8; 32],
372 pub component: ComponentInstanceId,
373}
374
375#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
382pub struct RootComponentChildMembershipActivationRequest {
383 pub operation_id: [u8; 32],
384 pub component: ComponentInstanceId,
385}
386
387#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
394pub struct RootComponentCreationRequest {
395 pub operation_id: [u8; 32],
396}
397
398#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
405pub struct RootComponentInstallRequest {
406 pub operation_id: [u8; 32],
407}
408
409#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
416pub struct RootComponentCommitRequest {
417 pub operation_id: [u8; 32],
418}
419
420#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
427pub struct RootComponentDirectoryPreparationRequest {
428 pub operation_id: [u8; 32],
429}
430
431#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
438pub struct RootComponentRuntimeActivationRequest {
439 pub operation_id: [u8; 32],
440}
441
442#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
449pub struct RootComponentMembershipActivationRequest {
450 pub operation_id: [u8; 32],
451}
452
453#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
460pub enum ComponentProvisioningOrigin {
461 FleetAdministrator { caller: Principal },
462}
463
464#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
471pub enum RootComponentAllocationPhase {
472 Reserved,
473 CreationIntent,
474 Created,
475 InstallIntent,
476 Installed,
477 Verified,
478 Committed,
479}
480
481#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
488#[expect(
489 clippy::large_enum_variant,
490 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
491)]
492pub enum RootComponentSubtreeRemovalPhase {
493 Fenced,
494 Traversing(RootComponentSubtreeRemovalNode),
495 LeafSelected(RootComponentSubtreeRemovalNode),
496 StopIntent(RootComponentSubtreeRemovalStopIntent),
497 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
498 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
499 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
500 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
501 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
502 Completed(RootComponentSubtreeRemovalCompletedReceipt),
503}
504
505#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
512pub struct RootComponentSubtreeRemovalNode {
513 pub canister_id: Principal,
514 pub parent_canister_id: Principal,
515 pub role: CanisterRole,
516 pub kind: ComponentChildKind,
517 pub installed_artifact_hash: [u8; 32],
518 pub status: ComponentLifecycleStatus,
519}
520
521#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
528pub struct RootComponentSubtreeRemovalStopIntent {
529 pub leaf: RootComponentSubtreeRemovalNode,
530 pub controller: Principal,
531}
532
533#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
540pub struct RootComponentSubtreeRemovalStoppedReceipt {
541 pub stop: RootComponentSubtreeRemovalStopIntent,
542 pub observed_module_hash: [u8; 32],
543}
544
545#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
552pub struct RootComponentSubtreeRemovalDeleteIntent {
553 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
554}
555
556#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
563pub struct RootComponentSubtreeRemovalDeletedReceipt {
564 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
565}
566
567#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
574pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
575 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
576 pub removed_from_registry: ComponentRegistryHead,
577 pub previous_descendant_content_hash: [u8; 32],
578 pub previous_committed_descendants: u32,
579 pub registry: ComponentRegistryHead,
580 pub descendant_content_hash: [u8; 32],
581 pub registry_encoded_bytes: u64,
582 pub reserved_descendants: u32,
583 pub committed_descendants: u32,
584 pub directory_synchronized_at_ns: u64,
585 pub directory_authority_hash: [u8; 32],
586 pub parent_role_instances: u32,
587 pub root_managed_descendants: u32,
588 pub root_known_created_component_canisters: u32,
589}
590
591#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
598pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
599 pub operation_id: [u8; 32],
600 pub canister_id: Principal,
601 pub activation: ComponentRuntimeActivationEvidence,
602}
603
604#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
613pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
614 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
615 pub covered_fleet_registry_revision: u64,
616 pub covered_fleet_registry_content_hash: [u8; 32],
617 pub covered_component_registry: ComponentRegistryHead,
618 pub covered_authority_hash: [u8; 32],
619 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
620 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
621}
622
623#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
630pub struct RootComponentSubtreeRemovalCompletedReceipt {
631 pub registry: ComponentRegistryHead,
632 pub directory_authority_hash: [u8; 32],
633}
634
635#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub enum ComponentLifecycleStatus {
643 Prepared,
644 Active,
645 Draining,
646 Removed,
647}
648
649#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
656pub struct ComponentRegistryHead {
657 pub component: ComponentInstanceId,
658 pub revision: u64,
659 pub content_hash: [u8; 32],
660}
661
662#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
669pub struct ComponentRegistryPartitionRequest {
670 pub component: ComponentInstanceId,
671}
672
673#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
680pub struct ComponentRegistryPartitionResponse {
681 pub head: ComponentRegistryHead,
682 pub binding: ComponentBinding,
683 pub provisioning_origin: ComponentProvisioningOrigin,
684 pub release_set: FleetSubnetRootReleaseSet,
685 pub status: ComponentLifecycleStatus,
686 pub reserved_descendants: u32,
687 pub committed_descendants: u32,
688 pub encoded_bytes: u64,
689}
690
691#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
698pub struct ComponentDirectoryProvenance {
699 pub component: ComponentBinding,
700 pub source_fleet_subnet_root: Principal,
701 pub component_registry_revision: u64,
702 pub component_registry_content_hash: [u8; 32],
703 pub synchronized_at_ns: u64,
704}
705
706#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
713pub struct ComponentDirectoryHead {
714 pub provenance: ComponentDirectoryProvenance,
715 pub descendant_count: u32,
716}
717
718#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
725pub struct ComponentDirectoryHeadRequest {
726 pub component: ComponentInstanceId,
727}
728
729#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
736pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
737
738#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
745pub struct ComponentDirectoryPageRequest {
746 pub directory: ComponentDirectoryHead,
747 pub parent_canister_id: Option<Principal>,
748 pub role: Option<CanisterRole>,
749 pub status: Option<ComponentLifecycleStatus>,
750 pub cursor: Option<ComponentDirectoryPageCursor>,
751 pub limit: u16,
752}
753
754#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
761pub struct ComponentDirectoryChildEntry {
762 pub binding: ComponentChildBinding,
763 pub kind: ComponentChildKind,
764 pub installed_artifact_hash: [u8; 32],
765 pub status: ComponentLifecycleStatus,
766}
767
768#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
775pub struct ComponentDirectoryPageResponse {
776 pub directory: ComponentDirectoryHead,
777 pub entries: Vec<ComponentDirectoryChildEntry>,
778 pub next_cursor: Option<ComponentDirectoryPageCursor>,
779}
780
781#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
788pub struct ComponentRuntimeDirectoryAuthority {
789 pub fleet: FleetDirectorySnapshot,
790 pub component: ComponentDirectoryHead,
791}
792
793#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
800pub struct ComponentRuntimeDirectoryPreparationRequest {
801 pub operation_id: [u8; 32],
802 pub authority: ComponentRuntimeDirectoryAuthority,
803}
804
805#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
812pub struct ComponentRuntimeDirectorySynchronizationRequest {
813 pub operation_id: [u8; 32],
814 pub authority: ComponentRuntimeDirectoryAuthority,
815}
816
817#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
824pub enum ComponentRuntimePhase {
825 AwaitingDirectory,
826 DirectoryPrepared,
827 Active,
828}
829
830#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
837pub struct ComponentRuntimeActivationEvidence {
838 pub directory_authority_hash: [u8; 32],
839 pub activated_at_ns: u64,
840}
841
842#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
849pub struct ComponentRuntimeActivationRequest {
850 pub operation_id: [u8; 32],
851 pub directory_authority_hash: [u8; 32],
852}
853
854#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
861pub struct ComponentRuntimeStatusResponse {
862 pub operation_id: [u8; 32],
863 pub binding: ManagedCanisterBinding,
864 pub phase: ComponentRuntimePhase,
865 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
866 pub authority_hash: Option<[u8; 32]>,
867 pub activation: Option<ComponentRuntimeActivationEvidence>,
868}
869
870#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
877pub struct ComponentRuntimeDirectoryConvergenceEvidence {
878 pub operation_id: [u8; 32],
879 pub binding: ManagedCanisterBinding,
880 pub covered_authority: ComponentRuntimeDirectoryAuthority,
881 pub covered_authority_hash: [u8; 32],
882 pub activation: ComponentRuntimeActivationEvidence,
883}
884
885#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
892pub struct RootComponentCreationEvidence {
893 pub wasm_store: Principal,
894 pub payload_hash: [u8; 32],
895 pub payload_size_bytes: u64,
896 pub initial_cycles: Cycles,
897 pub controller: Principal,
898 pub canister: Option<Principal>,
899}
900
901#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
908pub struct RootComponentInstallEvidence {
909 pub raw_module_hash: [u8; 32],
910 pub chunk_hashes: Vec<Vec<u8>>,
911 pub binding: ComponentBinding,
912}
913
914#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
921pub struct RootComponentChildInstallEvidence {
922 pub raw_module_hash: [u8; 32],
923 pub chunk_hashes: Vec<Vec<u8>>,
924 pub binding: ComponentChildBinding,
925}
926
927#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
934pub struct RootComponentAllocationResponse {
935 pub operation_id: [u8; 32],
936 pub allocation_sequence: u64,
937 pub component: ComponentInstanceId,
938 pub component_spec: ComponentSpecId,
939 pub spec_hash: [u8; 32],
940 pub role: CanisterRole,
941 pub provisioning_origin: ComponentProvisioningOrigin,
942 pub release_set: FleetSubnetRootReleaseSet,
943 pub phase: RootComponentAllocationPhase,
944 pub creation: Option<RootComponentCreationEvidence>,
945 pub installation: Option<RootComponentInstallEvidence>,
946}
947
948#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
955pub struct RootComponentChildAllocationResponse {
956 pub operation_id: [u8; 32],
957 pub component: ComponentInstanceId,
958 pub parent_canister_id: Principal,
959 pub parent_role: CanisterRole,
960 pub child_role: CanisterRole,
961 pub child_kind: ComponentChildKind,
962 pub maximum_instances_per_parent: u32,
963 pub maximum_descendants: u32,
964 pub maximum_registry_bytes: u64,
965 pub reserved_against_registry: ComponentRegistryHead,
966 pub release_set: FleetSubnetRootReleaseSet,
967 pub phase: RootComponentAllocationPhase,
968 pub creation: Option<RootComponentCreationEvidence>,
969 pub installation: Option<RootComponentChildInstallEvidence>,
970}
971
972#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
979pub struct RootComponentSubtreeRemovalResponse {
980 pub operation_id: [u8; 32],
981 pub component: ComponentInstanceId,
982 pub target_canister_id: Principal,
983 pub target_parent_canister_id: Principal,
984 pub target_role: CanisterRole,
985 pub target_status: ComponentLifecycleStatus,
986 pub reserved_against_registry: ComponentRegistryHead,
987 pub maximum_completed_leaves: u32,
988 pub completed_leaves: u32,
989 pub traversal_steps: u32,
990 pub phase: RootComponentSubtreeRemovalPhase,
991}
992
993#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1000pub struct RootComponentDrainingResponse {
1001 pub operation_id: [u8; 32],
1002 pub component: ComponentInstanceId,
1003 pub previous_registry: ComponentRegistryHead,
1004 pub registry: ComponentRegistryHead,
1005 pub descendant_count: u32,
1006 pub descendant_content_hash: [u8; 32],
1007 pub directory_authority_hash: [u8; 32],
1008 pub started_at_ns: u64,
1009}
1010
1011#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1018pub struct RootComponentQuiescenceStopIntent {
1019 pub registry: ComponentRegistryHead,
1020 pub descendant_count: u32,
1021 pub descendant_content_hash: [u8; 32],
1022 pub canister_id: Principal,
1023 pub controller: Principal,
1024 pub expected_module_hash: [u8; 32],
1025 pub covered_fleet_registry_revision: u64,
1026 pub covered_fleet_registry_content_hash: [u8; 32],
1027 pub covered_authority_hash: [u8; 32],
1028 pub runtime_operation_id: [u8; 32],
1029 pub activation: ComponentRuntimeActivationEvidence,
1030 pub prepared_at_ns: u64,
1031}
1032
1033#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1040pub struct RootComponentQuiescentReceipt {
1041 pub stop: RootComponentQuiescenceStopIntent,
1042 pub observed_module_hash: [u8; 32],
1043 pub quiesced_at_ns: u64,
1044}
1045
1046#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1053pub enum RootComponentQuiescencePhase {
1054 StopIntent(RootComponentQuiescenceStopIntent),
1055 Quiescent(RootComponentQuiescentReceipt),
1056}
1057
1058#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1065pub struct RootComponentQuiescenceResponse {
1066 pub operation_id: [u8; 32],
1067 pub component: ComponentInstanceId,
1068 pub phase: RootComponentQuiescencePhase,
1069}
1070
1071#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1078pub struct RootComponentChildCommitResponse {
1079 pub allocation: RootComponentChildAllocationResponse,
1080 pub registry: ComponentRegistryPartitionResponse,
1081 pub directory: ComponentDirectoryHead,
1082}
1083
1084#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1091pub struct RootComponentChildDirectoryPreparationResponse {
1092 pub committed: RootComponentChildCommitResponse,
1093 pub child: ComponentRuntimeStatusResponse,
1094 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1095 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1096}
1097
1098#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1105pub struct RootComponentChildRuntimeActivationResponse {
1106 pub committed: RootComponentChildCommitResponse,
1107 pub child: ComponentRuntimeStatusResponse,
1108}
1109
1110#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1117pub struct RootComponentChildMembershipActivationResponse {
1118 pub committed: RootComponentChildCommitResponse,
1119 pub registry: ComponentRegistryPartitionResponse,
1120 pub directory: ComponentDirectoryHead,
1121 pub child: ComponentRuntimeStatusResponse,
1122}
1123
1124#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1131pub struct RootComponentCommitResponse {
1132 pub allocation: RootComponentAllocationResponse,
1133 pub registry: ComponentRegistryPartitionResponse,
1134 pub directory: ComponentDirectoryHead,
1135}
1136
1137#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1144pub struct RootComponentDirectoryPreparationResponse {
1145 pub committed: RootComponentCommitResponse,
1146 pub target: ComponentRuntimeStatusResponse,
1147}
1148
1149#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1156pub struct RootComponentRuntimeActivationResponse {
1157 pub committed: RootComponentCommitResponse,
1158 pub target: ComponentRuntimeStatusResponse,
1159}
1160
1161#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1168pub struct RootComponentMembershipActivationResponse {
1169 pub allocation: RootComponentAllocationResponse,
1170 pub registry: ComponentRegistryPartitionResponse,
1171 pub directory: ComponentDirectoryHead,
1172 pub target: ComponentRuntimeStatusResponse,
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178 use crate::{
1179 dto::root_store::RootStoreBootstrapRequest,
1180 ids::{
1181 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1182 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1183 },
1184 };
1185
1186 #[test]
1187 fn component_registry_contracts_round_trip_through_candid() {
1188 let request = RootComponentRegistryPreparationRequest {
1189 store_bootstrap: RootStoreBootstrapRequest {
1190 manifest_payload_size_bytes: 128,
1191 },
1192 expected_fleet_registry: FleetRegistryVersion {
1193 authority: fleet_registry_authority(),
1194 revision: 4,
1195 content_hash: [5; 32],
1196 },
1197 };
1198 let response = RootComponentRegistryStatusResponse {
1199 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1200 prepared_against_registry: request.expected_fleet_registry.clone(),
1201 release_set: FleetSubnetRootReleaseSet {
1202 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1203 [7; 32],
1204 )),
1205 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1206 },
1207 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1208 next_allocation_sequence: 1,
1209 reserved_component_instances: 0,
1210 committed_component_instances: 0,
1211 managed_descendants: 0,
1212 known_created_component_canisters: 0,
1213 encoded_bytes: 0,
1214 initial_inventory: Some(RootComponentInitialInventoryStatus {
1215 fleet_activation_operation_id: [10; 32],
1216 component_count: 0,
1217 inventory_hash: [11; 32],
1218 sealed_at_ns: 12,
1219 directories_converged: true,
1220 root_runtime_activated: true,
1221 }),
1222 };
1223 let allocation = RootComponentAllocationResponse {
1224 operation_id: [10; 32],
1225 allocation_sequence: 1,
1226 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1227 component_spec: "projects".parse().expect("Component Spec ID"),
1228 spec_hash: [12; 32],
1229 role: CanisterRole::new("project_hub"),
1230 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1231 caller: Principal::from_slice(&[13; 29]),
1232 },
1233 release_set: response.release_set,
1234 phase: RootComponentAllocationPhase::Reserved,
1235 creation: None,
1236 installation: None,
1237 };
1238 let created = RootComponentAllocationResponse {
1239 phase: RootComponentAllocationPhase::Created,
1240 creation: Some(RootComponentCreationEvidence {
1241 wasm_store: Principal::from_slice(&[14; 29]),
1242 payload_hash: [15; 32],
1243 payload_size_bytes: 4_096,
1244 initial_cycles: Cycles::new(5_000_000_000_000),
1245 controller: Principal::from_slice(&[6; 29]),
1246 canister: Some(Principal::from_slice(&[16; 29])),
1247 }),
1248 installation: None,
1249 ..allocation.clone()
1250 };
1251 let request_bytes = candid::encode_one(&request).expect("encode request");
1252 let response_bytes = candid::encode_one(&response).expect("encode response");
1253 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1254 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1255
1256 assert_eq!(
1257 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1258 .expect("decode request"),
1259 request
1260 );
1261 assert_eq!(
1262 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1263 .expect("decode response"),
1264 response
1265 );
1266 assert_eq!(
1267 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1268 .expect("decode allocation"),
1269 allocation
1270 );
1271 assert_eq!(
1272 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1273 .expect("decode created allocation"),
1274 created
1275 );
1276 }
1277
1278 #[test]
1279 fn component_commit_response_round_trips_through_candid() {
1280 let root = Principal::from_slice(&[6; 29]);
1281 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1282 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1283 let release_set = FleetSubnetRootReleaseSet {
1284 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1285 [7; 32],
1286 )),
1287 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1288 };
1289 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1290 caller: Principal::from_slice(&[13; 29]),
1291 };
1292 let binding = ComponentBinding {
1293 authority: fleet_registry_authority(),
1294 component,
1295 component_spec: component_spec.clone(),
1296 spec_hash: [12; 32],
1297 role: CanisterRole::new("project_hub"),
1298 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1299 fleet_subnet_root: root,
1300 canister_id: Principal::from_slice(&[16; 29]),
1301 };
1302 let head = ComponentRegistryHead {
1303 component,
1304 revision: 1,
1305 content_hash: [18; 32],
1306 };
1307 let committed = RootComponentCommitResponse {
1308 allocation: RootComponentAllocationResponse {
1309 operation_id: [10; 32],
1310 allocation_sequence: 1,
1311 component,
1312 component_spec,
1313 spec_hash: binding.spec_hash,
1314 role: binding.role.clone(),
1315 provisioning_origin: provisioning_origin.clone(),
1316 release_set,
1317 phase: RootComponentAllocationPhase::Committed,
1318 creation: Some(RootComponentCreationEvidence {
1319 wasm_store: Principal::from_slice(&[14; 29]),
1320 payload_hash: [15; 32],
1321 payload_size_bytes: 4_096,
1322 initial_cycles: Cycles::new(5_000_000_000_000),
1323 controller: root,
1324 canister: Some(binding.canister_id),
1325 }),
1326 installation: Some(RootComponentInstallEvidence {
1327 raw_module_hash: [20; 32],
1328 chunk_hashes: vec![vec![21; 32]],
1329 binding: binding.clone(),
1330 }),
1331 },
1332 registry: ComponentRegistryPartitionResponse {
1333 head: head.clone(),
1334 binding: binding.clone(),
1335 provisioning_origin,
1336 release_set,
1337 status: ComponentLifecycleStatus::Prepared,
1338 reserved_descendants: 0,
1339 committed_descendants: 0,
1340 encoded_bytes: 2_048,
1341 },
1342 directory: ComponentDirectoryHead {
1343 provenance: ComponentDirectoryProvenance {
1344 component: binding,
1345 source_fleet_subnet_root: root,
1346 component_registry_revision: head.revision,
1347 component_registry_content_hash: head.content_hash,
1348 synchronized_at_ns: 19,
1349 },
1350 descendant_count: 0,
1351 },
1352 };
1353 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1354
1355 assert_eq!(
1356 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1357 .expect("decode committed allocation"),
1358 committed
1359 );
1360 }
1361
1362 #[test]
1363 fn component_directory_page_contracts_round_trip_through_candid() {
1364 let root = Principal::from_slice(&[6; 29]);
1365 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1366 let binding = ComponentBinding {
1367 authority: fleet_registry_authority(),
1368 component,
1369 component_spec: "projects".parse().expect("Component Spec ID"),
1370 spec_hash: [12; 32],
1371 role: CanisterRole::new("project_hub"),
1372 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1373 fleet_subnet_root: root,
1374 canister_id: Principal::from_slice(&[16; 29]),
1375 };
1376 let directory = ComponentDirectoryHead {
1377 provenance: ComponentDirectoryProvenance {
1378 component: binding.clone(),
1379 source_fleet_subnet_root: root,
1380 component_registry_revision: 3,
1381 component_registry_content_hash: [18; 32],
1382 synchronized_at_ns: 19,
1383 },
1384 descendant_count: 1,
1385 };
1386 let request = ComponentDirectoryPageRequest {
1387 directory: directory.clone(),
1388 parent_canister_id: Some(binding.canister_id),
1389 role: Some(CanisterRole::new("project_instance")),
1390 status: Some(ComponentLifecycleStatus::Active),
1391 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1392 limit: 50,
1393 };
1394 let response = ComponentDirectoryPageResponse {
1395 directory,
1396 entries: vec![ComponentDirectoryChildEntry {
1397 binding: ComponentChildBinding {
1398 component: binding.clone(),
1399 parent_canister_id: binding.canister_id,
1400 role: CanisterRole::new("project_instance"),
1401 canister_id: Principal::from_slice(&[21; 29]),
1402 },
1403 kind: ComponentChildKind::Instance,
1404 installed_artifact_hash: [22; 32],
1405 status: ComponentLifecycleStatus::Active,
1406 }],
1407 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1408 };
1409 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1410 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1411
1412 assert_eq!(
1413 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1414 .expect("decode Directory page request"),
1415 request
1416 );
1417 assert_eq!(
1418 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1419 .expect("decode Directory page response"),
1420 response
1421 );
1422 }
1423
1424 fn fleet_registry_authority() -> FleetRegistryAuthority {
1425 FleetRegistryAuthority {
1426 binding: FleetCoordinatorBinding {
1427 fleet: crate::ids::FleetBinding {
1428 fleet: FleetKey {
1429 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1430 fleet_id: FleetId::from_generated_bytes([1; 32]),
1431 },
1432 app: AppId::from("toko"),
1433 },
1434 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1435 coordinator: Principal::from_slice(&[3; 29]),
1436 },
1437 epoch: 1,
1438 }
1439 }
1440
1441 #[test]
1442 fn component_creation_request_round_trips_through_candid() {
1443 let request = RootComponentCreationRequest {
1444 operation_id: [10; 32],
1445 };
1446 let bytes = candid::encode_one(request).expect("encode creation request");
1447
1448 assert_eq!(
1449 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1450 .expect("decode creation request"),
1451 request
1452 );
1453 }
1454
1455 #[test]
1456 #[expect(
1457 clippy::too_many_lines,
1458 reason = "one Candid contract test covers every subtree-removal phase receipt"
1459 )]
1460 fn component_subtree_removal_contracts_round_trip_through_candid() {
1461 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1462 let registry = ComponentRegistryHead {
1463 component,
1464 revision: 7,
1465 content_hash: [42; 32],
1466 };
1467 let request = RootComponentSubtreeRemovalRequest {
1468 operation_id: [43; 32],
1469 component,
1470 target_canister_id: Principal::from_slice(&[44; 29]),
1471 expected_registry: registry.clone(),
1472 };
1473 let status_request = RootComponentSubtreeRemovalStatusRequest {
1474 operation_id: request.operation_id,
1475 component,
1476 };
1477 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1478 operation_id: request.operation_id,
1479 component,
1480 expected_traversal_steps: 1,
1481 };
1482 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1483 operation_id: request.operation_id,
1484 component,
1485 expected_traversal_steps: 2,
1486 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1487 expected_leaf_parent_canister_id: request.target_canister_id,
1488 };
1489 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1490 observed_module_hash: [49; 32],
1491 stop: RootComponentSubtreeRemovalStopIntent {
1492 controller: Principal::from_slice(&[48; 29]),
1493 leaf: RootComponentSubtreeRemovalNode {
1494 canister_id: Principal::from_slice(&[46; 29]),
1495 parent_canister_id: request.target_canister_id,
1496 role: CanisterRole::new("project_ledger"),
1497 kind: ComponentChildKind::Singleton,
1498 installed_artifact_hash: [47; 32],
1499 status: ComponentLifecycleStatus::Active,
1500 },
1501 },
1502 };
1503 let response = RootComponentSubtreeRemovalResponse {
1504 operation_id: request.operation_id,
1505 component,
1506 target_canister_id: request.target_canister_id,
1507 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1508 target_role: CanisterRole::new("project_instance"),
1509 target_status: ComponentLifecycleStatus::Active,
1510 reserved_against_registry: registry,
1511 maximum_completed_leaves: 4,
1512 completed_leaves: 1,
1513 traversal_steps: 2,
1514 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1515 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1516 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1517 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1518 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1519 },
1520 removed_from_registry: ComponentRegistryHead {
1521 component,
1522 revision: 8,
1523 content_hash: [50; 32],
1524 },
1525 previous_descendant_content_hash: [51; 32],
1526 previous_committed_descendants: 4,
1527 registry: ComponentRegistryHead {
1528 component,
1529 revision: 9,
1530 content_hash: [52; 32],
1531 },
1532 descendant_content_hash: [53; 32],
1533 registry_encoded_bytes: 4_096,
1534 reserved_descendants: 1,
1535 committed_descendants: 3,
1536 directory_synchronized_at_ns: 54,
1537 directory_authority_hash: [55; 32],
1538 parent_role_instances: 0,
1539 root_managed_descendants: 4,
1540 root_known_created_component_canisters: 4,
1541 },
1542 covered_fleet_registry_revision: 6,
1543 covered_fleet_registry_content_hash: [56; 32],
1544 covered_component_registry: ComponentRegistryHead {
1545 component,
1546 revision: 9,
1547 content_hash: [52; 32],
1548 },
1549 covered_authority_hash: [55; 32],
1550 owning_component: Some(
1551 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1552 operation_id: [57; 32],
1553 canister_id: Principal::from_slice(&[58; 29]),
1554 activation: ComponentRuntimeActivationEvidence {
1555 directory_authority_hash: [59; 32],
1556 activated_at_ns: 60,
1557 },
1558 },
1559 ),
1560 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1561 operation_id: [61; 32],
1562 canister_id: request.target_canister_id,
1563 activation: ComponentRuntimeActivationEvidence {
1564 directory_authority_hash: [62; 32],
1565 activated_at_ns: 63,
1566 },
1567 }),
1568 },
1569 ),
1570 };
1571
1572 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1573 let advance_bytes =
1574 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1575 let stop_bytes =
1576 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1577 let status_bytes =
1578 candid::encode_one(status_request).expect("encode subtree removal status request");
1579 let response_bytes =
1580 candid::encode_one(&response).expect("encode subtree removal response");
1581
1582 assert_eq!(
1583 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1584 .expect("decode subtree removal request"),
1585 request
1586 );
1587 assert_eq!(
1588 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1589 .expect("decode subtree removal advance request"),
1590 advance_request
1591 );
1592 assert_eq!(
1593 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1594 .expect("decode subtree removal stop request"),
1595 stop_request
1596 );
1597 assert_eq!(
1598 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1599 .expect("decode subtree removal status request"),
1600 status_request
1601 );
1602 assert_eq!(
1603 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1604 .expect("decode subtree removal response"),
1605 response
1606 );
1607
1608 let mut quiescent_owner_response = response;
1609 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
1610 &mut quiescent_owner_response.phase
1611 else {
1612 panic!("Directory-synchronized response");
1613 };
1614 receipt.owning_component = None;
1615 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
1616 .expect("encode quiescent-owner subtree response");
1617 assert_eq!(
1618 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
1619 .expect("decode quiescent-owner subtree response"),
1620 quiescent_owner_response
1621 );
1622 }
1623
1624 #[test]
1625 fn component_draining_contracts_round_trip_through_candid() {
1626 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1627 let previous_registry = ComponentRegistryHead {
1628 component,
1629 revision: 7,
1630 content_hash: [61; 32],
1631 };
1632 let request = RootComponentDrainingRequest {
1633 operation_id: [62; 32],
1634 component,
1635 expected_registry: previous_registry.clone(),
1636 };
1637 let status_request = RootComponentDrainingStatusRequest {
1638 operation_id: request.operation_id,
1639 component,
1640 };
1641 let response = RootComponentDrainingResponse {
1642 operation_id: request.operation_id,
1643 component,
1644 previous_registry,
1645 registry: ComponentRegistryHead {
1646 component,
1647 revision: 8,
1648 content_hash: [63; 32],
1649 },
1650 descendant_count: 20_000,
1651 descendant_content_hash: [64; 32],
1652 directory_authority_hash: [65; 32],
1653 started_at_ns: 66,
1654 };
1655
1656 let request_bytes =
1657 candid::encode_one(&request).expect("encode Component draining request");
1658 let status_bytes =
1659 candid::encode_one(status_request).expect("encode Component draining status request");
1660 let response_bytes =
1661 candid::encode_one(&response).expect("encode Component draining response");
1662
1663 assert_eq!(
1664 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1665 .expect("decode Component draining request"),
1666 request
1667 );
1668 assert_eq!(
1669 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1670 .expect("decode Component draining status request"),
1671 status_request
1672 );
1673 assert_eq!(
1674 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1675 .expect("decode Component draining response"),
1676 response
1677 );
1678 }
1679
1680 #[test]
1681 fn component_quiescence_contracts_round_trip_through_candid() {
1682 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
1683 let registry = ComponentRegistryHead {
1684 component,
1685 revision: 9,
1686 content_hash: [68; 32],
1687 };
1688 let request = RootComponentQuiescenceRequest {
1689 operation_id: [69; 32],
1690 component,
1691 expected_registry: registry.clone(),
1692 };
1693 let status_request = RootComponentQuiescenceStatusRequest {
1694 operation_id: request.operation_id,
1695 component,
1696 };
1697 let stop = RootComponentQuiescenceStopIntent {
1698 registry,
1699 descendant_count: 20_000,
1700 descendant_content_hash: [70; 32],
1701 canister_id: Principal::from_slice(&[71; 29]),
1702 controller: Principal::from_slice(&[72; 29]),
1703 expected_module_hash: [73; 32],
1704 covered_fleet_registry_revision: 10,
1705 covered_fleet_registry_content_hash: [74; 32],
1706 covered_authority_hash: [75; 32],
1707 runtime_operation_id: [76; 32],
1708 activation: ComponentRuntimeActivationEvidence {
1709 directory_authority_hash: [77; 32],
1710 activated_at_ns: 78,
1711 },
1712 prepared_at_ns: 79,
1713 };
1714 let response = RootComponentQuiescenceResponse {
1715 operation_id: request.operation_id,
1716 component,
1717 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
1718 stop,
1719 observed_module_hash: [73; 32],
1720 quiesced_at_ns: 80,
1721 }),
1722 };
1723
1724 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
1725 let status_bytes =
1726 candid::encode_one(status_request).expect("encode quiescence status request");
1727 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
1728 assert_eq!(
1729 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
1730 .expect("decode quiescence request"),
1731 request
1732 );
1733 assert_eq!(
1734 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
1735 .expect("decode quiescence status request"),
1736 status_request
1737 );
1738 assert_eq!(
1739 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
1740 .expect("decode quiescence response"),
1741 response
1742 );
1743 }
1744
1745 #[test]
1746 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
1747 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
1748 operation_id: [50; 32],
1749 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1750 expected_traversal_steps: 3,
1751 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1752 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1753 };
1754 let request = RootComponentSubtreeRemovalDeleteRequest {
1755 operation_id: prepare.operation_id,
1756 component: prepare.component,
1757 expected_traversal_steps: prepare.expected_traversal_steps,
1758 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1759 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1760 };
1761 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
1762 operation_id: prepare.operation_id,
1763 component: prepare.component,
1764 expected_traversal_steps: prepare.expected_traversal_steps,
1765 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1766 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1767 };
1768 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
1769 operation_id: prepare.operation_id,
1770 component: prepare.component,
1771 expected_traversal_steps: prepare.expected_traversal_steps,
1772 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1773 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1774 };
1775 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
1776 operation_id: prepare.operation_id,
1777 component: prepare.component,
1778 expected_traversal_steps: prepare.expected_traversal_steps,
1779 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1780 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1781 };
1782 let prepare_bytes = candid::encode_one(prepare)
1783 .expect("encode subtree removal deletion preparation request");
1784 let request_bytes =
1785 candid::encode_one(request).expect("encode subtree removal deletion request");
1786 let membership_request_bytes = candid::encode_one(membership_request)
1787 .expect("encode subtree removal membership-removal request");
1788 let directory_request_bytes = candid::encode_one(directory_request)
1789 .expect("encode subtree removal Directory synchronization request");
1790 let finalization_request_bytes = candid::encode_one(finalization_request)
1791 .expect("encode subtree removal leaf-finalization request");
1792
1793 assert_eq!(
1794 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
1795 &prepare_bytes
1796 )
1797 .expect("decode subtree removal deletion preparation request"),
1798 prepare
1799 );
1800 assert_eq!(
1801 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
1802 .expect("decode subtree removal deletion request"),
1803 request
1804 );
1805 assert_eq!(
1806 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
1807 &membership_request_bytes
1808 )
1809 .expect("decode subtree removal membership-removal request"),
1810 membership_request
1811 );
1812 assert_eq!(
1813 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
1814 &directory_request_bytes
1815 )
1816 .expect("decode subtree removal Directory synchronization request"),
1817 directory_request
1818 );
1819 assert_eq!(
1820 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
1821 &finalization_request_bytes
1822 )
1823 .expect("decode subtree removal leaf-finalization request"),
1824 finalization_request
1825 );
1826 }
1827
1828 #[test]
1829 fn component_subtree_removal_stop_request_round_trips_through_candid() {
1830 let request = RootComponentSubtreeRemovalStopRequest {
1831 operation_id: [50; 32],
1832 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1833 expected_traversal_steps: 3,
1834 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1835 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1836 };
1837 let bytes =
1838 candid::encode_one(request).expect("encode subtree removal stop execution request");
1839
1840 assert_eq!(
1841 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
1842 .expect("decode subtree removal stop execution request"),
1843 request
1844 );
1845 }
1846
1847 #[test]
1848 #[expect(
1849 clippy::too_many_lines,
1850 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1851 )]
1852 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1853 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1854 let registry = ComponentRegistryHead {
1855 component,
1856 revision: 2,
1857 content_hash: [12; 32],
1858 };
1859 let request = RootComponentChildAllocationRequest {
1860 operation_id: [13; 32],
1861 component,
1862 expected_registry: registry.clone(),
1863 child_role: CanisterRole::new("project_instance"),
1864 };
1865 let status_request = RootComponentChildAllocationStatusRequest {
1866 operation_id: request.operation_id,
1867 component,
1868 };
1869 let creation_request = RootComponentChildCreationRequest {
1870 operation_id: request.operation_id,
1871 component,
1872 };
1873 let install_request = RootComponentChildInstallRequest {
1874 operation_id: request.operation_id,
1875 component,
1876 };
1877 let commit_request = RootComponentChildCommitRequest {
1878 operation_id: request.operation_id,
1879 component,
1880 };
1881 let directory_request = RootComponentChildDirectoryPreparationRequest {
1882 operation_id: request.operation_id,
1883 component,
1884 };
1885 let activation_request = RootComponentChildRuntimeActivationRequest {
1886 operation_id: request.operation_id,
1887 component,
1888 };
1889 let membership_request = RootComponentChildMembershipActivationRequest {
1890 operation_id: request.operation_id,
1891 component,
1892 };
1893 let root = Principal::from_slice(&[17; 29]);
1894 let parent = Principal::from_slice(&[14; 29]);
1895 let child = Principal::from_slice(&[18; 29]);
1896 let child_binding = ComponentChildBinding {
1897 component: ComponentBinding {
1898 authority: fleet_registry_authority(),
1899 component,
1900 component_spec: "projects".parse().expect("Component Spec"),
1901 spec_hash: [19; 32],
1902 role: CanisterRole::new("project_hub"),
1903 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1904 fleet_subnet_root: root,
1905 canister_id: parent,
1906 },
1907 parent_canister_id: parent,
1908 role: request.child_role.clone(),
1909 canister_id: child,
1910 };
1911 let response = RootComponentChildAllocationResponse {
1912 operation_id: request.operation_id,
1913 component,
1914 parent_canister_id: parent,
1915 parent_role: CanisterRole::new("project_hub"),
1916 child_role: request.child_role.clone(),
1917 child_kind: ComponentChildKind::Instance,
1918 maximum_instances_per_parent: 10_000,
1919 maximum_descendants: 20_000,
1920 maximum_registry_bytes: 16_777_216,
1921 reserved_against_registry: registry,
1922 release_set: FleetSubnetRootReleaseSet {
1923 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1924 [15; 32],
1925 )),
1926 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1927 },
1928 phase: RootComponentAllocationPhase::Verified,
1929 creation: Some(RootComponentCreationEvidence {
1930 wasm_store: Principal::from_slice(&[21; 29]),
1931 payload_hash: [22; 32],
1932 payload_size_bytes: 4_096,
1933 initial_cycles: Cycles::new(5_000_000_000_000),
1934 controller: root,
1935 canister: Some(child),
1936 }),
1937 installation: Some(RootComponentChildInstallEvidence {
1938 raw_module_hash: [23; 32],
1939 chunk_hashes: vec![vec![24; 32]],
1940 binding: child_binding.clone(),
1941 }),
1942 };
1943 let commit_response = RootComponentChildCommitResponse {
1944 allocation: response.clone(),
1945 registry: ComponentRegistryPartitionResponse {
1946 head: ComponentRegistryHead {
1947 component,
1948 revision: 3,
1949 content_hash: [25; 32],
1950 },
1951 binding: child_binding.component.clone(),
1952 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1953 caller: Principal::from_slice(&[26; 29]),
1954 },
1955 release_set: response.release_set,
1956 status: ComponentLifecycleStatus::Active,
1957 reserved_descendants: 0,
1958 committed_descendants: 1,
1959 encoded_bytes: 8_192,
1960 },
1961 directory: ComponentDirectoryHead {
1962 provenance: ComponentDirectoryProvenance {
1963 component: child_binding.component.clone(),
1964 source_fleet_subnet_root: root,
1965 component_registry_revision: 3,
1966 component_registry_content_hash: [25; 32],
1967 synchronized_at_ns: 27,
1968 },
1969 descendant_count: 1,
1970 },
1971 };
1972 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1973 fleet: FleetDirectorySnapshot {
1974 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1975 registry: FleetRegistryVersion {
1976 authority: fleet_registry_authority(),
1977 revision: 4,
1978 content_hash: [28; 32],
1979 },
1980 source_fleet_subnet_root: root,
1981 },
1982 fleet_subnet_roots: vec![
1983 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1984 placement_subnet: commit_response.registry.binding.placement_subnet,
1985 fleet_subnet_root: root,
1986 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1987 },
1988 ],
1989 },
1990 component: commit_response.directory.clone(),
1991 };
1992 let activation = ComponentRuntimeActivationEvidence {
1993 directory_authority_hash: [29; 32],
1994 activated_at_ns: 30,
1995 };
1996 let directory_response = RootComponentChildDirectoryPreparationResponse {
1997 committed: commit_response.clone(),
1998 child: ComponentRuntimeStatusResponse {
1999 operation_id: request.operation_id,
2000 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2001 phase: ComponentRuntimePhase::DirectoryPrepared,
2002 authority: Some(runtime_authority.clone()),
2003 authority_hash: Some([31; 32]),
2004 activation: None,
2005 },
2006 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2007 operation_id: [32; 32],
2008 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2009 covered_authority: runtime_authority.clone(),
2010 covered_authority_hash: [31; 32],
2011 activation,
2012 },
2013 parent: None,
2014 };
2015 let activation_response = RootComponentChildRuntimeActivationResponse {
2016 committed: commit_response.clone(),
2017 child: ComponentRuntimeStatusResponse {
2018 operation_id: request.operation_id,
2019 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2020 phase: ComponentRuntimePhase::Active,
2021 authority: Some(runtime_authority.clone()),
2022 authority_hash: Some([31; 32]),
2023 activation: Some(ComponentRuntimeActivationEvidence {
2024 directory_authority_hash: [31; 32],
2025 activated_at_ns: 33,
2026 }),
2027 },
2028 };
2029 let active_directory = ComponentDirectoryHead {
2030 provenance: ComponentDirectoryProvenance {
2031 component: child_binding.component.clone(),
2032 source_fleet_subnet_root: root,
2033 component_registry_revision: 4,
2034 component_registry_content_hash: [34; 32],
2035 synchronized_at_ns: 35,
2036 },
2037 descendant_count: 1,
2038 };
2039 let active_authority = ComponentRuntimeDirectoryAuthority {
2040 fleet: runtime_authority.fleet,
2041 component: active_directory.clone(),
2042 };
2043 let membership_response = RootComponentChildMembershipActivationResponse {
2044 committed: commit_response.clone(),
2045 registry: ComponentRegistryPartitionResponse {
2046 head: ComponentRegistryHead {
2047 component,
2048 revision: 4,
2049 content_hash: [34; 32],
2050 },
2051 binding: child_binding.component.clone(),
2052 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2053 release_set: commit_response.registry.release_set,
2054 status: ComponentLifecycleStatus::Active,
2055 reserved_descendants: 0,
2056 committed_descendants: 1,
2057 encoded_bytes: 8_256,
2058 },
2059 directory: active_directory,
2060 child: ComponentRuntimeStatusResponse {
2061 operation_id: request.operation_id,
2062 binding: ManagedCanisterBinding::ComponentChild(child_binding),
2063 phase: ComponentRuntimePhase::Active,
2064 authority: Some(active_authority),
2065 authority_hash: Some([36; 32]),
2066 activation: Some(ComponentRuntimeActivationEvidence {
2067 directory_authority_hash: [31; 32],
2068 activated_at_ns: 33,
2069 }),
2070 },
2071 };
2072
2073 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2074 let status_bytes =
2075 candid::encode_one(status_request).expect("encode child reservation status");
2076 let creation_bytes =
2077 candid::encode_one(creation_request).expect("encode child creation request");
2078 let install_bytes =
2079 candid::encode_one(install_request).expect("encode child install request");
2080 let response_bytes = candid::encode_one(&response).expect("encode child response");
2081 let commit_request_bytes =
2082 candid::encode_one(commit_request).expect("encode child commit request");
2083 let directory_request_bytes =
2084 candid::encode_one(directory_request).expect("encode child Directory request");
2085 let activation_request_bytes =
2086 candid::encode_one(activation_request).expect("encode child activation request");
2087 let membership_request_bytes =
2088 candid::encode_one(membership_request).expect("encode child membership request");
2089 let commit_response_bytes =
2090 candid::encode_one(&commit_response).expect("encode child commit response");
2091 let directory_response_bytes =
2092 candid::encode_one(&directory_response).expect("encode child Directory response");
2093 let activation_response_bytes =
2094 candid::encode_one(&activation_response).expect("encode child activation response");
2095 let membership_response_bytes =
2096 candid::encode_one(&membership_response).expect("encode child membership response");
2097
2098 assert_eq!(
2099 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2100 .expect("decode child reservation"),
2101 request
2102 );
2103 assert_eq!(
2104 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2105 .expect("decode child reservation status"),
2106 status_request
2107 );
2108 assert_eq!(
2109 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2110 .expect("decode child creation request"),
2111 creation_request
2112 );
2113 assert_eq!(
2114 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2115 .expect("decode child install request"),
2116 install_request
2117 );
2118 assert_eq!(
2119 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2120 .expect("decode child response"),
2121 response
2122 );
2123 assert_eq!(
2124 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2125 .expect("decode child commit request"),
2126 commit_request
2127 );
2128 assert_eq!(
2129 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2130 &directory_request_bytes
2131 )
2132 .expect("decode child Directory request"),
2133 directory_request
2134 );
2135 assert_eq!(
2136 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2137 .expect("decode child commit response"),
2138 commit_response
2139 );
2140 assert_eq!(
2141 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2142 &directory_response_bytes
2143 )
2144 .expect("decode child Directory response"),
2145 directory_response
2146 );
2147 assert_eq!(
2148 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2149 &activation_request_bytes
2150 )
2151 .expect("decode child activation request"),
2152 activation_request
2153 );
2154 assert_eq!(
2155 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2156 &activation_response_bytes
2157 )
2158 .expect("decode child activation response"),
2159 activation_response
2160 );
2161 assert_eq!(
2162 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2163 &membership_request_bytes
2164 )
2165 .expect("decode child membership request"),
2166 membership_request
2167 );
2168 assert_eq!(
2169 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2170 &membership_response_bytes
2171 )
2172 .expect("decode child membership response"),
2173 membership_response
2174 );
2175 }
2176
2177 #[test]
2178 fn component_install_request_round_trips_through_candid() {
2179 let request = RootComponentInstallRequest {
2180 operation_id: [10; 32],
2181 };
2182 let bytes = candid::encode_one(request).expect("encode install request");
2183
2184 assert_eq!(
2185 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2186 .expect("decode install request"),
2187 request
2188 );
2189 }
2190
2191 #[test]
2192 fn component_commit_request_round_trips_through_candid() {
2193 let request = RootComponentCommitRequest {
2194 operation_id: [10; 32],
2195 };
2196 let bytes = candid::encode_one(request).expect("encode commit request");
2197
2198 assert_eq!(
2199 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2200 .expect("decode commit request"),
2201 request
2202 );
2203 }
2204
2205 #[test]
2206 fn component_runtime_activation_requests_round_trip_through_candid() {
2207 let root_request = RootComponentRuntimeActivationRequest {
2208 operation_id: [22; 32],
2209 };
2210 let target_request = ComponentRuntimeActivationRequest {
2211 operation_id: root_request.operation_id,
2212 directory_authority_hash: [23; 32],
2213 };
2214 let membership_request = RootComponentMembershipActivationRequest {
2215 operation_id: root_request.operation_id,
2216 };
2217 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2218 let target_bytes =
2219 candid::encode_one(target_request).expect("encode target activation request");
2220 let membership_bytes =
2221 candid::encode_one(membership_request).expect("encode membership activation request");
2222
2223 assert_eq!(
2224 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2225 .expect("decode root activation request"),
2226 root_request
2227 );
2228 assert_eq!(
2229 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2230 .expect("decode target activation request"),
2231 target_request
2232 );
2233 assert_eq!(
2234 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2235 .expect("decode membership activation request"),
2236 membership_request
2237 );
2238 }
2239}