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