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 RootComponentDrainingAdvanceRequest {
323 pub operation_id: [u8; 32],
324 pub component: ComponentInstanceId,
325}
326
327#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
334pub struct RootComponentFinalInventoryRequest {
335 pub operation_id: [u8; 32],
336 pub component: ComponentInstanceId,
337 pub expected_registry: ComponentRegistryHead,
338}
339
340#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
347pub struct RootComponentDeletionRequest {
348 pub operation_id: [u8; 32],
349 pub component: ComponentInstanceId,
350 pub expected_inventory_hash: [u8; 32],
351}
352
353#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
360pub struct RootComponentDeletionStatusRequest {
361 pub operation_id: [u8; 32],
362 pub component: ComponentInstanceId,
363}
364
365#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
372pub struct RootComponentChildCreationRequest {
373 pub operation_id: [u8; 32],
374 pub component: ComponentInstanceId,
375}
376
377#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
384pub struct RootComponentChildInstallRequest {
385 pub operation_id: [u8; 32],
386 pub component: ComponentInstanceId,
387}
388
389#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
396pub struct RootComponentChildCommitRequest {
397 pub operation_id: [u8; 32],
398 pub component: ComponentInstanceId,
399}
400
401#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
408pub struct RootComponentChildDirectoryPreparationRequest {
409 pub operation_id: [u8; 32],
410 pub component: ComponentInstanceId,
411}
412
413#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
420pub struct RootComponentChildRuntimeActivationRequest {
421 pub operation_id: [u8; 32],
422 pub component: ComponentInstanceId,
423}
424
425#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
432pub struct RootComponentChildMembershipActivationRequest {
433 pub operation_id: [u8; 32],
434 pub component: ComponentInstanceId,
435}
436
437#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
444pub struct RootComponentCreationRequest {
445 pub operation_id: [u8; 32],
446}
447
448#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
455pub struct RootComponentInstallRequest {
456 pub operation_id: [u8; 32],
457}
458
459#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
466pub struct RootComponentCommitRequest {
467 pub operation_id: [u8; 32],
468}
469
470#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
477pub struct RootComponentDirectoryPreparationRequest {
478 pub operation_id: [u8; 32],
479}
480
481#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
488pub struct RootComponentRuntimeActivationRequest {
489 pub operation_id: [u8; 32],
490}
491
492#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
499pub struct RootComponentMembershipActivationRequest {
500 pub operation_id: [u8; 32],
501}
502
503#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub enum ComponentProvisioningOrigin {
511 FleetAdministrator { caller: Principal },
512}
513
514#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
521pub enum RootComponentAllocationPhase {
522 Reserved,
523 CreationIntent,
524 Created,
525 InstallIntent,
526 Installed,
527 Verified,
528 Committed,
529 Removed,
530}
531
532#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
539#[expect(
540 clippy::large_enum_variant,
541 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
542)]
543pub enum RootComponentSubtreeRemovalPhase {
544 Fenced,
545 Traversing(RootComponentSubtreeRemovalNode),
546 LeafSelected(RootComponentSubtreeRemovalNode),
547 StopIntent(RootComponentSubtreeRemovalStopIntent),
548 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
549 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
550 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
551 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
552 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
553 Completed(RootComponentSubtreeRemovalCompletedReceipt),
554}
555
556#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
563pub struct RootComponentSubtreeRemovalNode {
564 pub canister_id: Principal,
565 pub parent_canister_id: Principal,
566 pub role: CanisterRole,
567 pub kind: ComponentChildKind,
568 pub installed_artifact_hash: [u8; 32],
569 pub status: ComponentLifecycleStatus,
570}
571
572#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
579pub struct RootComponentSubtreeRemovalStopIntent {
580 pub leaf: RootComponentSubtreeRemovalNode,
581 pub controller: Principal,
582}
583
584#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
591pub struct RootComponentSubtreeRemovalStoppedReceipt {
592 pub stop: RootComponentSubtreeRemovalStopIntent,
593 pub observed_module_hash: [u8; 32],
594}
595
596#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603pub struct RootComponentSubtreeRemovalDeleteIntent {
604 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
605}
606
607#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
614pub struct RootComponentSubtreeRemovalDeletedReceipt {
615 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
616}
617
618#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
625pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
626 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
627 pub removed_from_registry: ComponentRegistryHead,
628 pub previous_descendant_content_hash: [u8; 32],
629 pub previous_committed_descendants: u32,
630 pub registry: ComponentRegistryHead,
631 pub descendant_content_hash: [u8; 32],
632 pub registry_encoded_bytes: u64,
633 pub reserved_descendants: u32,
634 pub committed_descendants: u32,
635 pub directory_synchronized_at_ns: u64,
636 pub directory_authority_hash: [u8; 32],
637 pub parent_role_instances: u32,
638 pub root_managed_descendants: u32,
639 pub root_known_created_component_canisters: u32,
640}
641
642#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
649pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
650 pub operation_id: [u8; 32],
651 pub canister_id: Principal,
652 pub activation: ComponentRuntimeActivationEvidence,
653}
654
655#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
664pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
665 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
666 pub covered_fleet_registry_revision: u64,
667 pub covered_fleet_registry_content_hash: [u8; 32],
668 pub covered_component_registry: ComponentRegistryHead,
669 pub covered_authority_hash: [u8; 32],
670 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
671 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
672}
673
674#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
681pub struct RootComponentSubtreeRemovalCompletedReceipt {
682 pub registry: ComponentRegistryHead,
683 pub directory_authority_hash: [u8; 32],
684}
685
686#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
693pub enum ComponentLifecycleStatus {
694 Prepared,
695 Active,
696 Draining,
697 Removed,
698}
699
700#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
707pub struct ComponentRegistryHead {
708 pub component: ComponentInstanceId,
709 pub revision: u64,
710 pub content_hash: [u8; 32],
711}
712
713#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
720pub struct ComponentRegistryPartitionRequest {
721 pub component: ComponentInstanceId,
722}
723
724#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
731pub struct ComponentRegistryPartitionResponse {
732 pub head: ComponentRegistryHead,
733 pub binding: ComponentBinding,
734 pub provisioning_origin: ComponentProvisioningOrigin,
735 pub release_set: FleetSubnetRootReleaseSet,
736 pub status: ComponentLifecycleStatus,
737 pub reserved_descendants: u32,
738 pub committed_descendants: u32,
739 pub encoded_bytes: u64,
740}
741
742#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
749pub struct ComponentDirectoryProvenance {
750 pub component: ComponentBinding,
751 pub source_fleet_subnet_root: Principal,
752 pub component_registry_revision: u64,
753 pub component_registry_content_hash: [u8; 32],
754 pub synchronized_at_ns: u64,
755}
756
757#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
764pub struct ComponentDirectoryHead {
765 pub provenance: ComponentDirectoryProvenance,
766 pub descendant_count: u32,
767}
768
769#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
776pub struct ComponentDirectoryHeadRequest {
777 pub component: ComponentInstanceId,
778}
779
780#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
787pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
788
789#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796pub struct ComponentDirectoryPageRequest {
797 pub directory: ComponentDirectoryHead,
798 pub parent_canister_id: Option<Principal>,
799 pub role: Option<CanisterRole>,
800 pub status: Option<ComponentLifecycleStatus>,
801 pub cursor: Option<ComponentDirectoryPageCursor>,
802 pub limit: u16,
803}
804
805#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
812pub struct ComponentDirectoryChildEntry {
813 pub binding: ComponentChildBinding,
814 pub kind: ComponentChildKind,
815 pub installed_artifact_hash: [u8; 32],
816 pub status: ComponentLifecycleStatus,
817}
818
819#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
826pub struct ComponentDirectoryPageResponse {
827 pub directory: ComponentDirectoryHead,
828 pub entries: Vec<ComponentDirectoryChildEntry>,
829 pub next_cursor: Option<ComponentDirectoryPageCursor>,
830}
831
832#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
839pub struct ComponentRuntimeDirectoryAuthority {
840 pub fleet: FleetDirectorySnapshot,
841 pub component: ComponentDirectoryHead,
842}
843
844#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
851pub struct ComponentRuntimeDirectoryPreparationRequest {
852 pub operation_id: [u8; 32],
853 pub authority: ComponentRuntimeDirectoryAuthority,
854}
855
856#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
863pub struct ComponentRuntimeDirectorySynchronizationRequest {
864 pub operation_id: [u8; 32],
865 pub authority: ComponentRuntimeDirectoryAuthority,
866}
867
868#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
875pub enum ComponentRuntimePhase {
876 AwaitingDirectory,
877 DirectoryPrepared,
878 Active,
879}
880
881#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
888pub struct ComponentRuntimeActivationEvidence {
889 pub directory_authority_hash: [u8; 32],
890 pub activated_at_ns: u64,
891}
892
893#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
900pub struct ComponentRuntimeActivationRequest {
901 pub operation_id: [u8; 32],
902 pub directory_authority_hash: [u8; 32],
903}
904
905#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
912pub struct ComponentRuntimeStatusResponse {
913 pub operation_id: [u8; 32],
914 pub binding: ManagedCanisterBinding,
915 pub phase: ComponentRuntimePhase,
916 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
917 pub authority_hash: Option<[u8; 32]>,
918 pub activation: Option<ComponentRuntimeActivationEvidence>,
919}
920
921#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
928pub struct ComponentRuntimeDirectoryConvergenceEvidence {
929 pub operation_id: [u8; 32],
930 pub binding: ManagedCanisterBinding,
931 pub covered_authority: ComponentRuntimeDirectoryAuthority,
932 pub covered_authority_hash: [u8; 32],
933 pub activation: ComponentRuntimeActivationEvidence,
934}
935
936#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
943pub struct RootComponentCreationEvidence {
944 pub wasm_store: Principal,
945 pub payload_hash: [u8; 32],
946 pub payload_size_bytes: u64,
947 pub initial_cycles: Cycles,
948 pub controller: Principal,
949 pub canister: Option<Principal>,
950}
951
952#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
959pub struct RootComponentInstallEvidence {
960 pub raw_module_hash: [u8; 32],
961 pub chunk_hashes: Vec<Vec<u8>>,
962 pub binding: ComponentBinding,
963}
964
965#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
972pub struct RootComponentChildInstallEvidence {
973 pub raw_module_hash: [u8; 32],
974 pub chunk_hashes: Vec<Vec<u8>>,
975 pub binding: ComponentChildBinding,
976}
977
978#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
985pub struct RootComponentAllocationResponse {
986 pub operation_id: [u8; 32],
987 pub allocation_sequence: u64,
988 pub component: ComponentInstanceId,
989 pub component_spec: ComponentSpecId,
990 pub spec_hash: [u8; 32],
991 pub role: CanisterRole,
992 pub provisioning_origin: ComponentProvisioningOrigin,
993 pub release_set: FleetSubnetRootReleaseSet,
994 pub phase: RootComponentAllocationPhase,
995 pub creation: Option<RootComponentCreationEvidence>,
996 pub installation: Option<RootComponentInstallEvidence>,
997}
998
999#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1006pub struct RootComponentChildAllocationResponse {
1007 pub operation_id: [u8; 32],
1008 pub component: ComponentInstanceId,
1009 pub parent_canister_id: Principal,
1010 pub parent_role: CanisterRole,
1011 pub child_role: CanisterRole,
1012 pub child_kind: ComponentChildKind,
1013 pub maximum_instances_per_parent: u32,
1014 pub maximum_descendants: u32,
1015 pub maximum_registry_bytes: u64,
1016 pub reserved_against_registry: ComponentRegistryHead,
1017 pub release_set: FleetSubnetRootReleaseSet,
1018 pub phase: RootComponentAllocationPhase,
1019 pub creation: Option<RootComponentCreationEvidence>,
1020 pub installation: Option<RootComponentChildInstallEvidence>,
1021}
1022
1023#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030pub struct RootComponentSubtreeRemovalResponse {
1031 pub operation_id: [u8; 32],
1032 pub component: ComponentInstanceId,
1033 pub target_canister_id: Principal,
1034 pub target_parent_canister_id: Principal,
1035 pub target_role: CanisterRole,
1036 pub target_status: ComponentLifecycleStatus,
1037 pub reserved_against_registry: ComponentRegistryHead,
1038 pub maximum_completed_leaves: u32,
1039 pub completed_leaves: u32,
1040 pub traversal_steps: u32,
1041 pub phase: RootComponentSubtreeRemovalPhase,
1042}
1043
1044#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1051pub struct RootComponentDrainingResponse {
1052 pub operation_id: [u8; 32],
1053 pub component: ComponentInstanceId,
1054 pub previous_registry: ComponentRegistryHead,
1055 pub registry: ComponentRegistryHead,
1056 pub descendant_count: u32,
1057 pub descendant_content_hash: [u8; 32],
1058 pub directory_authority_hash: [u8; 32],
1059 pub started_at_ns: u64,
1060}
1061
1062#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1069pub struct RootComponentQuiescenceStopIntent {
1070 pub registry: ComponentRegistryHead,
1071 pub descendant_count: u32,
1072 pub descendant_content_hash: [u8; 32],
1073 pub canister_id: Principal,
1074 pub controller: Principal,
1075 pub expected_module_hash: [u8; 32],
1076 pub covered_fleet_registry_revision: u64,
1077 pub covered_fleet_registry_content_hash: [u8; 32],
1078 pub covered_authority_hash: [u8; 32],
1079 pub runtime_operation_id: [u8; 32],
1080 pub activation: ComponentRuntimeActivationEvidence,
1081 pub prepared_at_ns: u64,
1082}
1083
1084#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1091pub struct RootComponentQuiescentReceipt {
1092 pub stop: RootComponentQuiescenceStopIntent,
1093 pub observed_module_hash: [u8; 32],
1094 pub quiesced_at_ns: u64,
1095}
1096
1097#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1104pub enum RootComponentQuiescencePhase {
1105 StopIntent(RootComponentQuiescenceStopIntent),
1106 Quiescent(RootComponentQuiescentReceipt),
1107}
1108
1109#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1116pub struct RootComponentQuiescenceResponse {
1117 pub operation_id: [u8; 32],
1118 pub component: ComponentInstanceId,
1119 pub phase: RootComponentQuiescencePhase,
1120}
1121
1122#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1129pub struct RootComponentDrainingDescendantsEmpty {
1130 pub registry: ComponentRegistryHead,
1131 pub descendant_content_hash: [u8; 32],
1132}
1133
1134#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1141#[expect(
1142 clippy::large_enum_variant,
1143 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1144)]
1145pub enum RootComponentDrainingAdvancePhase {
1146 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1147 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1148}
1149
1150#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1157pub struct RootComponentDrainingAdvanceResponse {
1158 pub operation_id: [u8; 32],
1159 pub component: ComponentInstanceId,
1160 pub phase: RootComponentDrainingAdvancePhase,
1161}
1162
1163#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1170pub struct RootComponentFinalInventory {
1171 pub registry: ComponentRegistryHead,
1172 pub descendant_content_hash: [u8; 32],
1173 pub registry_encoded_bytes: u64,
1174 pub directory_synchronized_at_ns: u64,
1175 pub covered_fleet_registry_revision: u64,
1176 pub covered_fleet_registry_content_hash: [u8; 32],
1177 pub directory_authority_hash: [u8; 32],
1178 pub inventory_hash: [u8; 32],
1179 pub finalized_at_ns: u64,
1180}
1181
1182#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1189pub struct RootComponentFinalInventoryResponse {
1190 pub operation_id: [u8; 32],
1191 pub component: ComponentInstanceId,
1192 pub inventory: RootComponentFinalInventory,
1193}
1194
1195#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1202pub struct RootComponentDeletionIntent {
1203 pub final_inventory: RootComponentFinalInventory,
1204 pub quiescence: RootComponentQuiescentReceipt,
1205 pub prepared_at_ns: u64,
1206}
1207
1208#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1215pub struct RootComponentDeletedReceipt {
1216 pub deletion: RootComponentDeletionIntent,
1217 pub deleted_at_ns: u64,
1218}
1219
1220#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1227pub struct RootComponentMembershipRemovedReceipt {
1228 pub deleted: RootComponentDeletedReceipt,
1229 pub allocation_operation_id: [u8; 32],
1230 pub remaining_spec_committed_instances: u32,
1231 pub root_committed_component_instances: u32,
1232 pub root_known_created_component_canisters: u32,
1233 pub root_registry_encoded_bytes: u64,
1234 pub removed_at_ns: u64,
1235 pub removal_hash: [u8; 32],
1236}
1237
1238#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1245pub enum RootComponentDeletionPhase {
1246 DeleteIntent(RootComponentDeletionIntent),
1247 Deleted(RootComponentDeletedReceipt),
1248 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1249}
1250
1251#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1258pub struct RootComponentDeletionResponse {
1259 pub operation_id: [u8; 32],
1260 pub component: ComponentInstanceId,
1261 pub phase: RootComponentDeletionPhase,
1262}
1263
1264#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1271pub struct RootComponentChildCommitResponse {
1272 pub allocation: RootComponentChildAllocationResponse,
1273 pub registry: ComponentRegistryPartitionResponse,
1274 pub directory: ComponentDirectoryHead,
1275}
1276
1277#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1284pub struct RootComponentChildDirectoryPreparationResponse {
1285 pub committed: RootComponentChildCommitResponse,
1286 pub child: ComponentRuntimeStatusResponse,
1287 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1288 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1289}
1290
1291#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1298pub struct RootComponentChildRuntimeActivationResponse {
1299 pub committed: RootComponentChildCommitResponse,
1300 pub child: ComponentRuntimeStatusResponse,
1301}
1302
1303#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1310pub struct RootComponentChildMembershipActivationResponse {
1311 pub committed: RootComponentChildCommitResponse,
1312 pub registry: ComponentRegistryPartitionResponse,
1313 pub directory: ComponentDirectoryHead,
1314 pub child: ComponentRuntimeStatusResponse,
1315}
1316
1317#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1324pub struct RootComponentCommitResponse {
1325 pub allocation: RootComponentAllocationResponse,
1326 pub registry: ComponentRegistryPartitionResponse,
1327 pub directory: ComponentDirectoryHead,
1328}
1329
1330#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1337pub struct RootComponentDirectoryPreparationResponse {
1338 pub committed: RootComponentCommitResponse,
1339 pub target: ComponentRuntimeStatusResponse,
1340}
1341
1342#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1349pub struct RootComponentRuntimeActivationResponse {
1350 pub committed: RootComponentCommitResponse,
1351 pub target: ComponentRuntimeStatusResponse,
1352}
1353
1354#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1361pub struct RootComponentMembershipActivationResponse {
1362 pub allocation: RootComponentAllocationResponse,
1363 pub registry: ComponentRegistryPartitionResponse,
1364 pub directory: ComponentDirectoryHead,
1365 pub target: ComponentRuntimeStatusResponse,
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::*;
1371 use crate::{
1372 dto::root_store::RootStoreBootstrapRequest,
1373 ids::{
1374 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1375 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1376 },
1377 };
1378
1379 #[test]
1380 fn component_registry_contracts_round_trip_through_candid() {
1381 let request = RootComponentRegistryPreparationRequest {
1382 store_bootstrap: RootStoreBootstrapRequest {
1383 manifest_payload_size_bytes: 128,
1384 },
1385 expected_fleet_registry: FleetRegistryVersion {
1386 authority: fleet_registry_authority(),
1387 revision: 4,
1388 content_hash: [5; 32],
1389 },
1390 };
1391 let response = RootComponentRegistryStatusResponse {
1392 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1393 prepared_against_registry: request.expected_fleet_registry.clone(),
1394 release_set: FleetSubnetRootReleaseSet {
1395 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1396 [7; 32],
1397 )),
1398 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1399 },
1400 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1401 next_allocation_sequence: 1,
1402 reserved_component_instances: 0,
1403 committed_component_instances: 0,
1404 managed_descendants: 0,
1405 known_created_component_canisters: 0,
1406 encoded_bytes: 0,
1407 initial_inventory: Some(RootComponentInitialInventoryStatus {
1408 fleet_activation_operation_id: [10; 32],
1409 component_count: 0,
1410 inventory_hash: [11; 32],
1411 sealed_at_ns: 12,
1412 directories_converged: true,
1413 root_runtime_activated: true,
1414 }),
1415 };
1416 let allocation = RootComponentAllocationResponse {
1417 operation_id: [10; 32],
1418 allocation_sequence: 1,
1419 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1420 component_spec: "projects".parse().expect("Component Spec ID"),
1421 spec_hash: [12; 32],
1422 role: CanisterRole::new("project_hub"),
1423 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1424 caller: Principal::from_slice(&[13; 29]),
1425 },
1426 release_set: response.release_set,
1427 phase: RootComponentAllocationPhase::Reserved,
1428 creation: None,
1429 installation: None,
1430 };
1431 let created = RootComponentAllocationResponse {
1432 phase: RootComponentAllocationPhase::Created,
1433 creation: Some(RootComponentCreationEvidence {
1434 wasm_store: Principal::from_slice(&[14; 29]),
1435 payload_hash: [15; 32],
1436 payload_size_bytes: 4_096,
1437 initial_cycles: Cycles::new(5_000_000_000_000),
1438 controller: Principal::from_slice(&[6; 29]),
1439 canister: Some(Principal::from_slice(&[16; 29])),
1440 }),
1441 installation: None,
1442 ..allocation.clone()
1443 };
1444 let request_bytes = candid::encode_one(&request).expect("encode request");
1445 let response_bytes = candid::encode_one(&response).expect("encode response");
1446 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1447 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1448
1449 assert_eq!(
1450 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1451 .expect("decode request"),
1452 request
1453 );
1454 assert_eq!(
1455 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1456 .expect("decode response"),
1457 response
1458 );
1459 assert_eq!(
1460 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1461 .expect("decode allocation"),
1462 allocation
1463 );
1464 assert_eq!(
1465 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1466 .expect("decode created allocation"),
1467 created
1468 );
1469 }
1470
1471 #[test]
1472 fn component_commit_response_round_trips_through_candid() {
1473 let root = Principal::from_slice(&[6; 29]);
1474 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1475 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1476 let release_set = FleetSubnetRootReleaseSet {
1477 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1478 [7; 32],
1479 )),
1480 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1481 };
1482 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1483 caller: Principal::from_slice(&[13; 29]),
1484 };
1485 let binding = ComponentBinding {
1486 authority: fleet_registry_authority(),
1487 component,
1488 component_spec: component_spec.clone(),
1489 spec_hash: [12; 32],
1490 role: CanisterRole::new("project_hub"),
1491 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1492 fleet_subnet_root: root,
1493 canister_id: Principal::from_slice(&[16; 29]),
1494 };
1495 let head = ComponentRegistryHead {
1496 component,
1497 revision: 1,
1498 content_hash: [18; 32],
1499 };
1500 let committed = RootComponentCommitResponse {
1501 allocation: RootComponentAllocationResponse {
1502 operation_id: [10; 32],
1503 allocation_sequence: 1,
1504 component,
1505 component_spec,
1506 spec_hash: binding.spec_hash,
1507 role: binding.role.clone(),
1508 provisioning_origin: provisioning_origin.clone(),
1509 release_set,
1510 phase: RootComponentAllocationPhase::Committed,
1511 creation: Some(RootComponentCreationEvidence {
1512 wasm_store: Principal::from_slice(&[14; 29]),
1513 payload_hash: [15; 32],
1514 payload_size_bytes: 4_096,
1515 initial_cycles: Cycles::new(5_000_000_000_000),
1516 controller: root,
1517 canister: Some(binding.canister_id),
1518 }),
1519 installation: Some(RootComponentInstallEvidence {
1520 raw_module_hash: [20; 32],
1521 chunk_hashes: vec![vec![21; 32]],
1522 binding: binding.clone(),
1523 }),
1524 },
1525 registry: ComponentRegistryPartitionResponse {
1526 head: head.clone(),
1527 binding: binding.clone(),
1528 provisioning_origin,
1529 release_set,
1530 status: ComponentLifecycleStatus::Prepared,
1531 reserved_descendants: 0,
1532 committed_descendants: 0,
1533 encoded_bytes: 2_048,
1534 },
1535 directory: ComponentDirectoryHead {
1536 provenance: ComponentDirectoryProvenance {
1537 component: binding,
1538 source_fleet_subnet_root: root,
1539 component_registry_revision: head.revision,
1540 component_registry_content_hash: head.content_hash,
1541 synchronized_at_ns: 19,
1542 },
1543 descendant_count: 0,
1544 },
1545 };
1546 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1547
1548 assert_eq!(
1549 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1550 .expect("decode committed allocation"),
1551 committed
1552 );
1553 }
1554
1555 #[test]
1556 fn component_directory_page_contracts_round_trip_through_candid() {
1557 let root = Principal::from_slice(&[6; 29]);
1558 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1559 let binding = ComponentBinding {
1560 authority: fleet_registry_authority(),
1561 component,
1562 component_spec: "projects".parse().expect("Component Spec ID"),
1563 spec_hash: [12; 32],
1564 role: CanisterRole::new("project_hub"),
1565 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1566 fleet_subnet_root: root,
1567 canister_id: Principal::from_slice(&[16; 29]),
1568 };
1569 let directory = ComponentDirectoryHead {
1570 provenance: ComponentDirectoryProvenance {
1571 component: binding.clone(),
1572 source_fleet_subnet_root: root,
1573 component_registry_revision: 3,
1574 component_registry_content_hash: [18; 32],
1575 synchronized_at_ns: 19,
1576 },
1577 descendant_count: 1,
1578 };
1579 let request = ComponentDirectoryPageRequest {
1580 directory: directory.clone(),
1581 parent_canister_id: Some(binding.canister_id),
1582 role: Some(CanisterRole::new("project_instance")),
1583 status: Some(ComponentLifecycleStatus::Active),
1584 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1585 limit: 50,
1586 };
1587 let response = ComponentDirectoryPageResponse {
1588 directory,
1589 entries: vec![ComponentDirectoryChildEntry {
1590 binding: ComponentChildBinding {
1591 component: binding.clone(),
1592 parent_canister_id: binding.canister_id,
1593 role: CanisterRole::new("project_instance"),
1594 canister_id: Principal::from_slice(&[21; 29]),
1595 },
1596 kind: ComponentChildKind::Instance,
1597 installed_artifact_hash: [22; 32],
1598 status: ComponentLifecycleStatus::Active,
1599 }],
1600 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1601 };
1602 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1603 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1604
1605 assert_eq!(
1606 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1607 .expect("decode Directory page request"),
1608 request
1609 );
1610 assert_eq!(
1611 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1612 .expect("decode Directory page response"),
1613 response
1614 );
1615 }
1616
1617 fn fleet_registry_authority() -> FleetRegistryAuthority {
1618 FleetRegistryAuthority {
1619 binding: FleetCoordinatorBinding {
1620 fleet: crate::ids::FleetBinding {
1621 fleet: FleetKey {
1622 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1623 fleet_id: FleetId::from_generated_bytes([1; 32]),
1624 },
1625 app: AppId::from("toko"),
1626 },
1627 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1628 coordinator: Principal::from_slice(&[3; 29]),
1629 },
1630 epoch: 1,
1631 }
1632 }
1633
1634 #[test]
1635 fn component_creation_request_round_trips_through_candid() {
1636 let request = RootComponentCreationRequest {
1637 operation_id: [10; 32],
1638 };
1639 let bytes = candid::encode_one(request).expect("encode creation request");
1640
1641 assert_eq!(
1642 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1643 .expect("decode creation request"),
1644 request
1645 );
1646 }
1647
1648 #[test]
1649 #[expect(
1650 clippy::too_many_lines,
1651 reason = "one Candid contract test covers every subtree-removal phase receipt"
1652 )]
1653 fn component_subtree_removal_contracts_round_trip_through_candid() {
1654 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1655 let registry = ComponentRegistryHead {
1656 component,
1657 revision: 7,
1658 content_hash: [42; 32],
1659 };
1660 let request = RootComponentSubtreeRemovalRequest {
1661 operation_id: [43; 32],
1662 component,
1663 target_canister_id: Principal::from_slice(&[44; 29]),
1664 expected_registry: registry.clone(),
1665 };
1666 let status_request = RootComponentSubtreeRemovalStatusRequest {
1667 operation_id: request.operation_id,
1668 component,
1669 };
1670 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1671 operation_id: request.operation_id,
1672 component,
1673 expected_traversal_steps: 1,
1674 };
1675 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1676 operation_id: request.operation_id,
1677 component,
1678 expected_traversal_steps: 2,
1679 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1680 expected_leaf_parent_canister_id: request.target_canister_id,
1681 };
1682 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1683 observed_module_hash: [49; 32],
1684 stop: RootComponentSubtreeRemovalStopIntent {
1685 controller: Principal::from_slice(&[48; 29]),
1686 leaf: RootComponentSubtreeRemovalNode {
1687 canister_id: Principal::from_slice(&[46; 29]),
1688 parent_canister_id: request.target_canister_id,
1689 role: CanisterRole::new("project_ledger"),
1690 kind: ComponentChildKind::Singleton,
1691 installed_artifact_hash: [47; 32],
1692 status: ComponentLifecycleStatus::Active,
1693 },
1694 },
1695 };
1696 let response = RootComponentSubtreeRemovalResponse {
1697 operation_id: request.operation_id,
1698 component,
1699 target_canister_id: request.target_canister_id,
1700 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1701 target_role: CanisterRole::new("project_instance"),
1702 target_status: ComponentLifecycleStatus::Active,
1703 reserved_against_registry: registry,
1704 maximum_completed_leaves: 4,
1705 completed_leaves: 1,
1706 traversal_steps: 2,
1707 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1708 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1709 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1710 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1711 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1712 },
1713 removed_from_registry: ComponentRegistryHead {
1714 component,
1715 revision: 8,
1716 content_hash: [50; 32],
1717 },
1718 previous_descendant_content_hash: [51; 32],
1719 previous_committed_descendants: 4,
1720 registry: ComponentRegistryHead {
1721 component,
1722 revision: 9,
1723 content_hash: [52; 32],
1724 },
1725 descendant_content_hash: [53; 32],
1726 registry_encoded_bytes: 4_096,
1727 reserved_descendants: 1,
1728 committed_descendants: 3,
1729 directory_synchronized_at_ns: 54,
1730 directory_authority_hash: [55; 32],
1731 parent_role_instances: 0,
1732 root_managed_descendants: 4,
1733 root_known_created_component_canisters: 4,
1734 },
1735 covered_fleet_registry_revision: 6,
1736 covered_fleet_registry_content_hash: [56; 32],
1737 covered_component_registry: ComponentRegistryHead {
1738 component,
1739 revision: 9,
1740 content_hash: [52; 32],
1741 },
1742 covered_authority_hash: [55; 32],
1743 owning_component: Some(
1744 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1745 operation_id: [57; 32],
1746 canister_id: Principal::from_slice(&[58; 29]),
1747 activation: ComponentRuntimeActivationEvidence {
1748 directory_authority_hash: [59; 32],
1749 activated_at_ns: 60,
1750 },
1751 },
1752 ),
1753 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1754 operation_id: [61; 32],
1755 canister_id: request.target_canister_id,
1756 activation: ComponentRuntimeActivationEvidence {
1757 directory_authority_hash: [62; 32],
1758 activated_at_ns: 63,
1759 },
1760 }),
1761 },
1762 ),
1763 };
1764
1765 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1766 let advance_bytes =
1767 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1768 let stop_bytes =
1769 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1770 let status_bytes =
1771 candid::encode_one(status_request).expect("encode subtree removal status request");
1772 let response_bytes =
1773 candid::encode_one(&response).expect("encode subtree removal response");
1774
1775 assert_eq!(
1776 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1777 .expect("decode subtree removal request"),
1778 request
1779 );
1780 assert_eq!(
1781 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1782 .expect("decode subtree removal advance request"),
1783 advance_request
1784 );
1785 assert_eq!(
1786 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1787 .expect("decode subtree removal stop request"),
1788 stop_request
1789 );
1790 assert_eq!(
1791 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1792 .expect("decode subtree removal status request"),
1793 status_request
1794 );
1795 assert_eq!(
1796 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1797 .expect("decode subtree removal response"),
1798 response
1799 );
1800
1801 let mut quiescent_owner_response = response;
1802 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
1803 &mut quiescent_owner_response.phase
1804 else {
1805 panic!("Directory-synchronized response");
1806 };
1807 receipt.owning_component = None;
1808 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
1809 .expect("encode quiescent-owner subtree response");
1810 assert_eq!(
1811 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
1812 .expect("decode quiescent-owner subtree response"),
1813 quiescent_owner_response
1814 );
1815 }
1816
1817 #[test]
1818 fn component_draining_contracts_round_trip_through_candid() {
1819 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1820 let previous_registry = ComponentRegistryHead {
1821 component,
1822 revision: 7,
1823 content_hash: [61; 32],
1824 };
1825 let request = RootComponentDrainingRequest {
1826 operation_id: [62; 32],
1827 component,
1828 expected_registry: previous_registry.clone(),
1829 };
1830 let status_request = RootComponentDrainingStatusRequest {
1831 operation_id: request.operation_id,
1832 component,
1833 };
1834 let response = RootComponentDrainingResponse {
1835 operation_id: request.operation_id,
1836 component,
1837 previous_registry,
1838 registry: ComponentRegistryHead {
1839 component,
1840 revision: 8,
1841 content_hash: [63; 32],
1842 },
1843 descendant_count: 20_000,
1844 descendant_content_hash: [64; 32],
1845 directory_authority_hash: [65; 32],
1846 started_at_ns: 66,
1847 };
1848
1849 let request_bytes =
1850 candid::encode_one(&request).expect("encode Component draining request");
1851 let status_bytes =
1852 candid::encode_one(status_request).expect("encode Component draining status request");
1853 let response_bytes =
1854 candid::encode_one(&response).expect("encode Component draining response");
1855
1856 assert_eq!(
1857 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1858 .expect("decode Component draining request"),
1859 request
1860 );
1861 assert_eq!(
1862 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1863 .expect("decode Component draining status request"),
1864 status_request
1865 );
1866 assert_eq!(
1867 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1868 .expect("decode Component draining response"),
1869 response
1870 );
1871 }
1872
1873 #[test]
1874 fn component_quiescence_contracts_round_trip_through_candid() {
1875 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
1876 let registry = ComponentRegistryHead {
1877 component,
1878 revision: 9,
1879 content_hash: [68; 32],
1880 };
1881 let request = RootComponentQuiescenceRequest {
1882 operation_id: [69; 32],
1883 component,
1884 expected_registry: registry.clone(),
1885 };
1886 let status_request = RootComponentQuiescenceStatusRequest {
1887 operation_id: request.operation_id,
1888 component,
1889 };
1890 let stop = RootComponentQuiescenceStopIntent {
1891 registry,
1892 descendant_count: 20_000,
1893 descendant_content_hash: [70; 32],
1894 canister_id: Principal::from_slice(&[71; 29]),
1895 controller: Principal::from_slice(&[72; 29]),
1896 expected_module_hash: [73; 32],
1897 covered_fleet_registry_revision: 10,
1898 covered_fleet_registry_content_hash: [74; 32],
1899 covered_authority_hash: [75; 32],
1900 runtime_operation_id: [76; 32],
1901 activation: ComponentRuntimeActivationEvidence {
1902 directory_authority_hash: [77; 32],
1903 activated_at_ns: 78,
1904 },
1905 prepared_at_ns: 79,
1906 };
1907 let response = RootComponentQuiescenceResponse {
1908 operation_id: request.operation_id,
1909 component,
1910 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
1911 stop,
1912 observed_module_hash: [73; 32],
1913 quiesced_at_ns: 80,
1914 }),
1915 };
1916
1917 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
1918 let status_bytes =
1919 candid::encode_one(status_request).expect("encode quiescence status request");
1920 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
1921 assert_eq!(
1922 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
1923 .expect("decode quiescence request"),
1924 request
1925 );
1926 assert_eq!(
1927 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
1928 .expect("decode quiescence status request"),
1929 status_request
1930 );
1931 assert_eq!(
1932 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
1933 .expect("decode quiescence response"),
1934 response
1935 );
1936 }
1937
1938 #[test]
1939 fn component_draining_advance_contracts_round_trip_through_candid() {
1940 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
1941 let registry = ComponentRegistryHead {
1942 component,
1943 revision: 12,
1944 content_hash: [82; 32],
1945 };
1946 let request = RootComponentDrainingAdvanceRequest {
1947 operation_id: [83; 32],
1948 component,
1949 };
1950 let descendant_removal = RootComponentDrainingAdvanceResponse {
1951 operation_id: request.operation_id,
1952 component,
1953 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
1954 RootComponentSubtreeRemovalResponse {
1955 operation_id: [84; 32],
1956 component,
1957 target_canister_id: Principal::from_slice(&[85; 29]),
1958 target_parent_canister_id: Principal::from_slice(&[86; 29]),
1959 target_role: CanisterRole::new("project_instance"),
1960 target_status: ComponentLifecycleStatus::Active,
1961 reserved_against_registry: registry.clone(),
1962 maximum_completed_leaves: 20_000,
1963 completed_leaves: 0,
1964 traversal_steps: 0,
1965 phase: RootComponentSubtreeRemovalPhase::Fenced,
1966 },
1967 ),
1968 };
1969 let descendants_empty = RootComponentDrainingAdvanceResponse {
1970 operation_id: request.operation_id,
1971 component,
1972 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
1973 RootComponentDrainingDescendantsEmpty {
1974 registry,
1975 descendant_content_hash: [87; 32],
1976 },
1977 ),
1978 };
1979
1980 let request_bytes =
1981 candid::encode_one(request).expect("encode Component draining advance request");
1982 let removal_bytes = candid::encode_one(&descendant_removal)
1983 .expect("encode Component draining removal response");
1984 let empty_bytes = candid::encode_one(&descendants_empty)
1985 .expect("encode Component draining empty response");
1986
1987 assert_eq!(
1988 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
1989 .expect("decode Component draining advance request"),
1990 request
1991 );
1992 assert_eq!(
1993 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
1994 .expect("decode Component draining removal response"),
1995 descendant_removal
1996 );
1997 assert_eq!(
1998 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
1999 .expect("decode Component draining empty response"),
2000 descendants_empty
2001 );
2002 }
2003
2004 #[test]
2005 #[expect(
2006 clippy::too_many_lines,
2007 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2008 )]
2009 fn component_final_inventory_contracts_round_trip_through_candid() {
2010 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2011 let registry = ComponentRegistryHead {
2012 component,
2013 revision: 21,
2014 content_hash: [89; 32],
2015 };
2016 let request = RootComponentFinalInventoryRequest {
2017 operation_id: [90; 32],
2018 component,
2019 expected_registry: registry.clone(),
2020 };
2021 let inventory = RootComponentFinalInventory {
2022 registry,
2023 descendant_content_hash: [91; 32],
2024 registry_encoded_bytes: 4_096,
2025 directory_synchronized_at_ns: 92,
2026 covered_fleet_registry_revision: 93,
2027 covered_fleet_registry_content_hash: [94; 32],
2028 directory_authority_hash: [95; 32],
2029 inventory_hash: [96; 32],
2030 finalized_at_ns: 97,
2031 };
2032 let response = RootComponentFinalInventoryResponse {
2033 operation_id: request.operation_id,
2034 component,
2035 inventory: inventory.clone(),
2036 };
2037 let deletion_request = RootComponentDeletionRequest {
2038 operation_id: request.operation_id,
2039 component,
2040 expected_inventory_hash: inventory.inventory_hash,
2041 };
2042 let deletion_status_request = RootComponentDeletionStatusRequest {
2043 operation_id: request.operation_id,
2044 component,
2045 };
2046 let deletion = RootComponentDeletionIntent {
2047 final_inventory: inventory,
2048 quiescence: RootComponentQuiescentReceipt {
2049 stop: RootComponentQuiescenceStopIntent {
2050 registry: response.inventory.registry.clone(),
2051 descendant_count: 0,
2052 descendant_content_hash: response.inventory.descendant_content_hash,
2053 canister_id: Principal::from_slice(&[98; 29]),
2054 controller: Principal::from_slice(&[99; 29]),
2055 expected_module_hash: [100; 32],
2056 covered_fleet_registry_revision: 93,
2057 covered_fleet_registry_content_hash: [94; 32],
2058 covered_authority_hash: [101; 32],
2059 runtime_operation_id: [102; 32],
2060 activation: ComponentRuntimeActivationEvidence {
2061 directory_authority_hash: [103; 32],
2062 activated_at_ns: 104,
2063 },
2064 prepared_at_ns: 105,
2065 },
2066 observed_module_hash: [100; 32],
2067 quiesced_at_ns: 106,
2068 },
2069 prepared_at_ns: 107,
2070 };
2071 let deleted_receipt = RootComponentDeletedReceipt {
2072 deletion,
2073 deleted_at_ns: 108,
2074 };
2075 let deletion_response = RootComponentDeletionResponse {
2076 operation_id: request.operation_id,
2077 component,
2078 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2079 };
2080 let membership_removed_response = RootComponentDeletionResponse {
2081 operation_id: request.operation_id,
2082 component,
2083 phase: RootComponentDeletionPhase::MembershipRemoved(
2084 RootComponentMembershipRemovedReceipt {
2085 deleted: deleted_receipt,
2086 allocation_operation_id: [109; 32],
2087 remaining_spec_committed_instances: 2,
2088 root_committed_component_instances: 3,
2089 root_known_created_component_canisters: 4,
2090 root_registry_encoded_bytes: 5_000,
2091 removed_at_ns: 110,
2092 removal_hash: [111; 32],
2093 },
2094 ),
2095 };
2096
2097 let request_bytes =
2098 candid::encode_one(&request).expect("encode Component final inventory request");
2099 let response_bytes =
2100 candid::encode_one(&response).expect("encode Component final inventory response");
2101 let deletion_request_bytes =
2102 candid::encode_one(deletion_request).expect("encode Component deletion request");
2103 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2104 .expect("encode Component deletion status request");
2105 let deletion_response_bytes =
2106 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2107 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2108 .expect("encode Component membership-removal response");
2109 assert_eq!(
2110 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2111 .expect("decode Component final inventory request"),
2112 request
2113 );
2114 assert_eq!(
2115 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2116 .expect("decode Component final inventory response"),
2117 response
2118 );
2119 assert_eq!(
2120 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2121 .expect("decode Component deletion request"),
2122 deletion_request
2123 );
2124 assert_eq!(
2125 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2126 .expect("decode Component deletion status request"),
2127 deletion_status_request
2128 );
2129 assert_eq!(
2130 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2131 .expect("decode Component deletion response"),
2132 deletion_response
2133 );
2134 assert_eq!(
2135 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2136 .expect("decode Component membership-removal response"),
2137 membership_removed_response
2138 );
2139 }
2140
2141 #[test]
2142 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2143 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2144 operation_id: [50; 32],
2145 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2146 expected_traversal_steps: 3,
2147 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2148 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2149 };
2150 let request = RootComponentSubtreeRemovalDeleteRequest {
2151 operation_id: prepare.operation_id,
2152 component: prepare.component,
2153 expected_traversal_steps: prepare.expected_traversal_steps,
2154 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2155 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2156 };
2157 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2158 operation_id: prepare.operation_id,
2159 component: prepare.component,
2160 expected_traversal_steps: prepare.expected_traversal_steps,
2161 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2162 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2163 };
2164 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2165 operation_id: prepare.operation_id,
2166 component: prepare.component,
2167 expected_traversal_steps: prepare.expected_traversal_steps,
2168 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2169 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2170 };
2171 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2172 operation_id: prepare.operation_id,
2173 component: prepare.component,
2174 expected_traversal_steps: prepare.expected_traversal_steps,
2175 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2176 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2177 };
2178 let prepare_bytes = candid::encode_one(prepare)
2179 .expect("encode subtree removal deletion preparation request");
2180 let request_bytes =
2181 candid::encode_one(request).expect("encode subtree removal deletion request");
2182 let membership_request_bytes = candid::encode_one(membership_request)
2183 .expect("encode subtree removal membership-removal request");
2184 let directory_request_bytes = candid::encode_one(directory_request)
2185 .expect("encode subtree removal Directory synchronization request");
2186 let finalization_request_bytes = candid::encode_one(finalization_request)
2187 .expect("encode subtree removal leaf-finalization request");
2188
2189 assert_eq!(
2190 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2191 &prepare_bytes
2192 )
2193 .expect("decode subtree removal deletion preparation request"),
2194 prepare
2195 );
2196 assert_eq!(
2197 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2198 .expect("decode subtree removal deletion request"),
2199 request
2200 );
2201 assert_eq!(
2202 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2203 &membership_request_bytes
2204 )
2205 .expect("decode subtree removal membership-removal request"),
2206 membership_request
2207 );
2208 assert_eq!(
2209 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2210 &directory_request_bytes
2211 )
2212 .expect("decode subtree removal Directory synchronization request"),
2213 directory_request
2214 );
2215 assert_eq!(
2216 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2217 &finalization_request_bytes
2218 )
2219 .expect("decode subtree removal leaf-finalization request"),
2220 finalization_request
2221 );
2222 }
2223
2224 #[test]
2225 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2226 let request = RootComponentSubtreeRemovalStopRequest {
2227 operation_id: [50; 32],
2228 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2229 expected_traversal_steps: 3,
2230 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2231 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2232 };
2233 let bytes =
2234 candid::encode_one(request).expect("encode subtree removal stop execution request");
2235
2236 assert_eq!(
2237 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2238 .expect("decode subtree removal stop execution request"),
2239 request
2240 );
2241 }
2242
2243 #[test]
2244 #[expect(
2245 clippy::too_many_lines,
2246 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2247 )]
2248 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2249 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2250 let registry = ComponentRegistryHead {
2251 component,
2252 revision: 2,
2253 content_hash: [12; 32],
2254 };
2255 let request = RootComponentChildAllocationRequest {
2256 operation_id: [13; 32],
2257 component,
2258 expected_registry: registry.clone(),
2259 child_role: CanisterRole::new("project_instance"),
2260 };
2261 let status_request = RootComponentChildAllocationStatusRequest {
2262 operation_id: request.operation_id,
2263 component,
2264 };
2265 let creation_request = RootComponentChildCreationRequest {
2266 operation_id: request.operation_id,
2267 component,
2268 };
2269 let install_request = RootComponentChildInstallRequest {
2270 operation_id: request.operation_id,
2271 component,
2272 };
2273 let commit_request = RootComponentChildCommitRequest {
2274 operation_id: request.operation_id,
2275 component,
2276 };
2277 let directory_request = RootComponentChildDirectoryPreparationRequest {
2278 operation_id: request.operation_id,
2279 component,
2280 };
2281 let activation_request = RootComponentChildRuntimeActivationRequest {
2282 operation_id: request.operation_id,
2283 component,
2284 };
2285 let membership_request = RootComponentChildMembershipActivationRequest {
2286 operation_id: request.operation_id,
2287 component,
2288 };
2289 let root = Principal::from_slice(&[17; 29]);
2290 let parent = Principal::from_slice(&[14; 29]);
2291 let child = Principal::from_slice(&[18; 29]);
2292 let child_binding = ComponentChildBinding {
2293 component: ComponentBinding {
2294 authority: fleet_registry_authority(),
2295 component,
2296 component_spec: "projects".parse().expect("Component Spec"),
2297 spec_hash: [19; 32],
2298 role: CanisterRole::new("project_hub"),
2299 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2300 fleet_subnet_root: root,
2301 canister_id: parent,
2302 },
2303 parent_canister_id: parent,
2304 role: request.child_role.clone(),
2305 canister_id: child,
2306 };
2307 let response = RootComponentChildAllocationResponse {
2308 operation_id: request.operation_id,
2309 component,
2310 parent_canister_id: parent,
2311 parent_role: CanisterRole::new("project_hub"),
2312 child_role: request.child_role.clone(),
2313 child_kind: ComponentChildKind::Instance,
2314 maximum_instances_per_parent: 10_000,
2315 maximum_descendants: 20_000,
2316 maximum_registry_bytes: 16_777_216,
2317 reserved_against_registry: registry,
2318 release_set: FleetSubnetRootReleaseSet {
2319 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2320 [15; 32],
2321 )),
2322 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2323 },
2324 phase: RootComponentAllocationPhase::Verified,
2325 creation: Some(RootComponentCreationEvidence {
2326 wasm_store: Principal::from_slice(&[21; 29]),
2327 payload_hash: [22; 32],
2328 payload_size_bytes: 4_096,
2329 initial_cycles: Cycles::new(5_000_000_000_000),
2330 controller: root,
2331 canister: Some(child),
2332 }),
2333 installation: Some(RootComponentChildInstallEvidence {
2334 raw_module_hash: [23; 32],
2335 chunk_hashes: vec![vec![24; 32]],
2336 binding: child_binding.clone(),
2337 }),
2338 };
2339 let commit_response = RootComponentChildCommitResponse {
2340 allocation: response.clone(),
2341 registry: ComponentRegistryPartitionResponse {
2342 head: ComponentRegistryHead {
2343 component,
2344 revision: 3,
2345 content_hash: [25; 32],
2346 },
2347 binding: child_binding.component.clone(),
2348 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2349 caller: Principal::from_slice(&[26; 29]),
2350 },
2351 release_set: response.release_set,
2352 status: ComponentLifecycleStatus::Active,
2353 reserved_descendants: 0,
2354 committed_descendants: 1,
2355 encoded_bytes: 8_192,
2356 },
2357 directory: ComponentDirectoryHead {
2358 provenance: ComponentDirectoryProvenance {
2359 component: child_binding.component.clone(),
2360 source_fleet_subnet_root: root,
2361 component_registry_revision: 3,
2362 component_registry_content_hash: [25; 32],
2363 synchronized_at_ns: 27,
2364 },
2365 descendant_count: 1,
2366 },
2367 };
2368 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2369 fleet: FleetDirectorySnapshot {
2370 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2371 registry: FleetRegistryVersion {
2372 authority: fleet_registry_authority(),
2373 revision: 4,
2374 content_hash: [28; 32],
2375 },
2376 source_fleet_subnet_root: root,
2377 },
2378 fleet_subnet_roots: vec![
2379 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2380 placement_subnet: commit_response.registry.binding.placement_subnet,
2381 fleet_subnet_root: root,
2382 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2383 },
2384 ],
2385 },
2386 component: commit_response.directory.clone(),
2387 };
2388 let activation = ComponentRuntimeActivationEvidence {
2389 directory_authority_hash: [29; 32],
2390 activated_at_ns: 30,
2391 };
2392 let directory_response = RootComponentChildDirectoryPreparationResponse {
2393 committed: commit_response.clone(),
2394 child: ComponentRuntimeStatusResponse {
2395 operation_id: request.operation_id,
2396 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2397 phase: ComponentRuntimePhase::DirectoryPrepared,
2398 authority: Some(runtime_authority.clone()),
2399 authority_hash: Some([31; 32]),
2400 activation: None,
2401 },
2402 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2403 operation_id: [32; 32],
2404 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2405 covered_authority: runtime_authority.clone(),
2406 covered_authority_hash: [31; 32],
2407 activation,
2408 },
2409 parent: None,
2410 };
2411 let activation_response = RootComponentChildRuntimeActivationResponse {
2412 committed: commit_response.clone(),
2413 child: ComponentRuntimeStatusResponse {
2414 operation_id: request.operation_id,
2415 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2416 phase: ComponentRuntimePhase::Active,
2417 authority: Some(runtime_authority.clone()),
2418 authority_hash: Some([31; 32]),
2419 activation: Some(ComponentRuntimeActivationEvidence {
2420 directory_authority_hash: [31; 32],
2421 activated_at_ns: 33,
2422 }),
2423 },
2424 };
2425 let active_directory = ComponentDirectoryHead {
2426 provenance: ComponentDirectoryProvenance {
2427 component: child_binding.component.clone(),
2428 source_fleet_subnet_root: root,
2429 component_registry_revision: 4,
2430 component_registry_content_hash: [34; 32],
2431 synchronized_at_ns: 35,
2432 },
2433 descendant_count: 1,
2434 };
2435 let active_authority = ComponentRuntimeDirectoryAuthority {
2436 fleet: runtime_authority.fleet,
2437 component: active_directory.clone(),
2438 };
2439 let membership_response = RootComponentChildMembershipActivationResponse {
2440 committed: commit_response.clone(),
2441 registry: ComponentRegistryPartitionResponse {
2442 head: ComponentRegistryHead {
2443 component,
2444 revision: 4,
2445 content_hash: [34; 32],
2446 },
2447 binding: child_binding.component.clone(),
2448 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2449 release_set: commit_response.registry.release_set,
2450 status: ComponentLifecycleStatus::Active,
2451 reserved_descendants: 0,
2452 committed_descendants: 1,
2453 encoded_bytes: 8_256,
2454 },
2455 directory: active_directory,
2456 child: ComponentRuntimeStatusResponse {
2457 operation_id: request.operation_id,
2458 binding: ManagedCanisterBinding::ComponentChild(child_binding),
2459 phase: ComponentRuntimePhase::Active,
2460 authority: Some(active_authority),
2461 authority_hash: Some([36; 32]),
2462 activation: Some(ComponentRuntimeActivationEvidence {
2463 directory_authority_hash: [31; 32],
2464 activated_at_ns: 33,
2465 }),
2466 },
2467 };
2468
2469 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2470 let status_bytes =
2471 candid::encode_one(status_request).expect("encode child reservation status");
2472 let creation_bytes =
2473 candid::encode_one(creation_request).expect("encode child creation request");
2474 let install_bytes =
2475 candid::encode_one(install_request).expect("encode child install request");
2476 let response_bytes = candid::encode_one(&response).expect("encode child response");
2477 let commit_request_bytes =
2478 candid::encode_one(commit_request).expect("encode child commit request");
2479 let directory_request_bytes =
2480 candid::encode_one(directory_request).expect("encode child Directory request");
2481 let activation_request_bytes =
2482 candid::encode_one(activation_request).expect("encode child activation request");
2483 let membership_request_bytes =
2484 candid::encode_one(membership_request).expect("encode child membership request");
2485 let commit_response_bytes =
2486 candid::encode_one(&commit_response).expect("encode child commit response");
2487 let directory_response_bytes =
2488 candid::encode_one(&directory_response).expect("encode child Directory response");
2489 let activation_response_bytes =
2490 candid::encode_one(&activation_response).expect("encode child activation response");
2491 let membership_response_bytes =
2492 candid::encode_one(&membership_response).expect("encode child membership response");
2493
2494 assert_eq!(
2495 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2496 .expect("decode child reservation"),
2497 request
2498 );
2499 assert_eq!(
2500 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2501 .expect("decode child reservation status"),
2502 status_request
2503 );
2504 assert_eq!(
2505 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2506 .expect("decode child creation request"),
2507 creation_request
2508 );
2509 assert_eq!(
2510 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2511 .expect("decode child install request"),
2512 install_request
2513 );
2514 assert_eq!(
2515 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2516 .expect("decode child response"),
2517 response
2518 );
2519 assert_eq!(
2520 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2521 .expect("decode child commit request"),
2522 commit_request
2523 );
2524 assert_eq!(
2525 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2526 &directory_request_bytes
2527 )
2528 .expect("decode child Directory request"),
2529 directory_request
2530 );
2531 assert_eq!(
2532 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2533 .expect("decode child commit response"),
2534 commit_response
2535 );
2536 assert_eq!(
2537 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2538 &directory_response_bytes
2539 )
2540 .expect("decode child Directory response"),
2541 directory_response
2542 );
2543 assert_eq!(
2544 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2545 &activation_request_bytes
2546 )
2547 .expect("decode child activation request"),
2548 activation_request
2549 );
2550 assert_eq!(
2551 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2552 &activation_response_bytes
2553 )
2554 .expect("decode child activation response"),
2555 activation_response
2556 );
2557 assert_eq!(
2558 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2559 &membership_request_bytes
2560 )
2561 .expect("decode child membership request"),
2562 membership_request
2563 );
2564 assert_eq!(
2565 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2566 &membership_response_bytes
2567 )
2568 .expect("decode child membership response"),
2569 membership_response
2570 );
2571 }
2572
2573 #[test]
2574 fn component_install_request_round_trips_through_candid() {
2575 let request = RootComponentInstallRequest {
2576 operation_id: [10; 32],
2577 };
2578 let bytes = candid::encode_one(request).expect("encode install request");
2579
2580 assert_eq!(
2581 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2582 .expect("decode install request"),
2583 request
2584 );
2585 }
2586
2587 #[test]
2588 fn component_commit_request_round_trips_through_candid() {
2589 let request = RootComponentCommitRequest {
2590 operation_id: [10; 32],
2591 };
2592 let bytes = candid::encode_one(request).expect("encode commit request");
2593
2594 assert_eq!(
2595 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2596 .expect("decode commit request"),
2597 request
2598 );
2599 }
2600
2601 #[test]
2602 fn component_runtime_activation_requests_round_trip_through_candid() {
2603 let root_request = RootComponentRuntimeActivationRequest {
2604 operation_id: [22; 32],
2605 };
2606 let target_request = ComponentRuntimeActivationRequest {
2607 operation_id: root_request.operation_id,
2608 directory_authority_hash: [23; 32],
2609 };
2610 let membership_request = RootComponentMembershipActivationRequest {
2611 operation_id: root_request.operation_id,
2612 };
2613 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2614 let target_bytes =
2615 candid::encode_one(target_request).expect("encode target activation request");
2616 let membership_bytes =
2617 candid::encode_one(membership_request).expect("encode membership activation request");
2618
2619 assert_eq!(
2620 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2621 .expect("decode root activation request"),
2622 root_request
2623 );
2624 assert_eq!(
2625 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2626 .expect("decode target activation request"),
2627 target_request
2628 );
2629 assert_eq!(
2630 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2631 .expect("decode membership activation request"),
2632 membership_request
2633 );
2634 }
2635}