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