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, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
272pub struct RootComponentChildCreationRequest {
273 pub operation_id: [u8; 32],
274 pub component: ComponentInstanceId,
275}
276
277#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
284pub struct RootComponentChildInstallRequest {
285 pub operation_id: [u8; 32],
286 pub component: ComponentInstanceId,
287}
288
289#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
296pub struct RootComponentChildCommitRequest {
297 pub operation_id: [u8; 32],
298 pub component: ComponentInstanceId,
299}
300
301#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
308pub struct RootComponentChildDirectoryPreparationRequest {
309 pub operation_id: [u8; 32],
310 pub component: ComponentInstanceId,
311}
312
313#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub struct RootComponentChildRuntimeActivationRequest {
321 pub operation_id: [u8; 32],
322 pub component: ComponentInstanceId,
323}
324
325#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
332pub struct RootComponentChildMembershipActivationRequest {
333 pub operation_id: [u8; 32],
334 pub component: ComponentInstanceId,
335}
336
337#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
344pub struct RootComponentCreationRequest {
345 pub operation_id: [u8; 32],
346}
347
348#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
355pub struct RootComponentInstallRequest {
356 pub operation_id: [u8; 32],
357}
358
359#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
366pub struct RootComponentCommitRequest {
367 pub operation_id: [u8; 32],
368}
369
370#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
377pub struct RootComponentDirectoryPreparationRequest {
378 pub operation_id: [u8; 32],
379}
380
381#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct RootComponentRuntimeActivationRequest {
389 pub operation_id: [u8; 32],
390}
391
392#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
399pub struct RootComponentMembershipActivationRequest {
400 pub operation_id: [u8; 32],
401}
402
403#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
410pub enum ComponentProvisioningOrigin {
411 FleetAdministrator { caller: Principal },
412}
413
414#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
421pub enum RootComponentAllocationPhase {
422 Reserved,
423 CreationIntent,
424 Created,
425 InstallIntent,
426 Installed,
427 Verified,
428 Committed,
429}
430
431#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
438#[expect(
439 clippy::large_enum_variant,
440 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
441)]
442pub enum RootComponentSubtreeRemovalPhase {
443 Fenced,
444 Traversing(RootComponentSubtreeRemovalNode),
445 LeafSelected(RootComponentSubtreeRemovalNode),
446 StopIntent(RootComponentSubtreeRemovalStopIntent),
447 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
448 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
449 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
450 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
451 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
452 Completed(RootComponentSubtreeRemovalCompletedReceipt),
453}
454
455#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
462pub struct RootComponentSubtreeRemovalNode {
463 pub canister_id: Principal,
464 pub parent_canister_id: Principal,
465 pub role: CanisterRole,
466 pub kind: ComponentChildKind,
467 pub installed_artifact_hash: [u8; 32],
468 pub status: ComponentLifecycleStatus,
469}
470
471#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
478pub struct RootComponentSubtreeRemovalStopIntent {
479 pub leaf: RootComponentSubtreeRemovalNode,
480 pub controller: Principal,
481}
482
483#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
490pub struct RootComponentSubtreeRemovalStoppedReceipt {
491 pub stop: RootComponentSubtreeRemovalStopIntent,
492 pub observed_module_hash: [u8; 32],
493}
494
495#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
502pub struct RootComponentSubtreeRemovalDeleteIntent {
503 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
504}
505
506#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
513pub struct RootComponentSubtreeRemovalDeletedReceipt {
514 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
515}
516
517#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
524pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
525 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
526 pub removed_from_registry: ComponentRegistryHead,
527 pub previous_descendant_content_hash: [u8; 32],
528 pub previous_committed_descendants: u32,
529 pub registry: ComponentRegistryHead,
530 pub descendant_content_hash: [u8; 32],
531 pub registry_encoded_bytes: u64,
532 pub reserved_descendants: u32,
533 pub committed_descendants: u32,
534 pub directory_synchronized_at_ns: u64,
535 pub directory_authority_hash: [u8; 32],
536 pub parent_role_instances: u32,
537 pub root_managed_descendants: u32,
538 pub root_known_created_component_canisters: u32,
539}
540
541#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
548pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
549 pub operation_id: [u8; 32],
550 pub canister_id: Principal,
551 pub activation: ComponentRuntimeActivationEvidence,
552}
553
554#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
561pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
562 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
563 pub covered_fleet_registry_revision: u64,
564 pub covered_fleet_registry_content_hash: [u8; 32],
565 pub covered_component_registry: ComponentRegistryHead,
566 pub covered_authority_hash: [u8; 32],
567 pub owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence,
568 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
569}
570
571#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
578pub struct RootComponentSubtreeRemovalCompletedReceipt {
579 pub registry: ComponentRegistryHead,
580 pub directory_authority_hash: [u8; 32],
581}
582
583#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
590pub enum ComponentLifecycleStatus {
591 Prepared,
592 Active,
593 Draining,
594 Removed,
595}
596
597#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
604pub struct ComponentRegistryHead {
605 pub component: ComponentInstanceId,
606 pub revision: u64,
607 pub content_hash: [u8; 32],
608}
609
610#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
617pub struct ComponentRegistryPartitionRequest {
618 pub component: ComponentInstanceId,
619}
620
621#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
628pub struct ComponentRegistryPartitionResponse {
629 pub head: ComponentRegistryHead,
630 pub binding: ComponentBinding,
631 pub provisioning_origin: ComponentProvisioningOrigin,
632 pub release_set: FleetSubnetRootReleaseSet,
633 pub status: ComponentLifecycleStatus,
634 pub reserved_descendants: u32,
635 pub committed_descendants: u32,
636 pub encoded_bytes: u64,
637}
638
639#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
646pub struct ComponentDirectoryProvenance {
647 pub component: ComponentBinding,
648 pub source_fleet_subnet_root: Principal,
649 pub component_registry_revision: u64,
650 pub component_registry_content_hash: [u8; 32],
651 pub synchronized_at_ns: u64,
652}
653
654#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
661pub struct ComponentDirectoryHead {
662 pub provenance: ComponentDirectoryProvenance,
663 pub descendant_count: u32,
664}
665
666#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
673pub struct ComponentDirectoryHeadRequest {
674 pub component: ComponentInstanceId,
675}
676
677#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
684pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
685
686#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
693pub struct ComponentDirectoryPageRequest {
694 pub directory: ComponentDirectoryHead,
695 pub parent_canister_id: Option<Principal>,
696 pub role: Option<CanisterRole>,
697 pub status: Option<ComponentLifecycleStatus>,
698 pub cursor: Option<ComponentDirectoryPageCursor>,
699 pub limit: u16,
700}
701
702#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
709pub struct ComponentDirectoryChildEntry {
710 pub binding: ComponentChildBinding,
711 pub kind: ComponentChildKind,
712 pub installed_artifact_hash: [u8; 32],
713 pub status: ComponentLifecycleStatus,
714}
715
716#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
723pub struct ComponentDirectoryPageResponse {
724 pub directory: ComponentDirectoryHead,
725 pub entries: Vec<ComponentDirectoryChildEntry>,
726 pub next_cursor: Option<ComponentDirectoryPageCursor>,
727}
728
729#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
736pub struct ComponentRuntimeDirectoryAuthority {
737 pub fleet: FleetDirectorySnapshot,
738 pub component: ComponentDirectoryHead,
739}
740
741#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
748pub struct ComponentRuntimeDirectoryPreparationRequest {
749 pub operation_id: [u8; 32],
750 pub authority: ComponentRuntimeDirectoryAuthority,
751}
752
753#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
760pub struct ComponentRuntimeDirectorySynchronizationRequest {
761 pub operation_id: [u8; 32],
762 pub authority: ComponentRuntimeDirectoryAuthority,
763}
764
765#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
772pub enum ComponentRuntimePhase {
773 AwaitingDirectory,
774 DirectoryPrepared,
775 Active,
776}
777
778#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
785pub struct ComponentRuntimeActivationEvidence {
786 pub directory_authority_hash: [u8; 32],
787 pub activated_at_ns: u64,
788}
789
790#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
797pub struct ComponentRuntimeActivationRequest {
798 pub operation_id: [u8; 32],
799 pub directory_authority_hash: [u8; 32],
800}
801
802#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
809pub struct ComponentRuntimeStatusResponse {
810 pub operation_id: [u8; 32],
811 pub binding: ManagedCanisterBinding,
812 pub phase: ComponentRuntimePhase,
813 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
814 pub authority_hash: Option<[u8; 32]>,
815 pub activation: Option<ComponentRuntimeActivationEvidence>,
816}
817
818#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
825pub struct ComponentRuntimeDirectoryConvergenceEvidence {
826 pub operation_id: [u8; 32],
827 pub binding: ManagedCanisterBinding,
828 pub covered_authority: ComponentRuntimeDirectoryAuthority,
829 pub covered_authority_hash: [u8; 32],
830 pub activation: ComponentRuntimeActivationEvidence,
831}
832
833#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
840pub struct RootComponentCreationEvidence {
841 pub wasm_store: Principal,
842 pub payload_hash: [u8; 32],
843 pub payload_size_bytes: u64,
844 pub initial_cycles: Cycles,
845 pub controller: Principal,
846 pub canister: Option<Principal>,
847}
848
849#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
856pub struct RootComponentInstallEvidence {
857 pub raw_module_hash: [u8; 32],
858 pub chunk_hashes: Vec<Vec<u8>>,
859 pub binding: ComponentBinding,
860}
861
862#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
869pub struct RootComponentChildInstallEvidence {
870 pub raw_module_hash: [u8; 32],
871 pub chunk_hashes: Vec<Vec<u8>>,
872 pub binding: ComponentChildBinding,
873}
874
875#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
882pub struct RootComponentAllocationResponse {
883 pub operation_id: [u8; 32],
884 pub allocation_sequence: u64,
885 pub component: ComponentInstanceId,
886 pub component_spec: ComponentSpecId,
887 pub spec_hash: [u8; 32],
888 pub role: CanisterRole,
889 pub provisioning_origin: ComponentProvisioningOrigin,
890 pub release_set: FleetSubnetRootReleaseSet,
891 pub phase: RootComponentAllocationPhase,
892 pub creation: Option<RootComponentCreationEvidence>,
893 pub installation: Option<RootComponentInstallEvidence>,
894}
895
896#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
903pub struct RootComponentChildAllocationResponse {
904 pub operation_id: [u8; 32],
905 pub component: ComponentInstanceId,
906 pub parent_canister_id: Principal,
907 pub parent_role: CanisterRole,
908 pub child_role: CanisterRole,
909 pub child_kind: ComponentChildKind,
910 pub maximum_instances_per_parent: u32,
911 pub maximum_descendants: u32,
912 pub maximum_registry_bytes: u64,
913 pub reserved_against_registry: ComponentRegistryHead,
914 pub release_set: FleetSubnetRootReleaseSet,
915 pub phase: RootComponentAllocationPhase,
916 pub creation: Option<RootComponentCreationEvidence>,
917 pub installation: Option<RootComponentChildInstallEvidence>,
918}
919
920#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
927pub struct RootComponentSubtreeRemovalResponse {
928 pub operation_id: [u8; 32],
929 pub component: ComponentInstanceId,
930 pub target_canister_id: Principal,
931 pub target_parent_canister_id: Principal,
932 pub target_role: CanisterRole,
933 pub target_status: ComponentLifecycleStatus,
934 pub reserved_against_registry: ComponentRegistryHead,
935 pub maximum_completed_leaves: u32,
936 pub completed_leaves: u32,
937 pub traversal_steps: u32,
938 pub phase: RootComponentSubtreeRemovalPhase,
939}
940
941#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
948pub struct RootComponentChildCommitResponse {
949 pub allocation: RootComponentChildAllocationResponse,
950 pub registry: ComponentRegistryPartitionResponse,
951 pub directory: ComponentDirectoryHead,
952}
953
954#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
961pub struct RootComponentChildDirectoryPreparationResponse {
962 pub committed: RootComponentChildCommitResponse,
963 pub child: ComponentRuntimeStatusResponse,
964 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
965 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
966}
967
968#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
975pub struct RootComponentChildRuntimeActivationResponse {
976 pub committed: RootComponentChildCommitResponse,
977 pub child: ComponentRuntimeStatusResponse,
978}
979
980#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
987pub struct RootComponentChildMembershipActivationResponse {
988 pub committed: RootComponentChildCommitResponse,
989 pub registry: ComponentRegistryPartitionResponse,
990 pub directory: ComponentDirectoryHead,
991 pub child: ComponentRuntimeStatusResponse,
992}
993
994#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1001pub struct RootComponentCommitResponse {
1002 pub allocation: RootComponentAllocationResponse,
1003 pub registry: ComponentRegistryPartitionResponse,
1004 pub directory: ComponentDirectoryHead,
1005}
1006
1007#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1014pub struct RootComponentDirectoryPreparationResponse {
1015 pub committed: RootComponentCommitResponse,
1016 pub target: ComponentRuntimeStatusResponse,
1017}
1018
1019#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1026pub struct RootComponentRuntimeActivationResponse {
1027 pub committed: RootComponentCommitResponse,
1028 pub target: ComponentRuntimeStatusResponse,
1029}
1030
1031#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1038pub struct RootComponentMembershipActivationResponse {
1039 pub allocation: RootComponentAllocationResponse,
1040 pub registry: ComponentRegistryPartitionResponse,
1041 pub directory: ComponentDirectoryHead,
1042 pub target: ComponentRuntimeStatusResponse,
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::{
1049 dto::root_store::RootStoreBootstrapRequest,
1050 ids::{
1051 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1052 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1053 },
1054 };
1055
1056 #[test]
1057 fn component_registry_contracts_round_trip_through_candid() {
1058 let request = RootComponentRegistryPreparationRequest {
1059 store_bootstrap: RootStoreBootstrapRequest {
1060 manifest_payload_size_bytes: 128,
1061 },
1062 expected_fleet_registry: FleetRegistryVersion {
1063 authority: fleet_registry_authority(),
1064 revision: 4,
1065 content_hash: [5; 32],
1066 },
1067 };
1068 let response = RootComponentRegistryStatusResponse {
1069 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1070 prepared_against_registry: request.expected_fleet_registry.clone(),
1071 release_set: FleetSubnetRootReleaseSet {
1072 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1073 [7; 32],
1074 )),
1075 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1076 },
1077 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1078 next_allocation_sequence: 1,
1079 reserved_component_instances: 0,
1080 committed_component_instances: 0,
1081 managed_descendants: 0,
1082 known_created_component_canisters: 0,
1083 encoded_bytes: 0,
1084 initial_inventory: Some(RootComponentInitialInventoryStatus {
1085 fleet_activation_operation_id: [10; 32],
1086 component_count: 0,
1087 inventory_hash: [11; 32],
1088 sealed_at_ns: 12,
1089 directories_converged: true,
1090 root_runtime_activated: true,
1091 }),
1092 };
1093 let allocation = RootComponentAllocationResponse {
1094 operation_id: [10; 32],
1095 allocation_sequence: 1,
1096 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1097 component_spec: "projects".parse().expect("Component Spec ID"),
1098 spec_hash: [12; 32],
1099 role: CanisterRole::new("project_hub"),
1100 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1101 caller: Principal::from_slice(&[13; 29]),
1102 },
1103 release_set: response.release_set,
1104 phase: RootComponentAllocationPhase::Reserved,
1105 creation: None,
1106 installation: None,
1107 };
1108 let created = RootComponentAllocationResponse {
1109 phase: RootComponentAllocationPhase::Created,
1110 creation: Some(RootComponentCreationEvidence {
1111 wasm_store: Principal::from_slice(&[14; 29]),
1112 payload_hash: [15; 32],
1113 payload_size_bytes: 4_096,
1114 initial_cycles: Cycles::new(5_000_000_000_000),
1115 controller: Principal::from_slice(&[6; 29]),
1116 canister: Some(Principal::from_slice(&[16; 29])),
1117 }),
1118 installation: None,
1119 ..allocation.clone()
1120 };
1121 let request_bytes = candid::encode_one(&request).expect("encode request");
1122 let response_bytes = candid::encode_one(&response).expect("encode response");
1123 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1124 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1125
1126 assert_eq!(
1127 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1128 .expect("decode request"),
1129 request
1130 );
1131 assert_eq!(
1132 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1133 .expect("decode response"),
1134 response
1135 );
1136 assert_eq!(
1137 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1138 .expect("decode allocation"),
1139 allocation
1140 );
1141 assert_eq!(
1142 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1143 .expect("decode created allocation"),
1144 created
1145 );
1146 }
1147
1148 #[test]
1149 fn component_commit_response_round_trips_through_candid() {
1150 let root = Principal::from_slice(&[6; 29]);
1151 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1152 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1153 let release_set = FleetSubnetRootReleaseSet {
1154 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1155 [7; 32],
1156 )),
1157 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1158 };
1159 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1160 caller: Principal::from_slice(&[13; 29]),
1161 };
1162 let binding = ComponentBinding {
1163 authority: fleet_registry_authority(),
1164 component,
1165 component_spec: component_spec.clone(),
1166 spec_hash: [12; 32],
1167 role: CanisterRole::new("project_hub"),
1168 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1169 fleet_subnet_root: root,
1170 canister_id: Principal::from_slice(&[16; 29]),
1171 };
1172 let head = ComponentRegistryHead {
1173 component,
1174 revision: 1,
1175 content_hash: [18; 32],
1176 };
1177 let committed = RootComponentCommitResponse {
1178 allocation: RootComponentAllocationResponse {
1179 operation_id: [10; 32],
1180 allocation_sequence: 1,
1181 component,
1182 component_spec,
1183 spec_hash: binding.spec_hash,
1184 role: binding.role.clone(),
1185 provisioning_origin: provisioning_origin.clone(),
1186 release_set,
1187 phase: RootComponentAllocationPhase::Committed,
1188 creation: Some(RootComponentCreationEvidence {
1189 wasm_store: Principal::from_slice(&[14; 29]),
1190 payload_hash: [15; 32],
1191 payload_size_bytes: 4_096,
1192 initial_cycles: Cycles::new(5_000_000_000_000),
1193 controller: root,
1194 canister: Some(binding.canister_id),
1195 }),
1196 installation: Some(RootComponentInstallEvidence {
1197 raw_module_hash: [20; 32],
1198 chunk_hashes: vec![vec![21; 32]],
1199 binding: binding.clone(),
1200 }),
1201 },
1202 registry: ComponentRegistryPartitionResponse {
1203 head: head.clone(),
1204 binding: binding.clone(),
1205 provisioning_origin,
1206 release_set,
1207 status: ComponentLifecycleStatus::Prepared,
1208 reserved_descendants: 0,
1209 committed_descendants: 0,
1210 encoded_bytes: 2_048,
1211 },
1212 directory: ComponentDirectoryHead {
1213 provenance: ComponentDirectoryProvenance {
1214 component: binding,
1215 source_fleet_subnet_root: root,
1216 component_registry_revision: head.revision,
1217 component_registry_content_hash: head.content_hash,
1218 synchronized_at_ns: 19,
1219 },
1220 descendant_count: 0,
1221 },
1222 };
1223 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1224
1225 assert_eq!(
1226 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1227 .expect("decode committed allocation"),
1228 committed
1229 );
1230 }
1231
1232 #[test]
1233 fn component_directory_page_contracts_round_trip_through_candid() {
1234 let root = Principal::from_slice(&[6; 29]);
1235 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1236 let binding = ComponentBinding {
1237 authority: fleet_registry_authority(),
1238 component,
1239 component_spec: "projects".parse().expect("Component Spec ID"),
1240 spec_hash: [12; 32],
1241 role: CanisterRole::new("project_hub"),
1242 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1243 fleet_subnet_root: root,
1244 canister_id: Principal::from_slice(&[16; 29]),
1245 };
1246 let directory = ComponentDirectoryHead {
1247 provenance: ComponentDirectoryProvenance {
1248 component: binding.clone(),
1249 source_fleet_subnet_root: root,
1250 component_registry_revision: 3,
1251 component_registry_content_hash: [18; 32],
1252 synchronized_at_ns: 19,
1253 },
1254 descendant_count: 1,
1255 };
1256 let request = ComponentDirectoryPageRequest {
1257 directory: directory.clone(),
1258 parent_canister_id: Some(binding.canister_id),
1259 role: Some(CanisterRole::new("project_instance")),
1260 status: Some(ComponentLifecycleStatus::Active),
1261 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1262 limit: 50,
1263 };
1264 let response = ComponentDirectoryPageResponse {
1265 directory,
1266 entries: vec![ComponentDirectoryChildEntry {
1267 binding: ComponentChildBinding {
1268 component: binding.clone(),
1269 parent_canister_id: binding.canister_id,
1270 role: CanisterRole::new("project_instance"),
1271 canister_id: Principal::from_slice(&[21; 29]),
1272 },
1273 kind: ComponentChildKind::Instance,
1274 installed_artifact_hash: [22; 32],
1275 status: ComponentLifecycleStatus::Active,
1276 }],
1277 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1278 };
1279 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1280 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1281
1282 assert_eq!(
1283 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1284 .expect("decode Directory page request"),
1285 request
1286 );
1287 assert_eq!(
1288 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1289 .expect("decode Directory page response"),
1290 response
1291 );
1292 }
1293
1294 fn fleet_registry_authority() -> FleetRegistryAuthority {
1295 FleetRegistryAuthority {
1296 binding: FleetCoordinatorBinding {
1297 fleet: crate::ids::FleetBinding {
1298 fleet: FleetKey {
1299 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1300 fleet_id: FleetId::from_generated_bytes([1; 32]),
1301 },
1302 app: AppId::from("toko"),
1303 },
1304 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1305 coordinator: Principal::from_slice(&[3; 29]),
1306 },
1307 epoch: 1,
1308 }
1309 }
1310
1311 #[test]
1312 fn component_creation_request_round_trips_through_candid() {
1313 let request = RootComponentCreationRequest {
1314 operation_id: [10; 32],
1315 };
1316 let bytes = candid::encode_one(request).expect("encode creation request");
1317
1318 assert_eq!(
1319 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1320 .expect("decode creation request"),
1321 request
1322 );
1323 }
1324
1325 #[test]
1326 #[expect(
1327 clippy::too_many_lines,
1328 reason = "one Candid contract test covers every subtree-removal phase receipt"
1329 )]
1330 fn component_subtree_removal_contracts_round_trip_through_candid() {
1331 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1332 let registry = ComponentRegistryHead {
1333 component,
1334 revision: 7,
1335 content_hash: [42; 32],
1336 };
1337 let request = RootComponentSubtreeRemovalRequest {
1338 operation_id: [43; 32],
1339 component,
1340 target_canister_id: Principal::from_slice(&[44; 29]),
1341 expected_registry: registry.clone(),
1342 };
1343 let status_request = RootComponentSubtreeRemovalStatusRequest {
1344 operation_id: request.operation_id,
1345 component,
1346 };
1347 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1348 operation_id: request.operation_id,
1349 component,
1350 expected_traversal_steps: 1,
1351 };
1352 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1353 operation_id: request.operation_id,
1354 component,
1355 expected_traversal_steps: 2,
1356 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1357 expected_leaf_parent_canister_id: request.target_canister_id,
1358 };
1359 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1360 observed_module_hash: [49; 32],
1361 stop: RootComponentSubtreeRemovalStopIntent {
1362 controller: Principal::from_slice(&[48; 29]),
1363 leaf: RootComponentSubtreeRemovalNode {
1364 canister_id: Principal::from_slice(&[46; 29]),
1365 parent_canister_id: request.target_canister_id,
1366 role: CanisterRole::new("project_ledger"),
1367 kind: ComponentChildKind::Singleton,
1368 installed_artifact_hash: [47; 32],
1369 status: ComponentLifecycleStatus::Active,
1370 },
1371 },
1372 };
1373 let response = RootComponentSubtreeRemovalResponse {
1374 operation_id: request.operation_id,
1375 component,
1376 target_canister_id: request.target_canister_id,
1377 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1378 target_role: CanisterRole::new("project_instance"),
1379 target_status: ComponentLifecycleStatus::Active,
1380 reserved_against_registry: registry,
1381 maximum_completed_leaves: 4,
1382 completed_leaves: 1,
1383 traversal_steps: 2,
1384 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1385 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1386 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1387 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1388 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1389 },
1390 removed_from_registry: ComponentRegistryHead {
1391 component,
1392 revision: 8,
1393 content_hash: [50; 32],
1394 },
1395 previous_descendant_content_hash: [51; 32],
1396 previous_committed_descendants: 4,
1397 registry: ComponentRegistryHead {
1398 component,
1399 revision: 9,
1400 content_hash: [52; 32],
1401 },
1402 descendant_content_hash: [53; 32],
1403 registry_encoded_bytes: 4_096,
1404 reserved_descendants: 1,
1405 committed_descendants: 3,
1406 directory_synchronized_at_ns: 54,
1407 directory_authority_hash: [55; 32],
1408 parent_role_instances: 0,
1409 root_managed_descendants: 4,
1410 root_known_created_component_canisters: 4,
1411 },
1412 covered_fleet_registry_revision: 6,
1413 covered_fleet_registry_content_hash: [56; 32],
1414 covered_component_registry: ComponentRegistryHead {
1415 component,
1416 revision: 9,
1417 content_hash: [52; 32],
1418 },
1419 covered_authority_hash: [55; 32],
1420 owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1421 operation_id: [57; 32],
1422 canister_id: Principal::from_slice(&[58; 29]),
1423 activation: ComponentRuntimeActivationEvidence {
1424 directory_authority_hash: [59; 32],
1425 activated_at_ns: 60,
1426 },
1427 },
1428 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1429 operation_id: [61; 32],
1430 canister_id: request.target_canister_id,
1431 activation: ComponentRuntimeActivationEvidence {
1432 directory_authority_hash: [62; 32],
1433 activated_at_ns: 63,
1434 },
1435 }),
1436 },
1437 ),
1438 };
1439
1440 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1441 let advance_bytes =
1442 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1443 let stop_bytes =
1444 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1445 let status_bytes =
1446 candid::encode_one(status_request).expect("encode subtree removal status request");
1447 let response_bytes =
1448 candid::encode_one(&response).expect("encode subtree removal response");
1449
1450 assert_eq!(
1451 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1452 .expect("decode subtree removal request"),
1453 request
1454 );
1455 assert_eq!(
1456 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1457 .expect("decode subtree removal advance request"),
1458 advance_request
1459 );
1460 assert_eq!(
1461 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1462 .expect("decode subtree removal stop request"),
1463 stop_request
1464 );
1465 assert_eq!(
1466 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1467 .expect("decode subtree removal status request"),
1468 status_request
1469 );
1470 assert_eq!(
1471 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1472 .expect("decode subtree removal response"),
1473 response
1474 );
1475 }
1476
1477 #[test]
1478 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
1479 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
1480 operation_id: [50; 32],
1481 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1482 expected_traversal_steps: 3,
1483 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1484 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1485 };
1486 let request = RootComponentSubtreeRemovalDeleteRequest {
1487 operation_id: prepare.operation_id,
1488 component: prepare.component,
1489 expected_traversal_steps: prepare.expected_traversal_steps,
1490 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1491 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1492 };
1493 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
1494 operation_id: prepare.operation_id,
1495 component: prepare.component,
1496 expected_traversal_steps: prepare.expected_traversal_steps,
1497 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1498 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1499 };
1500 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
1501 operation_id: prepare.operation_id,
1502 component: prepare.component,
1503 expected_traversal_steps: prepare.expected_traversal_steps,
1504 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1505 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1506 };
1507 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
1508 operation_id: prepare.operation_id,
1509 component: prepare.component,
1510 expected_traversal_steps: prepare.expected_traversal_steps,
1511 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1512 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1513 };
1514 let prepare_bytes = candid::encode_one(prepare)
1515 .expect("encode subtree removal deletion preparation request");
1516 let request_bytes =
1517 candid::encode_one(request).expect("encode subtree removal deletion request");
1518 let membership_request_bytes = candid::encode_one(membership_request)
1519 .expect("encode subtree removal membership-removal request");
1520 let directory_request_bytes = candid::encode_one(directory_request)
1521 .expect("encode subtree removal Directory synchronization request");
1522 let finalization_request_bytes = candid::encode_one(finalization_request)
1523 .expect("encode subtree removal leaf-finalization request");
1524
1525 assert_eq!(
1526 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
1527 &prepare_bytes
1528 )
1529 .expect("decode subtree removal deletion preparation request"),
1530 prepare
1531 );
1532 assert_eq!(
1533 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
1534 .expect("decode subtree removal deletion request"),
1535 request
1536 );
1537 assert_eq!(
1538 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
1539 &membership_request_bytes
1540 )
1541 .expect("decode subtree removal membership-removal request"),
1542 membership_request
1543 );
1544 assert_eq!(
1545 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
1546 &directory_request_bytes
1547 )
1548 .expect("decode subtree removal Directory synchronization request"),
1549 directory_request
1550 );
1551 assert_eq!(
1552 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
1553 &finalization_request_bytes
1554 )
1555 .expect("decode subtree removal leaf-finalization request"),
1556 finalization_request
1557 );
1558 }
1559
1560 #[test]
1561 fn component_subtree_removal_stop_request_round_trips_through_candid() {
1562 let request = RootComponentSubtreeRemovalStopRequest {
1563 operation_id: [50; 32],
1564 component: ComponentInstanceId::from_generated_bytes([51; 32]),
1565 expected_traversal_steps: 3,
1566 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1567 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1568 };
1569 let bytes =
1570 candid::encode_one(request).expect("encode subtree removal stop execution request");
1571
1572 assert_eq!(
1573 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
1574 .expect("decode subtree removal stop execution request"),
1575 request
1576 );
1577 }
1578
1579 #[test]
1580 #[expect(
1581 clippy::too_many_lines,
1582 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1583 )]
1584 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1585 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1586 let registry = ComponentRegistryHead {
1587 component,
1588 revision: 2,
1589 content_hash: [12; 32],
1590 };
1591 let request = RootComponentChildAllocationRequest {
1592 operation_id: [13; 32],
1593 component,
1594 expected_registry: registry.clone(),
1595 child_role: CanisterRole::new("project_instance"),
1596 };
1597 let status_request = RootComponentChildAllocationStatusRequest {
1598 operation_id: request.operation_id,
1599 component,
1600 };
1601 let creation_request = RootComponentChildCreationRequest {
1602 operation_id: request.operation_id,
1603 component,
1604 };
1605 let install_request = RootComponentChildInstallRequest {
1606 operation_id: request.operation_id,
1607 component,
1608 };
1609 let commit_request = RootComponentChildCommitRequest {
1610 operation_id: request.operation_id,
1611 component,
1612 };
1613 let directory_request = RootComponentChildDirectoryPreparationRequest {
1614 operation_id: request.operation_id,
1615 component,
1616 };
1617 let activation_request = RootComponentChildRuntimeActivationRequest {
1618 operation_id: request.operation_id,
1619 component,
1620 };
1621 let membership_request = RootComponentChildMembershipActivationRequest {
1622 operation_id: request.operation_id,
1623 component,
1624 };
1625 let root = Principal::from_slice(&[17; 29]);
1626 let parent = Principal::from_slice(&[14; 29]);
1627 let child = Principal::from_slice(&[18; 29]);
1628 let child_binding = ComponentChildBinding {
1629 component: ComponentBinding {
1630 authority: fleet_registry_authority(),
1631 component,
1632 component_spec: "projects".parse().expect("Component Spec"),
1633 spec_hash: [19; 32],
1634 role: CanisterRole::new("project_hub"),
1635 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1636 fleet_subnet_root: root,
1637 canister_id: parent,
1638 },
1639 parent_canister_id: parent,
1640 role: request.child_role.clone(),
1641 canister_id: child,
1642 };
1643 let response = RootComponentChildAllocationResponse {
1644 operation_id: request.operation_id,
1645 component,
1646 parent_canister_id: parent,
1647 parent_role: CanisterRole::new("project_hub"),
1648 child_role: request.child_role.clone(),
1649 child_kind: ComponentChildKind::Instance,
1650 maximum_instances_per_parent: 10_000,
1651 maximum_descendants: 20_000,
1652 maximum_registry_bytes: 16_777_216,
1653 reserved_against_registry: registry,
1654 release_set: FleetSubnetRootReleaseSet {
1655 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1656 [15; 32],
1657 )),
1658 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1659 },
1660 phase: RootComponentAllocationPhase::Verified,
1661 creation: Some(RootComponentCreationEvidence {
1662 wasm_store: Principal::from_slice(&[21; 29]),
1663 payload_hash: [22; 32],
1664 payload_size_bytes: 4_096,
1665 initial_cycles: Cycles::new(5_000_000_000_000),
1666 controller: root,
1667 canister: Some(child),
1668 }),
1669 installation: Some(RootComponentChildInstallEvidence {
1670 raw_module_hash: [23; 32],
1671 chunk_hashes: vec![vec![24; 32]],
1672 binding: child_binding.clone(),
1673 }),
1674 };
1675 let commit_response = RootComponentChildCommitResponse {
1676 allocation: response.clone(),
1677 registry: ComponentRegistryPartitionResponse {
1678 head: ComponentRegistryHead {
1679 component,
1680 revision: 3,
1681 content_hash: [25; 32],
1682 },
1683 binding: child_binding.component.clone(),
1684 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1685 caller: Principal::from_slice(&[26; 29]),
1686 },
1687 release_set: response.release_set,
1688 status: ComponentLifecycleStatus::Active,
1689 reserved_descendants: 0,
1690 committed_descendants: 1,
1691 encoded_bytes: 8_192,
1692 },
1693 directory: ComponentDirectoryHead {
1694 provenance: ComponentDirectoryProvenance {
1695 component: child_binding.component.clone(),
1696 source_fleet_subnet_root: root,
1697 component_registry_revision: 3,
1698 component_registry_content_hash: [25; 32],
1699 synchronized_at_ns: 27,
1700 },
1701 descendant_count: 1,
1702 },
1703 };
1704 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1705 fleet: FleetDirectorySnapshot {
1706 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1707 registry: FleetRegistryVersion {
1708 authority: fleet_registry_authority(),
1709 revision: 4,
1710 content_hash: [28; 32],
1711 },
1712 source_fleet_subnet_root: root,
1713 },
1714 fleet_subnet_roots: vec![
1715 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1716 placement_subnet: commit_response.registry.binding.placement_subnet,
1717 fleet_subnet_root: root,
1718 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1719 },
1720 ],
1721 },
1722 component: commit_response.directory.clone(),
1723 };
1724 let activation = ComponentRuntimeActivationEvidence {
1725 directory_authority_hash: [29; 32],
1726 activated_at_ns: 30,
1727 };
1728 let directory_response = RootComponentChildDirectoryPreparationResponse {
1729 committed: commit_response.clone(),
1730 child: ComponentRuntimeStatusResponse {
1731 operation_id: request.operation_id,
1732 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1733 phase: ComponentRuntimePhase::DirectoryPrepared,
1734 authority: Some(runtime_authority.clone()),
1735 authority_hash: Some([31; 32]),
1736 activation: None,
1737 },
1738 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1739 operation_id: [32; 32],
1740 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1741 covered_authority: runtime_authority.clone(),
1742 covered_authority_hash: [31; 32],
1743 activation,
1744 },
1745 parent: None,
1746 };
1747 let activation_response = RootComponentChildRuntimeActivationResponse {
1748 committed: commit_response.clone(),
1749 child: ComponentRuntimeStatusResponse {
1750 operation_id: request.operation_id,
1751 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1752 phase: ComponentRuntimePhase::Active,
1753 authority: Some(runtime_authority.clone()),
1754 authority_hash: Some([31; 32]),
1755 activation: Some(ComponentRuntimeActivationEvidence {
1756 directory_authority_hash: [31; 32],
1757 activated_at_ns: 33,
1758 }),
1759 },
1760 };
1761 let active_directory = ComponentDirectoryHead {
1762 provenance: ComponentDirectoryProvenance {
1763 component: child_binding.component.clone(),
1764 source_fleet_subnet_root: root,
1765 component_registry_revision: 4,
1766 component_registry_content_hash: [34; 32],
1767 synchronized_at_ns: 35,
1768 },
1769 descendant_count: 1,
1770 };
1771 let active_authority = ComponentRuntimeDirectoryAuthority {
1772 fleet: runtime_authority.fleet,
1773 component: active_directory.clone(),
1774 };
1775 let membership_response = RootComponentChildMembershipActivationResponse {
1776 committed: commit_response.clone(),
1777 registry: ComponentRegistryPartitionResponse {
1778 head: ComponentRegistryHead {
1779 component,
1780 revision: 4,
1781 content_hash: [34; 32],
1782 },
1783 binding: child_binding.component.clone(),
1784 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1785 release_set: commit_response.registry.release_set,
1786 status: ComponentLifecycleStatus::Active,
1787 reserved_descendants: 0,
1788 committed_descendants: 1,
1789 encoded_bytes: 8_256,
1790 },
1791 directory: active_directory,
1792 child: ComponentRuntimeStatusResponse {
1793 operation_id: request.operation_id,
1794 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1795 phase: ComponentRuntimePhase::Active,
1796 authority: Some(active_authority),
1797 authority_hash: Some([36; 32]),
1798 activation: Some(ComponentRuntimeActivationEvidence {
1799 directory_authority_hash: [31; 32],
1800 activated_at_ns: 33,
1801 }),
1802 },
1803 };
1804
1805 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1806 let status_bytes =
1807 candid::encode_one(status_request).expect("encode child reservation status");
1808 let creation_bytes =
1809 candid::encode_one(creation_request).expect("encode child creation request");
1810 let install_bytes =
1811 candid::encode_one(install_request).expect("encode child install request");
1812 let response_bytes = candid::encode_one(&response).expect("encode child response");
1813 let commit_request_bytes =
1814 candid::encode_one(commit_request).expect("encode child commit request");
1815 let directory_request_bytes =
1816 candid::encode_one(directory_request).expect("encode child Directory request");
1817 let activation_request_bytes =
1818 candid::encode_one(activation_request).expect("encode child activation request");
1819 let membership_request_bytes =
1820 candid::encode_one(membership_request).expect("encode child membership request");
1821 let commit_response_bytes =
1822 candid::encode_one(&commit_response).expect("encode child commit response");
1823 let directory_response_bytes =
1824 candid::encode_one(&directory_response).expect("encode child Directory response");
1825 let activation_response_bytes =
1826 candid::encode_one(&activation_response).expect("encode child activation response");
1827 let membership_response_bytes =
1828 candid::encode_one(&membership_response).expect("encode child membership response");
1829
1830 assert_eq!(
1831 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1832 .expect("decode child reservation"),
1833 request
1834 );
1835 assert_eq!(
1836 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1837 .expect("decode child reservation status"),
1838 status_request
1839 );
1840 assert_eq!(
1841 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1842 .expect("decode child creation request"),
1843 creation_request
1844 );
1845 assert_eq!(
1846 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1847 .expect("decode child install request"),
1848 install_request
1849 );
1850 assert_eq!(
1851 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1852 .expect("decode child response"),
1853 response
1854 );
1855 assert_eq!(
1856 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1857 .expect("decode child commit request"),
1858 commit_request
1859 );
1860 assert_eq!(
1861 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1862 &directory_request_bytes
1863 )
1864 .expect("decode child Directory request"),
1865 directory_request
1866 );
1867 assert_eq!(
1868 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1869 .expect("decode child commit response"),
1870 commit_response
1871 );
1872 assert_eq!(
1873 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1874 &directory_response_bytes
1875 )
1876 .expect("decode child Directory response"),
1877 directory_response
1878 );
1879 assert_eq!(
1880 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1881 &activation_request_bytes
1882 )
1883 .expect("decode child activation request"),
1884 activation_request
1885 );
1886 assert_eq!(
1887 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1888 &activation_response_bytes
1889 )
1890 .expect("decode child activation response"),
1891 activation_response
1892 );
1893 assert_eq!(
1894 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1895 &membership_request_bytes
1896 )
1897 .expect("decode child membership request"),
1898 membership_request
1899 );
1900 assert_eq!(
1901 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1902 &membership_response_bytes
1903 )
1904 .expect("decode child membership response"),
1905 membership_response
1906 );
1907 }
1908
1909 #[test]
1910 fn component_install_request_round_trips_through_candid() {
1911 let request = RootComponentInstallRequest {
1912 operation_id: [10; 32],
1913 };
1914 let bytes = candid::encode_one(request).expect("encode install request");
1915
1916 assert_eq!(
1917 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1918 .expect("decode install request"),
1919 request
1920 );
1921 }
1922
1923 #[test]
1924 fn component_commit_request_round_trips_through_candid() {
1925 let request = RootComponentCommitRequest {
1926 operation_id: [10; 32],
1927 };
1928 let bytes = candid::encode_one(request).expect("encode commit request");
1929
1930 assert_eq!(
1931 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1932 .expect("decode commit request"),
1933 request
1934 );
1935 }
1936
1937 #[test]
1938 fn component_runtime_activation_requests_round_trip_through_candid() {
1939 let root_request = RootComponentRuntimeActivationRequest {
1940 operation_id: [22; 32],
1941 };
1942 let target_request = ComponentRuntimeActivationRequest {
1943 operation_id: root_request.operation_id,
1944 directory_authority_hash: [23; 32],
1945 };
1946 let membership_request = RootComponentMembershipActivationRequest {
1947 operation_id: root_request.operation_id,
1948 };
1949 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1950 let target_bytes =
1951 candid::encode_one(target_request).expect("encode target activation request");
1952 let membership_bytes =
1953 candid::encode_one(membership_request).expect("encode membership activation request");
1954
1955 assert_eq!(
1956 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1957 .expect("decode root activation request"),
1958 root_request
1959 );
1960 assert_eq!(
1961 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1962 .expect("decode target activation request"),
1963 target_request
1964 );
1965 assert_eq!(
1966 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1967 .expect("decode membership activation request"),
1968 membership_request
1969 );
1970 }
1971}