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, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct RootComponentChildCreationRequest {
298 pub operation_id: [u8; 32],
299 pub component: ComponentInstanceId,
300}
301
302#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
309pub struct RootComponentChildInstallRequest {
310 pub operation_id: [u8; 32],
311 pub component: ComponentInstanceId,
312}
313
314#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct RootComponentChildCommitRequest {
322 pub operation_id: [u8; 32],
323 pub component: ComponentInstanceId,
324}
325
326#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
333pub struct RootComponentChildDirectoryPreparationRequest {
334 pub operation_id: [u8; 32],
335 pub component: ComponentInstanceId,
336}
337
338#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
345pub struct RootComponentChildRuntimeActivationRequest {
346 pub operation_id: [u8; 32],
347 pub component: ComponentInstanceId,
348}
349
350#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
357pub struct RootComponentChildMembershipActivationRequest {
358 pub operation_id: [u8; 32],
359 pub component: ComponentInstanceId,
360}
361
362#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
369pub struct RootComponentCreationRequest {
370 pub operation_id: [u8; 32],
371}
372
373#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
380pub struct RootComponentInstallRequest {
381 pub operation_id: [u8; 32],
382}
383
384#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
391pub struct RootComponentCommitRequest {
392 pub operation_id: [u8; 32],
393}
394
395#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
402pub struct RootComponentDirectoryPreparationRequest {
403 pub operation_id: [u8; 32],
404}
405
406#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct RootComponentRuntimeActivationRequest {
414 pub operation_id: [u8; 32],
415}
416
417#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
424pub struct RootComponentMembershipActivationRequest {
425 pub operation_id: [u8; 32],
426}
427
428#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
435pub enum ComponentProvisioningOrigin {
436 FleetAdministrator { caller: Principal },
437}
438
439#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub enum RootComponentAllocationPhase {
447 Reserved,
448 CreationIntent,
449 Created,
450 InstallIntent,
451 Installed,
452 Verified,
453 Committed,
454}
455
456#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
463#[expect(
464 clippy::large_enum_variant,
465 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
466)]
467pub enum RootComponentSubtreeRemovalPhase {
468 Fenced,
469 Traversing(RootComponentSubtreeRemovalNode),
470 LeafSelected(RootComponentSubtreeRemovalNode),
471 StopIntent(RootComponentSubtreeRemovalStopIntent),
472 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
473 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
474 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
475 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
476 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
477 Completed(RootComponentSubtreeRemovalCompletedReceipt),
478}
479
480#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
487pub struct RootComponentSubtreeRemovalNode {
488 pub canister_id: Principal,
489 pub parent_canister_id: Principal,
490 pub role: CanisterRole,
491 pub kind: ComponentChildKind,
492 pub installed_artifact_hash: [u8; 32],
493 pub status: ComponentLifecycleStatus,
494}
495
496#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
503pub struct RootComponentSubtreeRemovalStopIntent {
504 pub leaf: RootComponentSubtreeRemovalNode,
505 pub controller: Principal,
506}
507
508#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
515pub struct RootComponentSubtreeRemovalStoppedReceipt {
516 pub stop: RootComponentSubtreeRemovalStopIntent,
517 pub observed_module_hash: [u8; 32],
518}
519
520#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
527pub struct RootComponentSubtreeRemovalDeleteIntent {
528 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
529}
530
531#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
538pub struct RootComponentSubtreeRemovalDeletedReceipt {
539 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
540}
541
542#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
549pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
550 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
551 pub removed_from_registry: ComponentRegistryHead,
552 pub previous_descendant_content_hash: [u8; 32],
553 pub previous_committed_descendants: u32,
554 pub registry: ComponentRegistryHead,
555 pub descendant_content_hash: [u8; 32],
556 pub registry_encoded_bytes: u64,
557 pub reserved_descendants: u32,
558 pub committed_descendants: u32,
559 pub directory_synchronized_at_ns: u64,
560 pub directory_authority_hash: [u8; 32],
561 pub parent_role_instances: u32,
562 pub root_managed_descendants: u32,
563 pub root_known_created_component_canisters: u32,
564}
565
566#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
573pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
574 pub operation_id: [u8; 32],
575 pub canister_id: Principal,
576 pub activation: ComponentRuntimeActivationEvidence,
577}
578
579#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
586pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
587 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
588 pub covered_fleet_registry_revision: u64,
589 pub covered_fleet_registry_content_hash: [u8; 32],
590 pub covered_component_registry: ComponentRegistryHead,
591 pub covered_authority_hash: [u8; 32],
592 pub owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence,
593 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
594}
595
596#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603pub struct RootComponentSubtreeRemovalCompletedReceipt {
604 pub registry: ComponentRegistryHead,
605 pub directory_authority_hash: [u8; 32],
606}
607
608#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
615pub enum ComponentLifecycleStatus {
616 Prepared,
617 Active,
618 Draining,
619 Removed,
620}
621
622#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
629pub struct ComponentRegistryHead {
630 pub component: ComponentInstanceId,
631 pub revision: u64,
632 pub content_hash: [u8; 32],
633}
634
635#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub struct ComponentRegistryPartitionRequest {
643 pub component: ComponentInstanceId,
644}
645
646#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
653pub struct ComponentRegistryPartitionResponse {
654 pub head: ComponentRegistryHead,
655 pub binding: ComponentBinding,
656 pub provisioning_origin: ComponentProvisioningOrigin,
657 pub release_set: FleetSubnetRootReleaseSet,
658 pub status: ComponentLifecycleStatus,
659 pub reserved_descendants: u32,
660 pub committed_descendants: u32,
661 pub encoded_bytes: u64,
662}
663
664#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
671pub struct ComponentDirectoryProvenance {
672 pub component: ComponentBinding,
673 pub source_fleet_subnet_root: Principal,
674 pub component_registry_revision: u64,
675 pub component_registry_content_hash: [u8; 32],
676 pub synchronized_at_ns: u64,
677}
678
679#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
686pub struct ComponentDirectoryHead {
687 pub provenance: ComponentDirectoryProvenance,
688 pub descendant_count: u32,
689}
690
691#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
698pub struct ComponentDirectoryHeadRequest {
699 pub component: ComponentInstanceId,
700}
701
702#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
709pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
710
711#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
718pub struct ComponentDirectoryPageRequest {
719 pub directory: ComponentDirectoryHead,
720 pub parent_canister_id: Option<Principal>,
721 pub role: Option<CanisterRole>,
722 pub status: Option<ComponentLifecycleStatus>,
723 pub cursor: Option<ComponentDirectoryPageCursor>,
724 pub limit: u16,
725}
726
727#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
734pub struct ComponentDirectoryChildEntry {
735 pub binding: ComponentChildBinding,
736 pub kind: ComponentChildKind,
737 pub installed_artifact_hash: [u8; 32],
738 pub status: ComponentLifecycleStatus,
739}
740
741#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
748pub struct ComponentDirectoryPageResponse {
749 pub directory: ComponentDirectoryHead,
750 pub entries: Vec<ComponentDirectoryChildEntry>,
751 pub next_cursor: Option<ComponentDirectoryPageCursor>,
752}
753
754#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
761pub struct ComponentRuntimeDirectoryAuthority {
762 pub fleet: FleetDirectorySnapshot,
763 pub component: ComponentDirectoryHead,
764}
765
766#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
773pub struct ComponentRuntimeDirectoryPreparationRequest {
774 pub operation_id: [u8; 32],
775 pub authority: ComponentRuntimeDirectoryAuthority,
776}
777
778#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
785pub struct ComponentRuntimeDirectorySynchronizationRequest {
786 pub operation_id: [u8; 32],
787 pub authority: ComponentRuntimeDirectoryAuthority,
788}
789
790#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
797pub enum ComponentRuntimePhase {
798 AwaitingDirectory,
799 DirectoryPrepared,
800 Active,
801}
802
803#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
810pub struct ComponentRuntimeActivationEvidence {
811 pub directory_authority_hash: [u8; 32],
812 pub activated_at_ns: u64,
813}
814
815#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
822pub struct ComponentRuntimeActivationRequest {
823 pub operation_id: [u8; 32],
824 pub directory_authority_hash: [u8; 32],
825}
826
827#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
834pub struct ComponentRuntimeStatusResponse {
835 pub operation_id: [u8; 32],
836 pub binding: ManagedCanisterBinding,
837 pub phase: ComponentRuntimePhase,
838 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
839 pub authority_hash: Option<[u8; 32]>,
840 pub activation: Option<ComponentRuntimeActivationEvidence>,
841}
842
843#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
850pub struct ComponentRuntimeDirectoryConvergenceEvidence {
851 pub operation_id: [u8; 32],
852 pub binding: ManagedCanisterBinding,
853 pub covered_authority: ComponentRuntimeDirectoryAuthority,
854 pub covered_authority_hash: [u8; 32],
855 pub activation: ComponentRuntimeActivationEvidence,
856}
857
858#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
865pub struct RootComponentCreationEvidence {
866 pub wasm_store: Principal,
867 pub payload_hash: [u8; 32],
868 pub payload_size_bytes: u64,
869 pub initial_cycles: Cycles,
870 pub controller: Principal,
871 pub canister: Option<Principal>,
872}
873
874#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
881pub struct RootComponentInstallEvidence {
882 pub raw_module_hash: [u8; 32],
883 pub chunk_hashes: Vec<Vec<u8>>,
884 pub binding: ComponentBinding,
885}
886
887#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
894pub struct RootComponentChildInstallEvidence {
895 pub raw_module_hash: [u8; 32],
896 pub chunk_hashes: Vec<Vec<u8>>,
897 pub binding: ComponentChildBinding,
898}
899
900#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
907pub struct RootComponentAllocationResponse {
908 pub operation_id: [u8; 32],
909 pub allocation_sequence: u64,
910 pub component: ComponentInstanceId,
911 pub component_spec: ComponentSpecId,
912 pub spec_hash: [u8; 32],
913 pub role: CanisterRole,
914 pub provisioning_origin: ComponentProvisioningOrigin,
915 pub release_set: FleetSubnetRootReleaseSet,
916 pub phase: RootComponentAllocationPhase,
917 pub creation: Option<RootComponentCreationEvidence>,
918 pub installation: Option<RootComponentInstallEvidence>,
919}
920
921#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
928pub struct RootComponentChildAllocationResponse {
929 pub operation_id: [u8; 32],
930 pub component: ComponentInstanceId,
931 pub parent_canister_id: Principal,
932 pub parent_role: CanisterRole,
933 pub child_role: CanisterRole,
934 pub child_kind: ComponentChildKind,
935 pub maximum_instances_per_parent: u32,
936 pub maximum_descendants: u32,
937 pub maximum_registry_bytes: u64,
938 pub reserved_against_registry: ComponentRegistryHead,
939 pub release_set: FleetSubnetRootReleaseSet,
940 pub phase: RootComponentAllocationPhase,
941 pub creation: Option<RootComponentCreationEvidence>,
942 pub installation: Option<RootComponentChildInstallEvidence>,
943}
944
945#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
952pub struct RootComponentSubtreeRemovalResponse {
953 pub operation_id: [u8; 32],
954 pub component: ComponentInstanceId,
955 pub target_canister_id: Principal,
956 pub target_parent_canister_id: Principal,
957 pub target_role: CanisterRole,
958 pub target_status: ComponentLifecycleStatus,
959 pub reserved_against_registry: ComponentRegistryHead,
960 pub maximum_completed_leaves: u32,
961 pub completed_leaves: u32,
962 pub traversal_steps: u32,
963 pub phase: RootComponentSubtreeRemovalPhase,
964}
965
966#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
973pub struct RootComponentDrainingResponse {
974 pub operation_id: [u8; 32],
975 pub component: ComponentInstanceId,
976 pub previous_registry: ComponentRegistryHead,
977 pub registry: ComponentRegistryHead,
978 pub descendant_count: u32,
979 pub descendant_content_hash: [u8; 32],
980 pub directory_authority_hash: [u8; 32],
981 pub started_at_ns: u64,
982}
983
984#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
991pub struct RootComponentChildCommitResponse {
992 pub allocation: RootComponentChildAllocationResponse,
993 pub registry: ComponentRegistryPartitionResponse,
994 pub directory: ComponentDirectoryHead,
995}
996
997#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1004pub struct RootComponentChildDirectoryPreparationResponse {
1005 pub committed: RootComponentChildCommitResponse,
1006 pub child: ComponentRuntimeStatusResponse,
1007 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1008 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1009}
1010
1011#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1018pub struct RootComponentChildRuntimeActivationResponse {
1019 pub committed: RootComponentChildCommitResponse,
1020 pub child: ComponentRuntimeStatusResponse,
1021}
1022
1023#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030pub struct RootComponentChildMembershipActivationResponse {
1031 pub committed: RootComponentChildCommitResponse,
1032 pub registry: ComponentRegistryPartitionResponse,
1033 pub directory: ComponentDirectoryHead,
1034 pub child: ComponentRuntimeStatusResponse,
1035}
1036
1037#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1044pub struct RootComponentCommitResponse {
1045 pub allocation: RootComponentAllocationResponse,
1046 pub registry: ComponentRegistryPartitionResponse,
1047 pub directory: ComponentDirectoryHead,
1048}
1049
1050#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1057pub struct RootComponentDirectoryPreparationResponse {
1058 pub committed: RootComponentCommitResponse,
1059 pub target: ComponentRuntimeStatusResponse,
1060}
1061
1062#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1069pub struct RootComponentRuntimeActivationResponse {
1070 pub committed: RootComponentCommitResponse,
1071 pub target: ComponentRuntimeStatusResponse,
1072}
1073
1074#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1081pub struct RootComponentMembershipActivationResponse {
1082 pub allocation: RootComponentAllocationResponse,
1083 pub registry: ComponentRegistryPartitionResponse,
1084 pub directory: ComponentDirectoryHead,
1085 pub target: ComponentRuntimeStatusResponse,
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090 use super::*;
1091 use crate::{
1092 dto::root_store::RootStoreBootstrapRequest,
1093 ids::{
1094 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1095 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1096 },
1097 };
1098
1099 #[test]
1100 fn component_registry_contracts_round_trip_through_candid() {
1101 let request = RootComponentRegistryPreparationRequest {
1102 store_bootstrap: RootStoreBootstrapRequest {
1103 manifest_payload_size_bytes: 128,
1104 },
1105 expected_fleet_registry: FleetRegistryVersion {
1106 authority: fleet_registry_authority(),
1107 revision: 4,
1108 content_hash: [5; 32],
1109 },
1110 };
1111 let response = RootComponentRegistryStatusResponse {
1112 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1113 prepared_against_registry: request.expected_fleet_registry.clone(),
1114 release_set: FleetSubnetRootReleaseSet {
1115 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1116 [7; 32],
1117 )),
1118 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1119 },
1120 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1121 next_allocation_sequence: 1,
1122 reserved_component_instances: 0,
1123 committed_component_instances: 0,
1124 managed_descendants: 0,
1125 known_created_component_canisters: 0,
1126 encoded_bytes: 0,
1127 initial_inventory: Some(RootComponentInitialInventoryStatus {
1128 fleet_activation_operation_id: [10; 32],
1129 component_count: 0,
1130 inventory_hash: [11; 32],
1131 sealed_at_ns: 12,
1132 directories_converged: true,
1133 root_runtime_activated: true,
1134 }),
1135 };
1136 let allocation = RootComponentAllocationResponse {
1137 operation_id: [10; 32],
1138 allocation_sequence: 1,
1139 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1140 component_spec: "projects".parse().expect("Component Spec ID"),
1141 spec_hash: [12; 32],
1142 role: CanisterRole::new("project_hub"),
1143 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1144 caller: Principal::from_slice(&[13; 29]),
1145 },
1146 release_set: response.release_set,
1147 phase: RootComponentAllocationPhase::Reserved,
1148 creation: None,
1149 installation: None,
1150 };
1151 let created = RootComponentAllocationResponse {
1152 phase: RootComponentAllocationPhase::Created,
1153 creation: Some(RootComponentCreationEvidence {
1154 wasm_store: Principal::from_slice(&[14; 29]),
1155 payload_hash: [15; 32],
1156 payload_size_bytes: 4_096,
1157 initial_cycles: Cycles::new(5_000_000_000_000),
1158 controller: Principal::from_slice(&[6; 29]),
1159 canister: Some(Principal::from_slice(&[16; 29])),
1160 }),
1161 installation: None,
1162 ..allocation.clone()
1163 };
1164 let request_bytes = candid::encode_one(&request).expect("encode request");
1165 let response_bytes = candid::encode_one(&response).expect("encode response");
1166 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1167 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1168
1169 assert_eq!(
1170 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1171 .expect("decode request"),
1172 request
1173 );
1174 assert_eq!(
1175 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1176 .expect("decode response"),
1177 response
1178 );
1179 assert_eq!(
1180 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1181 .expect("decode allocation"),
1182 allocation
1183 );
1184 assert_eq!(
1185 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1186 .expect("decode created allocation"),
1187 created
1188 );
1189 }
1190
1191 #[test]
1192 fn component_commit_response_round_trips_through_candid() {
1193 let root = Principal::from_slice(&[6; 29]);
1194 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1195 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1196 let release_set = FleetSubnetRootReleaseSet {
1197 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1198 [7; 32],
1199 )),
1200 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1201 };
1202 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1203 caller: Principal::from_slice(&[13; 29]),
1204 };
1205 let binding = ComponentBinding {
1206 authority: fleet_registry_authority(),
1207 component,
1208 component_spec: component_spec.clone(),
1209 spec_hash: [12; 32],
1210 role: CanisterRole::new("project_hub"),
1211 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1212 fleet_subnet_root: root,
1213 canister_id: Principal::from_slice(&[16; 29]),
1214 };
1215 let head = ComponentRegistryHead {
1216 component,
1217 revision: 1,
1218 content_hash: [18; 32],
1219 };
1220 let committed = RootComponentCommitResponse {
1221 allocation: RootComponentAllocationResponse {
1222 operation_id: [10; 32],
1223 allocation_sequence: 1,
1224 component,
1225 component_spec,
1226 spec_hash: binding.spec_hash,
1227 role: binding.role.clone(),
1228 provisioning_origin: provisioning_origin.clone(),
1229 release_set,
1230 phase: RootComponentAllocationPhase::Committed,
1231 creation: Some(RootComponentCreationEvidence {
1232 wasm_store: Principal::from_slice(&[14; 29]),
1233 payload_hash: [15; 32],
1234 payload_size_bytes: 4_096,
1235 initial_cycles: Cycles::new(5_000_000_000_000),
1236 controller: root,
1237 canister: Some(binding.canister_id),
1238 }),
1239 installation: Some(RootComponentInstallEvidence {
1240 raw_module_hash: [20; 32],
1241 chunk_hashes: vec![vec![21; 32]],
1242 binding: binding.clone(),
1243 }),
1244 },
1245 registry: ComponentRegistryPartitionResponse {
1246 head: head.clone(),
1247 binding: binding.clone(),
1248 provisioning_origin,
1249 release_set,
1250 status: ComponentLifecycleStatus::Prepared,
1251 reserved_descendants: 0,
1252 committed_descendants: 0,
1253 encoded_bytes: 2_048,
1254 },
1255 directory: ComponentDirectoryHead {
1256 provenance: ComponentDirectoryProvenance {
1257 component: binding,
1258 source_fleet_subnet_root: root,
1259 component_registry_revision: head.revision,
1260 component_registry_content_hash: head.content_hash,
1261 synchronized_at_ns: 19,
1262 },
1263 descendant_count: 0,
1264 },
1265 };
1266 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1267
1268 assert_eq!(
1269 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1270 .expect("decode committed allocation"),
1271 committed
1272 );
1273 }
1274
1275 #[test]
1276 fn component_directory_page_contracts_round_trip_through_candid() {
1277 let root = Principal::from_slice(&[6; 29]);
1278 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1279 let binding = ComponentBinding {
1280 authority: fleet_registry_authority(),
1281 component,
1282 component_spec: "projects".parse().expect("Component Spec ID"),
1283 spec_hash: [12; 32],
1284 role: CanisterRole::new("project_hub"),
1285 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1286 fleet_subnet_root: root,
1287 canister_id: Principal::from_slice(&[16; 29]),
1288 };
1289 let directory = ComponentDirectoryHead {
1290 provenance: ComponentDirectoryProvenance {
1291 component: binding.clone(),
1292 source_fleet_subnet_root: root,
1293 component_registry_revision: 3,
1294 component_registry_content_hash: [18; 32],
1295 synchronized_at_ns: 19,
1296 },
1297 descendant_count: 1,
1298 };
1299 let request = ComponentDirectoryPageRequest {
1300 directory: directory.clone(),
1301 parent_canister_id: Some(binding.canister_id),
1302 role: Some(CanisterRole::new("project_instance")),
1303 status: Some(ComponentLifecycleStatus::Active),
1304 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1305 limit: 50,
1306 };
1307 let response = ComponentDirectoryPageResponse {
1308 directory,
1309 entries: vec![ComponentDirectoryChildEntry {
1310 binding: ComponentChildBinding {
1311 component: binding.clone(),
1312 parent_canister_id: binding.canister_id,
1313 role: CanisterRole::new("project_instance"),
1314 canister_id: Principal::from_slice(&[21; 29]),
1315 },
1316 kind: ComponentChildKind::Instance,
1317 installed_artifact_hash: [22; 32],
1318 status: ComponentLifecycleStatus::Active,
1319 }],
1320 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1321 };
1322 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1323 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1324
1325 assert_eq!(
1326 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1327 .expect("decode Directory page request"),
1328 request
1329 );
1330 assert_eq!(
1331 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1332 .expect("decode Directory page response"),
1333 response
1334 );
1335 }
1336
1337 fn fleet_registry_authority() -> FleetRegistryAuthority {
1338 FleetRegistryAuthority {
1339 binding: FleetCoordinatorBinding {
1340 fleet: crate::ids::FleetBinding {
1341 fleet: FleetKey {
1342 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1343 fleet_id: FleetId::from_generated_bytes([1; 32]),
1344 },
1345 app: AppId::from("toko"),
1346 },
1347 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1348 coordinator: Principal::from_slice(&[3; 29]),
1349 },
1350 epoch: 1,
1351 }
1352 }
1353
1354 #[test]
1355 fn component_creation_request_round_trips_through_candid() {
1356 let request = RootComponentCreationRequest {
1357 operation_id: [10; 32],
1358 };
1359 let bytes = candid::encode_one(request).expect("encode creation request");
1360
1361 assert_eq!(
1362 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1363 .expect("decode creation request"),
1364 request
1365 );
1366 }
1367
1368 #[test]
1369 #[expect(
1370 clippy::too_many_lines,
1371 reason = "one Candid contract test covers every subtree-removal phase receipt"
1372 )]
1373 fn component_subtree_removal_contracts_round_trip_through_candid() {
1374 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1375 let registry = ComponentRegistryHead {
1376 component,
1377 revision: 7,
1378 content_hash: [42; 32],
1379 };
1380 let request = RootComponentSubtreeRemovalRequest {
1381 operation_id: [43; 32],
1382 component,
1383 target_canister_id: Principal::from_slice(&[44; 29]),
1384 expected_registry: registry.clone(),
1385 };
1386 let status_request = RootComponentSubtreeRemovalStatusRequest {
1387 operation_id: request.operation_id,
1388 component,
1389 };
1390 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1391 operation_id: request.operation_id,
1392 component,
1393 expected_traversal_steps: 1,
1394 };
1395 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1396 operation_id: request.operation_id,
1397 component,
1398 expected_traversal_steps: 2,
1399 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1400 expected_leaf_parent_canister_id: request.target_canister_id,
1401 };
1402 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1403 observed_module_hash: [49; 32],
1404 stop: RootComponentSubtreeRemovalStopIntent {
1405 controller: Principal::from_slice(&[48; 29]),
1406 leaf: RootComponentSubtreeRemovalNode {
1407 canister_id: Principal::from_slice(&[46; 29]),
1408 parent_canister_id: request.target_canister_id,
1409 role: CanisterRole::new("project_ledger"),
1410 kind: ComponentChildKind::Singleton,
1411 installed_artifact_hash: [47; 32],
1412 status: ComponentLifecycleStatus::Active,
1413 },
1414 },
1415 };
1416 let response = RootComponentSubtreeRemovalResponse {
1417 operation_id: request.operation_id,
1418 component,
1419 target_canister_id: request.target_canister_id,
1420 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1421 target_role: CanisterRole::new("project_instance"),
1422 target_status: ComponentLifecycleStatus::Active,
1423 reserved_against_registry: registry,
1424 maximum_completed_leaves: 4,
1425 completed_leaves: 1,
1426 traversal_steps: 2,
1427 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1428 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1429 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1430 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1431 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1432 },
1433 removed_from_registry: ComponentRegistryHead {
1434 component,
1435 revision: 8,
1436 content_hash: [50; 32],
1437 },
1438 previous_descendant_content_hash: [51; 32],
1439 previous_committed_descendants: 4,
1440 registry: ComponentRegistryHead {
1441 component,
1442 revision: 9,
1443 content_hash: [52; 32],
1444 },
1445 descendant_content_hash: [53; 32],
1446 registry_encoded_bytes: 4_096,
1447 reserved_descendants: 1,
1448 committed_descendants: 3,
1449 directory_synchronized_at_ns: 54,
1450 directory_authority_hash: [55; 32],
1451 parent_role_instances: 0,
1452 root_managed_descendants: 4,
1453 root_known_created_component_canisters: 4,
1454 },
1455 covered_fleet_registry_revision: 6,
1456 covered_fleet_registry_content_hash: [56; 32],
1457 covered_component_registry: ComponentRegistryHead {
1458 component,
1459 revision: 9,
1460 content_hash: [52; 32],
1461 },
1462 covered_authority_hash: [55; 32],
1463 owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1464 operation_id: [57; 32],
1465 canister_id: Principal::from_slice(&[58; 29]),
1466 activation: ComponentRuntimeActivationEvidence {
1467 directory_authority_hash: [59; 32],
1468 activated_at_ns: 60,
1469 },
1470 },
1471 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1472 operation_id: [61; 32],
1473 canister_id: request.target_canister_id,
1474 activation: ComponentRuntimeActivationEvidence {
1475 directory_authority_hash: [62; 32],
1476 activated_at_ns: 63,
1477 },
1478 }),
1479 },
1480 ),
1481 };
1482
1483 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1484 let advance_bytes =
1485 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1486 let stop_bytes =
1487 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1488 let status_bytes =
1489 candid::encode_one(status_request).expect("encode subtree removal status request");
1490 let response_bytes =
1491 candid::encode_one(&response).expect("encode subtree removal response");
1492
1493 assert_eq!(
1494 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1495 .expect("decode subtree removal request"),
1496 request
1497 );
1498 assert_eq!(
1499 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1500 .expect("decode subtree removal advance request"),
1501 advance_request
1502 );
1503 assert_eq!(
1504 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1505 .expect("decode subtree removal stop request"),
1506 stop_request
1507 );
1508 assert_eq!(
1509 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1510 .expect("decode subtree removal status request"),
1511 status_request
1512 );
1513 assert_eq!(
1514 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1515 .expect("decode subtree removal response"),
1516 response
1517 );
1518 }
1519
1520 #[test]
1521 fn component_draining_contracts_round_trip_through_candid() {
1522 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1523 let previous_registry = ComponentRegistryHead {
1524 component,
1525 revision: 7,
1526 content_hash: [61; 32],
1527 };
1528 let request = RootComponentDrainingRequest {
1529 operation_id: [62; 32],
1530 component,
1531 expected_registry: previous_registry.clone(),
1532 };
1533 let status_request = RootComponentDrainingStatusRequest {
1534 operation_id: request.operation_id,
1535 component,
1536 };
1537 let response = RootComponentDrainingResponse {
1538 operation_id: request.operation_id,
1539 component,
1540 previous_registry,
1541 registry: ComponentRegistryHead {
1542 component,
1543 revision: 8,
1544 content_hash: [63; 32],
1545 },
1546 descendant_count: 20_000,
1547 descendant_content_hash: [64; 32],
1548 directory_authority_hash: [65; 32],
1549 started_at_ns: 66,
1550 };
1551
1552 let request_bytes =
1553 candid::encode_one(&request).expect("encode Component draining request");
1554 let status_bytes =
1555 candid::encode_one(status_request).expect("encode Component draining status request");
1556 let response_bytes =
1557 candid::encode_one(&response).expect("encode Component draining response");
1558
1559 assert_eq!(
1560 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1561 .expect("decode Component draining request"),
1562 request
1563 );
1564 assert_eq!(
1565 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1566 .expect("decode Component draining status request"),
1567 status_request
1568 );
1569 assert_eq!(
1570 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1571 .expect("decode Component draining response"),
1572 response
1573 );
1574 }
1575
1576 #[test]
1577 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
1578 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
1579 operation_id: [50; 32],
1580 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1581 expected_traversal_steps: 3,
1582 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1583 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1584 };
1585 let request = RootComponentSubtreeRemovalDeleteRequest {
1586 operation_id: prepare.operation_id,
1587 component: prepare.component,
1588 expected_traversal_steps: prepare.expected_traversal_steps,
1589 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1590 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1591 };
1592 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
1593 operation_id: prepare.operation_id,
1594 component: prepare.component,
1595 expected_traversal_steps: prepare.expected_traversal_steps,
1596 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1597 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1598 };
1599 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
1600 operation_id: prepare.operation_id,
1601 component: prepare.component,
1602 expected_traversal_steps: prepare.expected_traversal_steps,
1603 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1604 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1605 };
1606 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
1607 operation_id: prepare.operation_id,
1608 component: prepare.component,
1609 expected_traversal_steps: prepare.expected_traversal_steps,
1610 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1611 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1612 };
1613 let prepare_bytes = candid::encode_one(prepare)
1614 .expect("encode subtree removal deletion preparation request");
1615 let request_bytes =
1616 candid::encode_one(request).expect("encode subtree removal deletion request");
1617 let membership_request_bytes = candid::encode_one(membership_request)
1618 .expect("encode subtree removal membership-removal request");
1619 let directory_request_bytes = candid::encode_one(directory_request)
1620 .expect("encode subtree removal Directory synchronization request");
1621 let finalization_request_bytes = candid::encode_one(finalization_request)
1622 .expect("encode subtree removal leaf-finalization request");
1623
1624 assert_eq!(
1625 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
1626 &prepare_bytes
1627 )
1628 .expect("decode subtree removal deletion preparation request"),
1629 prepare
1630 );
1631 assert_eq!(
1632 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
1633 .expect("decode subtree removal deletion request"),
1634 request
1635 );
1636 assert_eq!(
1637 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
1638 &membership_request_bytes
1639 )
1640 .expect("decode subtree removal membership-removal request"),
1641 membership_request
1642 );
1643 assert_eq!(
1644 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
1645 &directory_request_bytes
1646 )
1647 .expect("decode subtree removal Directory synchronization request"),
1648 directory_request
1649 );
1650 assert_eq!(
1651 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
1652 &finalization_request_bytes
1653 )
1654 .expect("decode subtree removal leaf-finalization request"),
1655 finalization_request
1656 );
1657 }
1658
1659 #[test]
1660 fn component_subtree_removal_stop_request_round_trips_through_candid() {
1661 let request = RootComponentSubtreeRemovalStopRequest {
1662 operation_id: [50; 32],
1663 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1664 expected_traversal_steps: 3,
1665 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1666 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1667 };
1668 let bytes =
1669 candid::encode_one(request).expect("encode subtree removal stop execution request");
1670
1671 assert_eq!(
1672 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
1673 .expect("decode subtree removal stop execution request"),
1674 request
1675 );
1676 }
1677
1678 #[test]
1679 #[expect(
1680 clippy::too_many_lines,
1681 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1682 )]
1683 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1684 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1685 let registry = ComponentRegistryHead {
1686 component,
1687 revision: 2,
1688 content_hash: [12; 32],
1689 };
1690 let request = RootComponentChildAllocationRequest {
1691 operation_id: [13; 32],
1692 component,
1693 expected_registry: registry.clone(),
1694 child_role: CanisterRole::new("project_instance"),
1695 };
1696 let status_request = RootComponentChildAllocationStatusRequest {
1697 operation_id: request.operation_id,
1698 component,
1699 };
1700 let creation_request = RootComponentChildCreationRequest {
1701 operation_id: request.operation_id,
1702 component,
1703 };
1704 let install_request = RootComponentChildInstallRequest {
1705 operation_id: request.operation_id,
1706 component,
1707 };
1708 let commit_request = RootComponentChildCommitRequest {
1709 operation_id: request.operation_id,
1710 component,
1711 };
1712 let directory_request = RootComponentChildDirectoryPreparationRequest {
1713 operation_id: request.operation_id,
1714 component,
1715 };
1716 let activation_request = RootComponentChildRuntimeActivationRequest {
1717 operation_id: request.operation_id,
1718 component,
1719 };
1720 let membership_request = RootComponentChildMembershipActivationRequest {
1721 operation_id: request.operation_id,
1722 component,
1723 };
1724 let root = Principal::from_slice(&[17; 29]);
1725 let parent = Principal::from_slice(&[14; 29]);
1726 let child = Principal::from_slice(&[18; 29]);
1727 let child_binding = ComponentChildBinding {
1728 component: ComponentBinding {
1729 authority: fleet_registry_authority(),
1730 component,
1731 component_spec: "projects".parse().expect("Component Spec"),
1732 spec_hash: [19; 32],
1733 role: CanisterRole::new("project_hub"),
1734 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1735 fleet_subnet_root: root,
1736 canister_id: parent,
1737 },
1738 parent_canister_id: parent,
1739 role: request.child_role.clone(),
1740 canister_id: child,
1741 };
1742 let response = RootComponentChildAllocationResponse {
1743 operation_id: request.operation_id,
1744 component,
1745 parent_canister_id: parent,
1746 parent_role: CanisterRole::new("project_hub"),
1747 child_role: request.child_role.clone(),
1748 child_kind: ComponentChildKind::Instance,
1749 maximum_instances_per_parent: 10_000,
1750 maximum_descendants: 20_000,
1751 maximum_registry_bytes: 16_777_216,
1752 reserved_against_registry: registry,
1753 release_set: FleetSubnetRootReleaseSet {
1754 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1755 [15; 32],
1756 )),
1757 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1758 },
1759 phase: RootComponentAllocationPhase::Verified,
1760 creation: Some(RootComponentCreationEvidence {
1761 wasm_store: Principal::from_slice(&[21; 29]),
1762 payload_hash: [22; 32],
1763 payload_size_bytes: 4_096,
1764 initial_cycles: Cycles::new(5_000_000_000_000),
1765 controller: root,
1766 canister: Some(child),
1767 }),
1768 installation: Some(RootComponentChildInstallEvidence {
1769 raw_module_hash: [23; 32],
1770 chunk_hashes: vec![vec![24; 32]],
1771 binding: child_binding.clone(),
1772 }),
1773 };
1774 let commit_response = RootComponentChildCommitResponse {
1775 allocation: response.clone(),
1776 registry: ComponentRegistryPartitionResponse {
1777 head: ComponentRegistryHead {
1778 component,
1779 revision: 3,
1780 content_hash: [25; 32],
1781 },
1782 binding: child_binding.component.clone(),
1783 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1784 caller: Principal::from_slice(&[26; 29]),
1785 },
1786 release_set: response.release_set,
1787 status: ComponentLifecycleStatus::Active,
1788 reserved_descendants: 0,
1789 committed_descendants: 1,
1790 encoded_bytes: 8_192,
1791 },
1792 directory: ComponentDirectoryHead {
1793 provenance: ComponentDirectoryProvenance {
1794 component: child_binding.component.clone(),
1795 source_fleet_subnet_root: root,
1796 component_registry_revision: 3,
1797 component_registry_content_hash: [25; 32],
1798 synchronized_at_ns: 27,
1799 },
1800 descendant_count: 1,
1801 },
1802 };
1803 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1804 fleet: FleetDirectorySnapshot {
1805 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1806 registry: FleetRegistryVersion {
1807 authority: fleet_registry_authority(),
1808 revision: 4,
1809 content_hash: [28; 32],
1810 },
1811 source_fleet_subnet_root: root,
1812 },
1813 fleet_subnet_roots: vec![
1814 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1815 placement_subnet: commit_response.registry.binding.placement_subnet,
1816 fleet_subnet_root: root,
1817 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1818 },
1819 ],
1820 },
1821 component: commit_response.directory.clone(),
1822 };
1823 let activation = ComponentRuntimeActivationEvidence {
1824 directory_authority_hash: [29; 32],
1825 activated_at_ns: 30,
1826 };
1827 let directory_response = RootComponentChildDirectoryPreparationResponse {
1828 committed: commit_response.clone(),
1829 child: ComponentRuntimeStatusResponse {
1830 operation_id: request.operation_id,
1831 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1832 phase: ComponentRuntimePhase::DirectoryPrepared,
1833 authority: Some(runtime_authority.clone()),
1834 authority_hash: Some([31; 32]),
1835 activation: None,
1836 },
1837 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1838 operation_id: [32; 32],
1839 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1840 covered_authority: runtime_authority.clone(),
1841 covered_authority_hash: [31; 32],
1842 activation,
1843 },
1844 parent: None,
1845 };
1846 let activation_response = RootComponentChildRuntimeActivationResponse {
1847 committed: commit_response.clone(),
1848 child: ComponentRuntimeStatusResponse {
1849 operation_id: request.operation_id,
1850 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1851 phase: ComponentRuntimePhase::Active,
1852 authority: Some(runtime_authority.clone()),
1853 authority_hash: Some([31; 32]),
1854 activation: Some(ComponentRuntimeActivationEvidence {
1855 directory_authority_hash: [31; 32],
1856 activated_at_ns: 33,
1857 }),
1858 },
1859 };
1860 let active_directory = ComponentDirectoryHead {
1861 provenance: ComponentDirectoryProvenance {
1862 component: child_binding.component.clone(),
1863 source_fleet_subnet_root: root,
1864 component_registry_revision: 4,
1865 component_registry_content_hash: [34; 32],
1866 synchronized_at_ns: 35,
1867 },
1868 descendant_count: 1,
1869 };
1870 let active_authority = ComponentRuntimeDirectoryAuthority {
1871 fleet: runtime_authority.fleet,
1872 component: active_directory.clone(),
1873 };
1874 let membership_response = RootComponentChildMembershipActivationResponse {
1875 committed: commit_response.clone(),
1876 registry: ComponentRegistryPartitionResponse {
1877 head: ComponentRegistryHead {
1878 component,
1879 revision: 4,
1880 content_hash: [34; 32],
1881 },
1882 binding: child_binding.component.clone(),
1883 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1884 release_set: commit_response.registry.release_set,
1885 status: ComponentLifecycleStatus::Active,
1886 reserved_descendants: 0,
1887 committed_descendants: 1,
1888 encoded_bytes: 8_256,
1889 },
1890 directory: active_directory,
1891 child: ComponentRuntimeStatusResponse {
1892 operation_id: request.operation_id,
1893 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1894 phase: ComponentRuntimePhase::Active,
1895 authority: Some(active_authority),
1896 authority_hash: Some([36; 32]),
1897 activation: Some(ComponentRuntimeActivationEvidence {
1898 directory_authority_hash: [31; 32],
1899 activated_at_ns: 33,
1900 }),
1901 },
1902 };
1903
1904 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1905 let status_bytes =
1906 candid::encode_one(status_request).expect("encode child reservation status");
1907 let creation_bytes =
1908 candid::encode_one(creation_request).expect("encode child creation request");
1909 let install_bytes =
1910 candid::encode_one(install_request).expect("encode child install request");
1911 let response_bytes = candid::encode_one(&response).expect("encode child response");
1912 let commit_request_bytes =
1913 candid::encode_one(commit_request).expect("encode child commit request");
1914 let directory_request_bytes =
1915 candid::encode_one(directory_request).expect("encode child Directory request");
1916 let activation_request_bytes =
1917 candid::encode_one(activation_request).expect("encode child activation request");
1918 let membership_request_bytes =
1919 candid::encode_one(membership_request).expect("encode child membership request");
1920 let commit_response_bytes =
1921 candid::encode_one(&commit_response).expect("encode child commit response");
1922 let directory_response_bytes =
1923 candid::encode_one(&directory_response).expect("encode child Directory response");
1924 let activation_response_bytes =
1925 candid::encode_one(&activation_response).expect("encode child activation response");
1926 let membership_response_bytes =
1927 candid::encode_one(&membership_response).expect("encode child membership response");
1928
1929 assert_eq!(
1930 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1931 .expect("decode child reservation"),
1932 request
1933 );
1934 assert_eq!(
1935 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1936 .expect("decode child reservation status"),
1937 status_request
1938 );
1939 assert_eq!(
1940 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1941 .expect("decode child creation request"),
1942 creation_request
1943 );
1944 assert_eq!(
1945 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1946 .expect("decode child install request"),
1947 install_request
1948 );
1949 assert_eq!(
1950 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1951 .expect("decode child response"),
1952 response
1953 );
1954 assert_eq!(
1955 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1956 .expect("decode child commit request"),
1957 commit_request
1958 );
1959 assert_eq!(
1960 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1961 &directory_request_bytes
1962 )
1963 .expect("decode child Directory request"),
1964 directory_request
1965 );
1966 assert_eq!(
1967 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1968 .expect("decode child commit response"),
1969 commit_response
1970 );
1971 assert_eq!(
1972 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1973 &directory_response_bytes
1974 )
1975 .expect("decode child Directory response"),
1976 directory_response
1977 );
1978 assert_eq!(
1979 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1980 &activation_request_bytes
1981 )
1982 .expect("decode child activation request"),
1983 activation_request
1984 );
1985 assert_eq!(
1986 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1987 &activation_response_bytes
1988 )
1989 .expect("decode child activation response"),
1990 activation_response
1991 );
1992 assert_eq!(
1993 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1994 &membership_request_bytes
1995 )
1996 .expect("decode child membership request"),
1997 membership_request
1998 );
1999 assert_eq!(
2000 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2001 &membership_response_bytes
2002 )
2003 .expect("decode child membership response"),
2004 membership_response
2005 );
2006 }
2007
2008 #[test]
2009 fn component_install_request_round_trips_through_candid() {
2010 let request = RootComponentInstallRequest {
2011 operation_id: [10; 32],
2012 };
2013 let bytes = candid::encode_one(request).expect("encode install request");
2014
2015 assert_eq!(
2016 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2017 .expect("decode install request"),
2018 request
2019 );
2020 }
2021
2022 #[test]
2023 fn component_commit_request_round_trips_through_candid() {
2024 let request = RootComponentCommitRequest {
2025 operation_id: [10; 32],
2026 };
2027 let bytes = candid::encode_one(request).expect("encode commit request");
2028
2029 assert_eq!(
2030 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2031 .expect("decode commit request"),
2032 request
2033 );
2034 }
2035
2036 #[test]
2037 fn component_runtime_activation_requests_round_trip_through_candid() {
2038 let root_request = RootComponentRuntimeActivationRequest {
2039 operation_id: [22; 32],
2040 };
2041 let target_request = ComponentRuntimeActivationRequest {
2042 operation_id: root_request.operation_id,
2043 directory_authority_hash: [23; 32],
2044 };
2045 let membership_request = RootComponentMembershipActivationRequest {
2046 operation_id: root_request.operation_id,
2047 };
2048 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2049 let target_bytes =
2050 candid::encode_one(target_request).expect("encode target activation request");
2051 let membership_bytes =
2052 candid::encode_one(membership_request).expect("encode membership activation request");
2053
2054 assert_eq!(
2055 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2056 .expect("decode root activation request"),
2057 root_request
2058 );
2059 assert_eq!(
2060 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2061 .expect("decode target activation request"),
2062 target_request
2063 );
2064 assert_eq!(
2065 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2066 .expect("decode membership activation request"),
2067 membership_request
2068 );
2069 }
2070}