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