1use crate::{
8 cdk::types::Cycles,
9 config::{FleetServiceMemberPurpose, schema::ComponentChildKind},
10 dto::{
11 component_deployment::ProtectedComponentDeployment,
12 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
13 root_store::RootStoreBootstrapRequest,
14 },
15 ids::{
16 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentGroupMemberPath,
17 ComponentGroupPlacementId, ComponentInstanceId, ComponentSpecId, ComponentTopologyDigest,
18 FleetServiceId, FleetSubnetRootReleaseSet, ManagedCanisterBinding,
19 },
20 role_contract::ProtocolProfileDigest,
21};
22use candid::{CandidType, Principal};
23use serde::{Deserialize, Serialize};
24
25#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
32pub struct RootComponentRegistryPreparationRequest {
33 pub store_bootstrap: RootStoreBootstrapRequest,
34 pub expected_fleet_registry: FleetRegistryVersion,
35}
36
37#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
44pub struct RootComponentInitialInventoryStatus {
45 pub fleet_activation_operation_id: [u8; 32],
46 pub component_count: u32,
47 pub inventory_hash: [u8; 32],
48 pub sealed_at_ns: u64,
49 pub directories_converged: bool,
50 pub root_runtime_activated: bool,
51}
52
53#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
60pub struct RootComponentRegistryStatusResponse {
61 pub fleet_subnet_root: Principal,
62 pub prepared_against_registry: FleetRegistryVersion,
63 pub release_set: FleetSubnetRootReleaseSet,
64 pub component_topology_digest: ComponentTopologyDigest,
65 pub next_allocation_sequence: u64,
66 pub reserved_component_instances: u32,
67 pub committed_component_instances: u32,
68 pub managed_descendants: u32,
69 pub known_created_component_canisters: u32,
70 pub encoded_bytes: u64,
71 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
72}
73
74#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
81pub struct RootComponentAllocationRequest {
82 pub operation_id: [u8; 32],
83 pub component_spec: ComponentSpecId,
84}
85
86#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93pub enum PeerComponentRequester {
94 SameRoot,
95 FleetService {
96 service: FleetServiceId,
97 expected_registry: Box<FleetRegistryVersion>,
98 },
99}
100
101#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
108pub struct RootPeerComponentAllocationRequest {
109 pub operation_id: [u8; 32],
110 pub component_spec: ComponentSpecId,
111 pub requester: PeerComponentRequester,
112}
113
114#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
121pub struct RootComponentAllocationStatusRequest {
122 pub operation_id: [u8; 32],
123}
124
125#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
132pub struct RootComponentChildAllocationRequest {
133 pub operation_id: [u8; 32],
134 pub component: ComponentInstanceId,
135 pub expected_registry: ComponentRegistryHead,
136 pub child_role: CanisterRole,
137 pub application_init_args: Option<Vec<u8>>,
138}
139
140#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
147pub struct RootComponentChildAllocationStatusRequest {
148 pub operation_id: [u8; 32],
149 pub component: ComponentInstanceId,
150}
151
152#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
159pub struct RootComponentSubtreeRemovalRequest {
160 pub operation_id: [u8; 32],
161 pub component: ComponentInstanceId,
162 pub target_canister_id: Principal,
163 pub expected_registry: ComponentRegistryHead,
164}
165
166#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
173pub struct RootComponentSubtreeRemovalAdvanceRequest {
174 pub operation_id: [u8; 32],
175 pub component: ComponentInstanceId,
176 pub expected_traversal_steps: u32,
177}
178
179#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
186pub struct RootComponentSubtreeRemovalStopPreparationRequest {
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 RootComponentSubtreeRemovalStopRequest {
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 RootComponentSubtreeRemovalDeletePreparationRequest {
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 RootComponentSubtreeRemovalDeleteRequest {
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 RootComponentSubtreeRemovalMembershipRemovalRequest {
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 RootComponentSubtreeRemovalDirectorySynchronizationRequest {
262 pub operation_id: [u8; 32],
263 pub component: ComponentInstanceId,
264 pub expected_traversal_steps: u32,
265 pub expected_leaf_canister_id: Principal,
266 pub expected_leaf_parent_canister_id: Principal,
267}
268
269#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
276pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
277 pub operation_id: [u8; 32],
278 pub component: ComponentInstanceId,
279 pub expected_traversal_steps: u32,
280 pub expected_leaf_canister_id: Principal,
281 pub expected_leaf_parent_canister_id: Principal,
282}
283
284#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
291pub struct RootComponentSubtreeRemovalStatusRequest {
292 pub operation_id: [u8; 32],
293 pub component: ComponentInstanceId,
294}
295
296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
303pub struct RootComponentDrainingRequest {
304 pub operation_id: [u8; 32],
305 pub component: ComponentInstanceId,
306 pub expected_registry: ComponentRegistryHead,
307}
308
309#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
316pub struct RootComponentDrainingStatusRequest {
317 pub operation_id: [u8; 32],
318 pub component: ComponentInstanceId,
319}
320
321#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
328pub struct RootComponentQuiescenceRequest {
329 pub operation_id: [u8; 32],
330 pub component: ComponentInstanceId,
331 pub expected_registry: ComponentRegistryHead,
332}
333
334#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
341pub struct RootComponentQuiescenceStatusRequest {
342 pub operation_id: [u8; 32],
343 pub component: ComponentInstanceId,
344}
345
346#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
353pub struct RootComponentDrainingAdvanceRequest {
354 pub operation_id: [u8; 32],
355 pub component: ComponentInstanceId,
356}
357
358#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct RootComponentFinalInventoryRequest {
366 pub operation_id: [u8; 32],
367 pub component: ComponentInstanceId,
368 pub expected_registry: ComponentRegistryHead,
369}
370
371#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
378pub struct RootComponentDeletionRequest {
379 pub operation_id: [u8; 32],
380 pub component: ComponentInstanceId,
381 pub expected_inventory_hash: [u8; 32],
382}
383
384#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
391pub struct RootComponentDeletionStatusRequest {
392 pub operation_id: [u8; 32],
393 pub component: ComponentInstanceId,
394}
395
396#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
403pub struct RootComponentChildCreationRequest {
404 pub operation_id: [u8; 32],
405 pub component: ComponentInstanceId,
406}
407
408#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
415pub struct RootComponentChildInstallRequest {
416 pub operation_id: [u8; 32],
417 pub component: ComponentInstanceId,
418}
419
420#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
427pub struct RootComponentChildCommitRequest {
428 pub operation_id: [u8; 32],
429 pub component: ComponentInstanceId,
430}
431
432#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
439pub struct RootComponentChildDirectoryPreparationRequest {
440 pub operation_id: [u8; 32],
441 pub component: ComponentInstanceId,
442}
443
444#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
451pub struct RootComponentChildRuntimeActivationRequest {
452 pub operation_id: [u8; 32],
453 pub component: ComponentInstanceId,
454}
455
456#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
463pub struct RootComponentChildMembershipActivationRequest {
464 pub operation_id: [u8; 32],
465 pub component: ComponentInstanceId,
466}
467
468#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
475pub struct RootComponentCreationRequest {
476 pub operation_id: [u8; 32],
477}
478
479#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
486pub struct RootComponentInstallRequest {
487 pub operation_id: [u8; 32],
488}
489
490#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
497pub struct RootComponentCommitRequest {
498 pub operation_id: [u8; 32],
499}
500
501#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
508pub struct RootComponentDirectoryPreparationRequest {
509 pub operation_id: [u8; 32],
510}
511
512#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
519pub struct RootComponentRuntimeActivationRequest {
520 pub operation_id: [u8; 32],
521}
522
523#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
530pub struct RootComponentMembershipActivationRequest {
531 pub operation_id: [u8; 32],
532}
533
534#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
541pub enum ComponentProvisioningOrigin {
542 FleetAdministrator {
543 caller: Principal,
544 },
545 Component {
546 requester: Box<ComponentBinding>,
547 grant: Box<crate::config::ComponentProvisioningGrant>,
548 },
549 FleetServiceComponent {
550 requester: Box<FleetServiceComponentRequester>,
551 registry: Box<FleetRegistryVersion>,
552 grant: Box<crate::config::ComponentProvisioningGrant>,
553 },
554 ComponentGroup {
555 operation_id: [u8; 32],
556 plan_hash: [u8; 32],
557 group_placement: ComponentGroupPlacementId,
558 member_path: ComponentGroupMemberPath,
559 },
560}
561
562#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
569pub struct FleetServiceComponentRequester {
570 pub service: FleetServiceId,
571 pub member_purpose: FleetServiceMemberPurpose,
572 pub group_placement: ComponentGroupPlacementId,
573 pub member_path: ComponentGroupMemberPath,
574 pub component: ComponentBinding,
575}
576
577#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
584pub enum RootComponentAllocationPhase {
585 Reserved,
586 CreationIntent,
587 Created,
588 InstallIntent,
589 Installed,
590 Verified,
591 Committed,
592 Removed,
593}
594
595#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
602#[expect(
603 clippy::large_enum_variant,
604 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
605)]
606pub enum RootComponentSubtreeRemovalPhase {
607 Fenced,
608 Traversing(RootComponentSubtreeRemovalNode),
609 LeafSelected(RootComponentSubtreeRemovalNode),
610 StopIntent(RootComponentSubtreeRemovalStopIntent),
611 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
612 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
613 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
614 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
615 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
616 Completed(RootComponentSubtreeRemovalCompletedReceipt),
617}
618
619#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
626pub struct RootComponentSubtreeRemovalNode {
627 pub canister_id: Principal,
628 pub parent_canister_id: Principal,
629 pub role: CanisterRole,
630 pub kind: ComponentChildKind,
631 pub installed_artifact_hash: [u8; 32],
632 pub status: ComponentLifecycleStatus,
633}
634
635#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub struct RootComponentSubtreeRemovalStopIntent {
643 pub leaf: RootComponentSubtreeRemovalNode,
644 pub controller: Principal,
645}
646
647#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
654pub struct RootComponentSubtreeRemovalStoppedReceipt {
655 pub stop: RootComponentSubtreeRemovalStopIntent,
656 pub observed_module_hash: [u8; 32],
657}
658
659#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
666pub struct RootComponentSubtreeRemovalDeleteIntent {
667 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
668}
669
670#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
677pub struct RootComponentSubtreeRemovalDeletedReceipt {
678 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
679}
680
681#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
688pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
689 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
690 pub removed_from_registry: ComponentRegistryHead,
691 pub previous_descendant_content_hash: [u8; 32],
692 pub previous_committed_descendants: u32,
693 pub registry: ComponentRegistryHead,
694 pub descendant_content_hash: [u8; 32],
695 pub registry_encoded_bytes: u64,
696 pub reserved_descendants: u32,
697 pub committed_descendants: u32,
698 pub directory_synchronized_at_ns: u64,
699 pub directory_authority_hash: [u8; 32],
700 pub parent_role_instances: u32,
701 pub root_managed_descendants: u32,
702 pub root_known_created_component_canisters: u32,
703}
704
705#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
712pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
713 pub operation_id: [u8; 32],
714 pub canister_id: Principal,
715 pub activation: ComponentRuntimeActivationEvidence,
716}
717
718#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
727pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
728 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
729 pub covered_fleet_registry_revision: u64,
730 pub covered_fleet_registry_content_hash: [u8; 32],
731 pub covered_component_registry: ComponentRegistryHead,
732 pub covered_authority_hash: [u8; 32],
733 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
734 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
735}
736
737#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
744pub struct RootComponentSubtreeRemovalCompletedReceipt {
745 pub registry: ComponentRegistryHead,
746 pub directory_authority_hash: [u8; 32],
747}
748
749#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
756pub enum ComponentLifecycleStatus {
757 Prepared,
758 Active,
759 Draining,
760 Removed,
761}
762
763#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
770pub struct ComponentRegistryHead {
771 pub component: ComponentInstanceId,
772 pub revision: u64,
773 pub content_hash: [u8; 32],
774}
775
776#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
783pub struct ComponentRegistryPartitionRequest {
784 pub component: ComponentInstanceId,
785}
786
787#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
794pub struct ComponentRegistryPartitionResponse {
795 pub head: ComponentRegistryHead,
796 pub binding: ComponentBinding,
797 pub protocol_profile_digest: ProtocolProfileDigest,
798 pub provisioning_origin: ComponentProvisioningOrigin,
799 pub release_set: FleetSubnetRootReleaseSet,
800 pub status: ComponentLifecycleStatus,
801 pub reserved_descendants: u32,
802 pub committed_descendants: u32,
803 pub encoded_bytes: u64,
804}
805
806#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
813pub struct ComponentDirectoryProvenance {
814 pub component: ComponentBinding,
815 pub source_fleet_subnet_root: Principal,
816 pub component_registry_revision: u64,
817 pub component_registry_content_hash: [u8; 32],
818 pub synchronized_at_ns: u64,
819}
820
821#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
828pub struct ComponentDirectoryHead {
829 pub provenance: ComponentDirectoryProvenance,
830 pub descendant_count: u32,
831}
832
833#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
840pub struct ComponentDirectoryHeadRequest {
841 pub component: ComponentInstanceId,
842}
843
844#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
851pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
852
853#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
860pub struct ComponentDirectoryPageRequest {
861 pub directory: ComponentDirectoryHead,
862 pub parent_canister_id: Option<Principal>,
863 pub role: Option<CanisterRole>,
864 pub status: Option<ComponentLifecycleStatus>,
865 pub cursor: Option<ComponentDirectoryPageCursor>,
866 pub limit: u16,
867}
868
869#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
876pub struct ComponentDirectoryChildEntry {
877 pub binding: ComponentChildBinding,
878 pub kind: ComponentChildKind,
879 pub installed_artifact_hash: [u8; 32],
880 pub protocol_profile_digest: ProtocolProfileDigest,
881 pub status: ComponentLifecycleStatus,
882}
883
884#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
891pub struct ComponentDirectoryPageResponse {
892 pub directory: ComponentDirectoryHead,
893 pub entries: Vec<ComponentDirectoryChildEntry>,
894 pub next_cursor: Option<ComponentDirectoryPageCursor>,
895}
896
897#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
904pub struct ComponentRuntimeDirectoryAuthority {
905 pub fleet: FleetDirectorySnapshot,
906 pub component: ComponentDirectoryHead,
907 pub component_group: Option<crate::dto::component_provisioning::ComponentGroupDirectory>,
908}
909
910#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
917pub struct ComponentRuntimeDirectChild {
918 pub canister_id: Principal,
919 pub role: CanisterRole,
920 pub protocol_profile_digest: ProtocolProfileDigest,
921}
922
923#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
930pub struct ComponentRuntimeDirectoryPreparationRequest {
931 pub operation_id: [u8; 32],
932 pub authority: ComponentRuntimeDirectoryAuthority,
933 pub direct_children: Vec<ComponentRuntimeDirectChild>,
934}
935
936#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
943pub struct ComponentRuntimeDirectorySynchronizationRequest {
944 pub operation_id: [u8; 32],
945 pub authority: ComponentRuntimeDirectoryAuthority,
946 pub direct_children: Vec<ComponentRuntimeDirectChild>,
947}
948
949#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
956pub enum ComponentRuntimePhase {
957 AwaitingDirectory,
958 DirectoryPrepared,
959 Active,
960}
961
962#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
969pub struct ComponentRuntimeActivationEvidence {
970 pub directory_authority_hash: [u8; 32],
971 pub activated_at_ns: u64,
972}
973
974#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
981pub struct ComponentRuntimeActivationRequest {
982 pub operation_id: [u8; 32],
983 pub directory_authority_hash: [u8; 32],
984}
985
986#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
993pub struct ComponentRuntimeStatusResponse {
994 pub operation_id: [u8; 32],
995 pub binding: ManagedCanisterBinding,
996 pub deployment: Box<ProtectedComponentDeployment>,
997 pub phase: ComponentRuntimePhase,
998 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
999 pub authority_hash: Option<[u8; 32]>,
1000 pub direct_children_hash: Option<[u8; 32]>,
1001 pub activation: Option<ComponentRuntimeActivationEvidence>,
1002}
1003
1004#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1011pub struct ComponentRuntimeDirectoryConvergenceEvidence {
1012 pub operation_id: [u8; 32],
1013 pub binding: ManagedCanisterBinding,
1014 pub covered_authority: ComponentRuntimeDirectoryAuthority,
1015 pub covered_authority_hash: [u8; 32],
1016 pub activation: ComponentRuntimeActivationEvidence,
1017}
1018
1019#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1026pub struct RootComponentCreationEvidence {
1027 pub wasm_store: Principal,
1028 pub payload_hash: [u8; 32],
1029 pub payload_size_bytes: u64,
1030 pub initial_cycles: Cycles,
1031 pub controller: Principal,
1032 pub canister: Option<Principal>,
1033}
1034
1035#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1042pub struct RootComponentInstallEvidence {
1043 pub raw_module_hash: [u8; 32],
1044 pub chunk_hashes: Vec<Vec<u8>>,
1045 pub binding: ComponentBinding,
1046}
1047
1048#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1055pub struct RootComponentChildInstallEvidence {
1056 pub raw_module_hash: [u8; 32],
1057 pub chunk_hashes: Vec<Vec<u8>>,
1058 pub binding: ComponentChildBinding,
1059}
1060
1061#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1068pub struct RootComponentAllocationResponse {
1069 pub operation_id: [u8; 32],
1070 pub allocation_sequence: u64,
1071 pub component: ComponentInstanceId,
1072 pub component_spec: ComponentSpecId,
1073 pub spec_hash: [u8; 32],
1074 pub role: CanisterRole,
1075 pub provisioning_origin: ComponentProvisioningOrigin,
1076 pub release_set: FleetSubnetRootReleaseSet,
1077 pub phase: RootComponentAllocationPhase,
1078 pub creation: Option<RootComponentCreationEvidence>,
1079 pub installation: Option<RootComponentInstallEvidence>,
1080}
1081
1082#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1089pub struct RootComponentChildAllocationResponse {
1090 pub operation_id: [u8; 32],
1091 pub component: ComponentInstanceId,
1092 pub parent_canister_id: Principal,
1093 pub parent_role: CanisterRole,
1094 pub child_role: CanisterRole,
1095 pub child_kind: ComponentChildKind,
1096 pub maximum_instances_per_parent: u32,
1097 pub maximum_descendants: u32,
1098 pub maximum_registry_bytes: u64,
1099 pub reserved_against_registry: ComponentRegistryHead,
1100 pub release_set: FleetSubnetRootReleaseSet,
1101 pub phase: RootComponentAllocationPhase,
1102 pub creation: Option<RootComponentCreationEvidence>,
1103 pub installation: Option<RootComponentChildInstallEvidence>,
1104}
1105
1106#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1113pub struct RootComponentSubtreeRemovalResponse {
1114 pub operation_id: [u8; 32],
1115 pub component: ComponentInstanceId,
1116 pub target_canister_id: Principal,
1117 pub target_parent_canister_id: Principal,
1118 pub target_role: CanisterRole,
1119 pub target_status: ComponentLifecycleStatus,
1120 pub reserved_against_registry: ComponentRegistryHead,
1121 pub maximum_completed_leaves: u32,
1122 pub completed_leaves: u32,
1123 pub traversal_steps: u32,
1124 pub phase: RootComponentSubtreeRemovalPhase,
1125}
1126
1127#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1134pub struct RootComponentDrainingResponse {
1135 pub operation_id: [u8; 32],
1136 pub component: ComponentInstanceId,
1137 pub previous_registry: ComponentRegistryHead,
1138 pub registry: ComponentRegistryHead,
1139 pub descendant_count: u32,
1140 pub descendant_content_hash: [u8; 32],
1141 pub directory_authority_hash: [u8; 32],
1142 pub started_at_ns: u64,
1143}
1144
1145#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1152pub struct RootComponentQuiescenceStopIntent {
1153 pub registry: ComponentRegistryHead,
1154 pub descendant_count: u32,
1155 pub descendant_content_hash: [u8; 32],
1156 pub canister_id: Principal,
1157 pub controller: Principal,
1158 pub expected_module_hash: [u8; 32],
1159 pub covered_fleet_registry_revision: u64,
1160 pub covered_fleet_registry_content_hash: [u8; 32],
1161 pub covered_authority_hash: [u8; 32],
1162 pub runtime_operation_id: [u8; 32],
1163 pub activation: ComponentRuntimeActivationEvidence,
1164 pub prepared_at_ns: u64,
1165}
1166
1167#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1174pub struct RootComponentQuiescentReceipt {
1175 pub stop: RootComponentQuiescenceStopIntent,
1176 pub observed_module_hash: [u8; 32],
1177 pub quiesced_at_ns: u64,
1178}
1179
1180#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1187pub enum RootComponentQuiescencePhase {
1188 StopIntent(RootComponentQuiescenceStopIntent),
1189 Quiescent(RootComponentQuiescentReceipt),
1190}
1191
1192#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1199pub struct RootComponentQuiescenceResponse {
1200 pub operation_id: [u8; 32],
1201 pub component: ComponentInstanceId,
1202 pub phase: RootComponentQuiescencePhase,
1203}
1204
1205#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1212pub struct RootComponentDrainingDescendantsEmpty {
1213 pub registry: ComponentRegistryHead,
1214 pub descendant_content_hash: [u8; 32],
1215}
1216
1217#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1224#[expect(
1225 clippy::large_enum_variant,
1226 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1227)]
1228pub enum RootComponentDrainingAdvancePhase {
1229 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1230 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1231}
1232
1233#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1240pub struct RootComponentDrainingAdvanceResponse {
1241 pub operation_id: [u8; 32],
1242 pub component: ComponentInstanceId,
1243 pub phase: RootComponentDrainingAdvancePhase,
1244}
1245
1246#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1253pub struct RootComponentFinalInventory {
1254 pub registry: ComponentRegistryHead,
1255 pub descendant_content_hash: [u8; 32],
1256 pub registry_encoded_bytes: u64,
1257 pub directory_synchronized_at_ns: u64,
1258 pub covered_fleet_registry_revision: u64,
1259 pub covered_fleet_registry_content_hash: [u8; 32],
1260 pub directory_authority_hash: [u8; 32],
1261 pub inventory_hash: [u8; 32],
1262 pub finalized_at_ns: u64,
1263}
1264
1265#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1272pub struct RootComponentFinalInventoryResponse {
1273 pub operation_id: [u8; 32],
1274 pub component: ComponentInstanceId,
1275 pub inventory: RootComponentFinalInventory,
1276}
1277
1278#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1285pub struct RootComponentDeletionIntent {
1286 pub final_inventory: RootComponentFinalInventory,
1287 pub quiescence: RootComponentQuiescentReceipt,
1288 pub prepared_at_ns: u64,
1289}
1290
1291#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1298pub struct RootComponentDeletedReceipt {
1299 pub deletion: RootComponentDeletionIntent,
1300 pub deleted_at_ns: u64,
1301}
1302
1303#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1310pub struct RootComponentMembershipRemovedReceipt {
1311 pub deleted: RootComponentDeletedReceipt,
1312 pub allocation_operation_id: [u8; 32],
1313 pub remaining_spec_committed_instances: u32,
1314 pub root_committed_component_instances: u32,
1315 pub root_known_created_component_canisters: u32,
1316 pub root_registry_encoded_bytes: u64,
1317 pub removed_at_ns: u64,
1318 pub removal_hash: [u8; 32],
1319}
1320
1321#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1328pub enum RootComponentDeletionPhase {
1329 DeleteIntent(RootComponentDeletionIntent),
1330 Deleted(RootComponentDeletedReceipt),
1331 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1332}
1333
1334#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1341pub struct RootComponentDeletionResponse {
1342 pub operation_id: [u8; 32],
1343 pub component: ComponentInstanceId,
1344 pub phase: RootComponentDeletionPhase,
1345}
1346
1347#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1354pub struct RootComponentChildCommitResponse {
1355 pub allocation: RootComponentChildAllocationResponse,
1356 pub registry: ComponentRegistryPartitionResponse,
1357 pub directory: ComponentDirectoryHead,
1358}
1359
1360#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1367pub struct RootComponentChildDirectoryPreparationResponse {
1368 pub committed: RootComponentChildCommitResponse,
1369 pub child: ComponentRuntimeStatusResponse,
1370 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1371 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1372}
1373
1374#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1381pub struct RootComponentChildRuntimeActivationResponse {
1382 pub committed: RootComponentChildCommitResponse,
1383 pub child: ComponentRuntimeStatusResponse,
1384}
1385
1386#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1393pub struct RootComponentChildMembershipActivationResponse {
1394 pub committed: RootComponentChildCommitResponse,
1395 pub registry: ComponentRegistryPartitionResponse,
1396 pub directory: ComponentDirectoryHead,
1397 pub child: ComponentRuntimeStatusResponse,
1398}
1399
1400#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1407pub struct RootComponentCommitResponse {
1408 pub allocation: RootComponentAllocationResponse,
1409 pub registry: ComponentRegistryPartitionResponse,
1410 pub directory: ComponentDirectoryHead,
1411}
1412
1413#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1420pub struct RootComponentDirectoryPreparationResponse {
1421 pub committed: RootComponentCommitResponse,
1422 pub target: ComponentRuntimeStatusResponse,
1423}
1424
1425#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1432pub struct RootComponentRuntimeActivationResponse {
1433 pub committed: RootComponentCommitResponse,
1434 pub target: ComponentRuntimeStatusResponse,
1435}
1436
1437#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1444pub struct RootComponentMembershipActivationResponse {
1445 pub allocation: RootComponentAllocationResponse,
1446 pub registry: ComponentRegistryPartitionResponse,
1447 pub directory: ComponentDirectoryHead,
1448 pub target: ComponentRuntimeStatusResponse,
1449}
1450
1451#[cfg(test)]
1452mod tests {
1453 use super::*;
1454 use crate::{
1455 dto::root_store::RootStoreBootstrapRequest,
1456 ids::{
1457 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1458 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1459 },
1460 };
1461
1462 #[test]
1463 fn component_registry_contracts_round_trip_through_candid() {
1464 let request = RootComponentRegistryPreparationRequest {
1465 store_bootstrap: RootStoreBootstrapRequest {
1466 operation_id: [8; 32],
1467 manifest_payload_size_bytes: 128,
1468 },
1469 expected_fleet_registry: FleetRegistryVersion {
1470 authority: fleet_registry_authority(),
1471 revision: 4,
1472 content_hash: [5; 32],
1473 },
1474 };
1475 let response = RootComponentRegistryStatusResponse {
1476 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1477 prepared_against_registry: request.expected_fleet_registry.clone(),
1478 release_set: FleetSubnetRootReleaseSet {
1479 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1480 [7; 32],
1481 )),
1482 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1483 },
1484 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1485 next_allocation_sequence: 1,
1486 reserved_component_instances: 0,
1487 committed_component_instances: 0,
1488 managed_descendants: 0,
1489 known_created_component_canisters: 0,
1490 encoded_bytes: 0,
1491 initial_inventory: Some(RootComponentInitialInventoryStatus {
1492 fleet_activation_operation_id: [10; 32],
1493 component_count: 0,
1494 inventory_hash: [11; 32],
1495 sealed_at_ns: 12,
1496 directories_converged: true,
1497 root_runtime_activated: true,
1498 }),
1499 };
1500 let allocation = RootComponentAllocationResponse {
1501 operation_id: [10; 32],
1502 allocation_sequence: 1,
1503 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1504 component_spec: "projects".parse().expect("Component Spec ID"),
1505 spec_hash: [12; 32],
1506 role: CanisterRole::new("project_hub"),
1507 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1508 caller: Principal::from_slice(&[13; 29]),
1509 },
1510 release_set: response.release_set,
1511 phase: RootComponentAllocationPhase::Reserved,
1512 creation: None,
1513 installation: None,
1514 };
1515 let created = RootComponentAllocationResponse {
1516 phase: RootComponentAllocationPhase::Created,
1517 creation: Some(RootComponentCreationEvidence {
1518 wasm_store: Principal::from_slice(&[14; 29]),
1519 payload_hash: [15; 32],
1520 payload_size_bytes: 4_096,
1521 initial_cycles: Cycles::new(5_000_000_000_000),
1522 controller: Principal::from_slice(&[6; 29]),
1523 canister: Some(Principal::from_slice(&[16; 29])),
1524 }),
1525 installation: None,
1526 ..allocation.clone()
1527 };
1528 let request_bytes = candid::encode_one(&request).expect("encode request");
1529 let response_bytes = candid::encode_one(&response).expect("encode response");
1530 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1531 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1532
1533 assert_eq!(
1534 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1535 .expect("decode request"),
1536 request
1537 );
1538 assert_eq!(
1539 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1540 .expect("decode response"),
1541 response
1542 );
1543 assert_eq!(
1544 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1545 .expect("decode allocation"),
1546 allocation
1547 );
1548 assert_eq!(
1549 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1550 .expect("decode created allocation"),
1551 created
1552 );
1553 }
1554
1555 #[test]
1556 fn component_commit_response_round_trips_through_candid() {
1557 let root = Principal::from_slice(&[6; 29]);
1558 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1559 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1560 let release_set = FleetSubnetRootReleaseSet {
1561 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1562 [7; 32],
1563 )),
1564 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1565 };
1566 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1567 caller: Principal::from_slice(&[13; 29]),
1568 };
1569 let binding = ComponentBinding {
1570 authority: fleet_registry_authority(),
1571 component,
1572 component_spec: component_spec.clone(),
1573 spec_hash: [12; 32],
1574 role: CanisterRole::new("project_hub"),
1575 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1576 fleet_subnet_root: root,
1577 canister_id: Principal::from_slice(&[16; 29]),
1578 };
1579 let head = ComponentRegistryHead {
1580 component,
1581 revision: 1,
1582 content_hash: [18; 32],
1583 };
1584 let committed = RootComponentCommitResponse {
1585 allocation: RootComponentAllocationResponse {
1586 operation_id: [10; 32],
1587 allocation_sequence: 1,
1588 component,
1589 component_spec,
1590 spec_hash: binding.spec_hash,
1591 role: binding.role.clone(),
1592 provisioning_origin: provisioning_origin.clone(),
1593 release_set,
1594 phase: RootComponentAllocationPhase::Committed,
1595 creation: Some(RootComponentCreationEvidence {
1596 wasm_store: Principal::from_slice(&[14; 29]),
1597 payload_hash: [15; 32],
1598 payload_size_bytes: 4_096,
1599 initial_cycles: Cycles::new(5_000_000_000_000),
1600 controller: root,
1601 canister: Some(binding.canister_id),
1602 }),
1603 installation: Some(RootComponentInstallEvidence {
1604 raw_module_hash: [20; 32],
1605 chunk_hashes: vec![vec![21; 32]],
1606 binding: binding.clone(),
1607 }),
1608 },
1609 registry: ComponentRegistryPartitionResponse {
1610 head: head.clone(),
1611 binding: binding.clone(),
1612 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1613 provisioning_origin,
1614 release_set,
1615 status: ComponentLifecycleStatus::Prepared,
1616 reserved_descendants: 0,
1617 committed_descendants: 0,
1618 encoded_bytes: 2_048,
1619 },
1620 directory: ComponentDirectoryHead {
1621 provenance: ComponentDirectoryProvenance {
1622 component: binding,
1623 source_fleet_subnet_root: root,
1624 component_registry_revision: head.revision,
1625 component_registry_content_hash: head.content_hash,
1626 synchronized_at_ns: 19,
1627 },
1628 descendant_count: 0,
1629 },
1630 };
1631 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1632
1633 assert_eq!(
1634 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1635 .expect("decode committed allocation"),
1636 committed
1637 );
1638 }
1639
1640 #[test]
1641 fn component_directory_page_contracts_round_trip_through_candid() {
1642 let root = Principal::from_slice(&[6; 29]);
1643 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1644 let binding = ComponentBinding {
1645 authority: fleet_registry_authority(),
1646 component,
1647 component_spec: "projects".parse().expect("Component Spec ID"),
1648 spec_hash: [12; 32],
1649 role: CanisterRole::new("project_hub"),
1650 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1651 fleet_subnet_root: root,
1652 canister_id: Principal::from_slice(&[16; 29]),
1653 };
1654 let directory = ComponentDirectoryHead {
1655 provenance: ComponentDirectoryProvenance {
1656 component: binding.clone(),
1657 source_fleet_subnet_root: root,
1658 component_registry_revision: 3,
1659 component_registry_content_hash: [18; 32],
1660 synchronized_at_ns: 19,
1661 },
1662 descendant_count: 1,
1663 };
1664 let request = ComponentDirectoryPageRequest {
1665 directory: directory.clone(),
1666 parent_canister_id: Some(binding.canister_id),
1667 role: Some(CanisterRole::new("project_instance")),
1668 status: Some(ComponentLifecycleStatus::Active),
1669 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1670 limit: 50,
1671 };
1672 let response = ComponentDirectoryPageResponse {
1673 directory,
1674 entries: vec![ComponentDirectoryChildEntry {
1675 binding: ComponentChildBinding {
1676 component: binding.clone(),
1677 parent_canister_id: binding.canister_id,
1678 role: CanisterRole::new("project_instance"),
1679 canister_id: Principal::from_slice(&[21; 29]),
1680 },
1681 kind: ComponentChildKind::Instance,
1682 installed_artifact_hash: [22; 32],
1683 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1684 status: ComponentLifecycleStatus::Active,
1685 }],
1686 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1687 };
1688 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1689 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1690
1691 assert_eq!(
1692 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1693 .expect("decode Directory page request"),
1694 request
1695 );
1696 assert_eq!(
1697 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1698 .expect("decode Directory page response"),
1699 response
1700 );
1701 }
1702
1703 fn fleet_registry_authority() -> FleetRegistryAuthority {
1704 FleetRegistryAuthority {
1705 binding: FleetCoordinatorBinding {
1706 fleet: crate::ids::FleetBinding {
1707 fleet: FleetKey {
1708 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1709 fleet_id: FleetId::from_generated_bytes([1; 32]),
1710 },
1711 app: AppId::from("toko"),
1712 },
1713 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1714 coordinator: Principal::from_slice(&[3; 29]),
1715 },
1716 epoch: 1,
1717 }
1718 }
1719
1720 #[test]
1721 fn component_creation_request_round_trips_through_candid() {
1722 let request = RootComponentCreationRequest {
1723 operation_id: [10; 32],
1724 };
1725 let bytes = candid::encode_one(request).expect("encode creation request");
1726
1727 assert_eq!(
1728 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1729 .expect("decode creation request"),
1730 request
1731 );
1732 }
1733
1734 #[test]
1735 fn peer_component_provisioning_origin_round_trips_through_candid() {
1736 let authority = fleet_registry_authority();
1737 let requester_spec: ComponentSpecId =
1738 "projects".parse().expect("requester Component Spec ID");
1739 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1740 let origin = ComponentProvisioningOrigin::Component {
1741 requester: Box::new(ComponentBinding {
1742 authority,
1743 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1744 component_spec: requester_spec.clone(),
1745 spec_hash: [21; 32],
1746 role: CanisterRole::new("project_hub"),
1747 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1748 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1749 canister_id: Principal::from_slice(&[24; 29]),
1750 }),
1751 grant: Box::new(crate::config::ComponentProvisioningGrant {
1752 requester_component_spec: requester_spec,
1753 target_component_spec: target_spec,
1754 maximum_instances_per_requester_per_root: 3,
1755 }),
1756 };
1757 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1758
1759 assert_eq!(
1760 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1761 .expect("decode peer provisioning origin"),
1762 origin
1763 );
1764 }
1765
1766 #[test]
1767 fn cross_root_peer_request_and_origin_round_trip_through_candid() {
1768 let authority = fleet_registry_authority();
1769 let requester_spec: ComponentSpecId =
1770 "projects".parse().expect("requester Component Spec ID");
1771 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1772 let registry = FleetRegistryVersion {
1773 authority: authority.clone(),
1774 revision: 7,
1775 content_hash: [30; 32],
1776 };
1777 let request = RootPeerComponentAllocationRequest {
1778 operation_id: [31; 32],
1779 component_spec: target_spec.clone(),
1780 requester: PeerComponentRequester::FleetService {
1781 service: "projects".parse().expect("Fleet service ID"),
1782 expected_registry: Box::new(registry.clone()),
1783 },
1784 };
1785 let origin = ComponentProvisioningOrigin::FleetServiceComponent {
1786 requester: Box::new(FleetServiceComponentRequester {
1787 service: "projects".parse().expect("Fleet service ID"),
1788 member_purpose: FleetServiceMemberPurpose::PoolMember,
1789 group_placement: ComponentGroupPlacementId {
1790 deployment: "project_hubs".parse().expect("deployment ID"),
1791 ordinal: 2,
1792 },
1793 member_path: ComponentGroupMemberPath::try_from(vec![
1794 "hub".parse().expect("member ID"),
1795 ])
1796 .expect("member path"),
1797 component: ComponentBinding {
1798 authority,
1799 component: ComponentInstanceId::from_generated_bytes([32; 32]),
1800 component_spec: requester_spec.clone(),
1801 spec_hash: [33; 32],
1802 role: CanisterRole::new("project_hub"),
1803 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[34; 29])),
1804 fleet_subnet_root: Principal::from_slice(&[35; 29]),
1805 canister_id: Principal::from_slice(&[36; 29]),
1806 },
1807 }),
1808 registry: Box::new(registry),
1809 grant: Box::new(crate::config::ComponentProvisioningGrant {
1810 requester_component_spec: requester_spec,
1811 target_component_spec: target_spec,
1812 maximum_instances_per_requester_per_root: 3,
1813 }),
1814 };
1815 let request_bytes = candid::encode_one(&request).expect("encode cross-root request");
1816 let origin_bytes = candid::encode_one(&origin).expect("encode cross-root origin");
1817
1818 assert_eq!(
1819 candid::decode_one::<RootPeerComponentAllocationRequest>(&request_bytes)
1820 .expect("decode cross-root request"),
1821 request
1822 );
1823 assert_eq!(
1824 candid::decode_one::<ComponentProvisioningOrigin>(&origin_bytes)
1825 .expect("decode cross-root origin"),
1826 origin
1827 );
1828 }
1829
1830 #[test]
1831 fn component_group_provisioning_origin_round_trips_through_candid() {
1832 let origin = ComponentProvisioningOrigin::ComponentGroup {
1833 operation_id: [25; 32],
1834 plan_hash: [26; 32],
1835 group_placement: ComponentGroupPlacementId {
1836 deployment: "cells".parse().expect("deployment ID"),
1837 ordinal: 3,
1838 },
1839 member_path: ComponentGroupMemberPath::try_from(vec![
1840 "api".parse().expect("member ID"),
1841 ])
1842 .expect("member path"),
1843 };
1844 let bytes = candid::encode_one(&origin).expect("encode group provisioning origin");
1845
1846 assert_eq!(
1847 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1848 .expect("decode group provisioning origin"),
1849 origin
1850 );
1851 }
1852
1853 #[test]
1854 #[expect(
1855 clippy::too_many_lines,
1856 reason = "one Candid contract test covers every subtree-removal phase receipt"
1857 )]
1858 fn component_subtree_removal_contracts_round_trip_through_candid() {
1859 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1860 let registry = ComponentRegistryHead {
1861 component,
1862 revision: 7,
1863 content_hash: [42; 32],
1864 };
1865 let request = RootComponentSubtreeRemovalRequest {
1866 operation_id: [43; 32],
1867 component,
1868 target_canister_id: Principal::from_slice(&[44; 29]),
1869 expected_registry: registry.clone(),
1870 };
1871 let status_request = RootComponentSubtreeRemovalStatusRequest {
1872 operation_id: request.operation_id,
1873 component,
1874 };
1875 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1876 operation_id: request.operation_id,
1877 component,
1878 expected_traversal_steps: 1,
1879 };
1880 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1881 operation_id: request.operation_id,
1882 component,
1883 expected_traversal_steps: 2,
1884 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1885 expected_leaf_parent_canister_id: request.target_canister_id,
1886 };
1887 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1888 observed_module_hash: [49; 32],
1889 stop: RootComponentSubtreeRemovalStopIntent {
1890 controller: Principal::from_slice(&[48; 29]),
1891 leaf: RootComponentSubtreeRemovalNode {
1892 canister_id: Principal::from_slice(&[46; 29]),
1893 parent_canister_id: request.target_canister_id,
1894 role: CanisterRole::new("project_ledger"),
1895 kind: ComponentChildKind::Singleton,
1896 installed_artifact_hash: [47; 32],
1897 status: ComponentLifecycleStatus::Active,
1898 },
1899 },
1900 };
1901 let response = RootComponentSubtreeRemovalResponse {
1902 operation_id: request.operation_id,
1903 component,
1904 target_canister_id: request.target_canister_id,
1905 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1906 target_role: CanisterRole::new("project_instance"),
1907 target_status: ComponentLifecycleStatus::Active,
1908 reserved_against_registry: registry,
1909 maximum_completed_leaves: 4,
1910 completed_leaves: 1,
1911 traversal_steps: 2,
1912 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1913 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1914 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1915 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1916 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1917 },
1918 removed_from_registry: ComponentRegistryHead {
1919 component,
1920 revision: 8,
1921 content_hash: [50; 32],
1922 },
1923 previous_descendant_content_hash: [51; 32],
1924 previous_committed_descendants: 4,
1925 registry: ComponentRegistryHead {
1926 component,
1927 revision: 9,
1928 content_hash: [52; 32],
1929 },
1930 descendant_content_hash: [53; 32],
1931 registry_encoded_bytes: 4_096,
1932 reserved_descendants: 1,
1933 committed_descendants: 3,
1934 directory_synchronized_at_ns: 54,
1935 directory_authority_hash: [55; 32],
1936 parent_role_instances: 0,
1937 root_managed_descendants: 4,
1938 root_known_created_component_canisters: 4,
1939 },
1940 covered_fleet_registry_revision: 6,
1941 covered_fleet_registry_content_hash: [56; 32],
1942 covered_component_registry: ComponentRegistryHead {
1943 component,
1944 revision: 9,
1945 content_hash: [52; 32],
1946 },
1947 covered_authority_hash: [55; 32],
1948 owning_component: Some(
1949 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1950 operation_id: [57; 32],
1951 canister_id: Principal::from_slice(&[58; 29]),
1952 activation: ComponentRuntimeActivationEvidence {
1953 directory_authority_hash: [59; 32],
1954 activated_at_ns: 60,
1955 },
1956 },
1957 ),
1958 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1959 operation_id: [61; 32],
1960 canister_id: request.target_canister_id,
1961 activation: ComponentRuntimeActivationEvidence {
1962 directory_authority_hash: [62; 32],
1963 activated_at_ns: 63,
1964 },
1965 }),
1966 },
1967 ),
1968 };
1969
1970 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1971 let advance_bytes =
1972 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1973 let stop_bytes =
1974 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1975 let status_bytes =
1976 candid::encode_one(status_request).expect("encode subtree removal status request");
1977 let response_bytes =
1978 candid::encode_one(&response).expect("encode subtree removal response");
1979
1980 assert_eq!(
1981 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1982 .expect("decode subtree removal request"),
1983 request
1984 );
1985 assert_eq!(
1986 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1987 .expect("decode subtree removal advance request"),
1988 advance_request
1989 );
1990 assert_eq!(
1991 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1992 .expect("decode subtree removal stop request"),
1993 stop_request
1994 );
1995 assert_eq!(
1996 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1997 .expect("decode subtree removal status request"),
1998 status_request
1999 );
2000 assert_eq!(
2001 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
2002 .expect("decode subtree removal response"),
2003 response
2004 );
2005
2006 let mut quiescent_owner_response = response;
2007 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
2008 &mut quiescent_owner_response.phase
2009 else {
2010 panic!("Directory-synchronized response");
2011 };
2012 receipt.owning_component = None;
2013 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
2014 .expect("encode quiescent-owner subtree response");
2015 assert_eq!(
2016 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
2017 .expect("decode quiescent-owner subtree response"),
2018 quiescent_owner_response
2019 );
2020 }
2021
2022 #[test]
2023 fn component_draining_contracts_round_trip_through_candid() {
2024 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
2025 let previous_registry = ComponentRegistryHead {
2026 component,
2027 revision: 7,
2028 content_hash: [61; 32],
2029 };
2030 let request = RootComponentDrainingRequest {
2031 operation_id: [62; 32],
2032 component,
2033 expected_registry: previous_registry.clone(),
2034 };
2035 let status_request = RootComponentDrainingStatusRequest {
2036 operation_id: request.operation_id,
2037 component,
2038 };
2039 let response = RootComponentDrainingResponse {
2040 operation_id: request.operation_id,
2041 component,
2042 previous_registry,
2043 registry: ComponentRegistryHead {
2044 component,
2045 revision: 8,
2046 content_hash: [63; 32],
2047 },
2048 descendant_count: 20_000,
2049 descendant_content_hash: [64; 32],
2050 directory_authority_hash: [65; 32],
2051 started_at_ns: 66,
2052 };
2053
2054 let request_bytes =
2055 candid::encode_one(&request).expect("encode Component draining request");
2056 let status_bytes =
2057 candid::encode_one(status_request).expect("encode Component draining status request");
2058 let response_bytes =
2059 candid::encode_one(&response).expect("encode Component draining response");
2060
2061 assert_eq!(
2062 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
2063 .expect("decode Component draining request"),
2064 request
2065 );
2066 assert_eq!(
2067 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
2068 .expect("decode Component draining status request"),
2069 status_request
2070 );
2071 assert_eq!(
2072 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
2073 .expect("decode Component draining response"),
2074 response
2075 );
2076 }
2077
2078 #[test]
2079 fn component_quiescence_contracts_round_trip_through_candid() {
2080 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
2081 let registry = ComponentRegistryHead {
2082 component,
2083 revision: 9,
2084 content_hash: [68; 32],
2085 };
2086 let request = RootComponentQuiescenceRequest {
2087 operation_id: [69; 32],
2088 component,
2089 expected_registry: registry.clone(),
2090 };
2091 let status_request = RootComponentQuiescenceStatusRequest {
2092 operation_id: request.operation_id,
2093 component,
2094 };
2095 let stop = RootComponentQuiescenceStopIntent {
2096 registry,
2097 descendant_count: 20_000,
2098 descendant_content_hash: [70; 32],
2099 canister_id: Principal::from_slice(&[71; 29]),
2100 controller: Principal::from_slice(&[72; 29]),
2101 expected_module_hash: [73; 32],
2102 covered_fleet_registry_revision: 10,
2103 covered_fleet_registry_content_hash: [74; 32],
2104 covered_authority_hash: [75; 32],
2105 runtime_operation_id: [76; 32],
2106 activation: ComponentRuntimeActivationEvidence {
2107 directory_authority_hash: [77; 32],
2108 activated_at_ns: 78,
2109 },
2110 prepared_at_ns: 79,
2111 };
2112 let response = RootComponentQuiescenceResponse {
2113 operation_id: request.operation_id,
2114 component,
2115 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
2116 stop,
2117 observed_module_hash: [73; 32],
2118 quiesced_at_ns: 80,
2119 }),
2120 };
2121
2122 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
2123 let status_bytes =
2124 candid::encode_one(status_request).expect("encode quiescence status request");
2125 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
2126 assert_eq!(
2127 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
2128 .expect("decode quiescence request"),
2129 request
2130 );
2131 assert_eq!(
2132 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
2133 .expect("decode quiescence status request"),
2134 status_request
2135 );
2136 assert_eq!(
2137 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
2138 .expect("decode quiescence response"),
2139 response
2140 );
2141 }
2142
2143 #[test]
2144 fn component_draining_advance_contracts_round_trip_through_candid() {
2145 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
2146 let registry = ComponentRegistryHead {
2147 component,
2148 revision: 12,
2149 content_hash: [82; 32],
2150 };
2151 let request = RootComponentDrainingAdvanceRequest {
2152 operation_id: [83; 32],
2153 component,
2154 };
2155 let descendant_removal = RootComponentDrainingAdvanceResponse {
2156 operation_id: request.operation_id,
2157 component,
2158 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2159 RootComponentSubtreeRemovalResponse {
2160 operation_id: [84; 32],
2161 component,
2162 target_canister_id: Principal::from_slice(&[85; 29]),
2163 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2164 target_role: CanisterRole::new("project_instance"),
2165 target_status: ComponentLifecycleStatus::Active,
2166 reserved_against_registry: registry.clone(),
2167 maximum_completed_leaves: 20_000,
2168 completed_leaves: 0,
2169 traversal_steps: 0,
2170 phase: RootComponentSubtreeRemovalPhase::Fenced,
2171 },
2172 ),
2173 };
2174 let descendants_empty = RootComponentDrainingAdvanceResponse {
2175 operation_id: request.operation_id,
2176 component,
2177 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2178 RootComponentDrainingDescendantsEmpty {
2179 registry,
2180 descendant_content_hash: [87; 32],
2181 },
2182 ),
2183 };
2184
2185 let request_bytes =
2186 candid::encode_one(request).expect("encode Component draining advance request");
2187 let removal_bytes = candid::encode_one(&descendant_removal)
2188 .expect("encode Component draining removal response");
2189 let empty_bytes = candid::encode_one(&descendants_empty)
2190 .expect("encode Component draining empty response");
2191
2192 assert_eq!(
2193 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2194 .expect("decode Component draining advance request"),
2195 request
2196 );
2197 assert_eq!(
2198 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2199 .expect("decode Component draining removal response"),
2200 descendant_removal
2201 );
2202 assert_eq!(
2203 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2204 .expect("decode Component draining empty response"),
2205 descendants_empty
2206 );
2207 }
2208
2209 #[test]
2210 #[expect(
2211 clippy::too_many_lines,
2212 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2213 )]
2214 fn component_final_inventory_contracts_round_trip_through_candid() {
2215 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2216 let registry = ComponentRegistryHead {
2217 component,
2218 revision: 21,
2219 content_hash: [89; 32],
2220 };
2221 let request = RootComponentFinalInventoryRequest {
2222 operation_id: [90; 32],
2223 component,
2224 expected_registry: registry.clone(),
2225 };
2226 let inventory = RootComponentFinalInventory {
2227 registry,
2228 descendant_content_hash: [91; 32],
2229 registry_encoded_bytes: 4_096,
2230 directory_synchronized_at_ns: 92,
2231 covered_fleet_registry_revision: 93,
2232 covered_fleet_registry_content_hash: [94; 32],
2233 directory_authority_hash: [95; 32],
2234 inventory_hash: [96; 32],
2235 finalized_at_ns: 97,
2236 };
2237 let response = RootComponentFinalInventoryResponse {
2238 operation_id: request.operation_id,
2239 component,
2240 inventory: inventory.clone(),
2241 };
2242 let deletion_request = RootComponentDeletionRequest {
2243 operation_id: request.operation_id,
2244 component,
2245 expected_inventory_hash: inventory.inventory_hash,
2246 };
2247 let deletion_status_request = RootComponentDeletionStatusRequest {
2248 operation_id: request.operation_id,
2249 component,
2250 };
2251 let deletion = RootComponentDeletionIntent {
2252 final_inventory: inventory,
2253 quiescence: RootComponentQuiescentReceipt {
2254 stop: RootComponentQuiescenceStopIntent {
2255 registry: response.inventory.registry.clone(),
2256 descendant_count: 0,
2257 descendant_content_hash: response.inventory.descendant_content_hash,
2258 canister_id: Principal::from_slice(&[98; 29]),
2259 controller: Principal::from_slice(&[99; 29]),
2260 expected_module_hash: [100; 32],
2261 covered_fleet_registry_revision: 93,
2262 covered_fleet_registry_content_hash: [94; 32],
2263 covered_authority_hash: [101; 32],
2264 runtime_operation_id: [102; 32],
2265 activation: ComponentRuntimeActivationEvidence {
2266 directory_authority_hash: [103; 32],
2267 activated_at_ns: 104,
2268 },
2269 prepared_at_ns: 105,
2270 },
2271 observed_module_hash: [100; 32],
2272 quiesced_at_ns: 106,
2273 },
2274 prepared_at_ns: 107,
2275 };
2276 let deleted_receipt = RootComponentDeletedReceipt {
2277 deletion,
2278 deleted_at_ns: 108,
2279 };
2280 let deletion_response = RootComponentDeletionResponse {
2281 operation_id: request.operation_id,
2282 component,
2283 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2284 };
2285 let membership_removed_response = RootComponentDeletionResponse {
2286 operation_id: request.operation_id,
2287 component,
2288 phase: RootComponentDeletionPhase::MembershipRemoved(
2289 RootComponentMembershipRemovedReceipt {
2290 deleted: deleted_receipt,
2291 allocation_operation_id: [109; 32],
2292 remaining_spec_committed_instances: 2,
2293 root_committed_component_instances: 3,
2294 root_known_created_component_canisters: 4,
2295 root_registry_encoded_bytes: 5_000,
2296 removed_at_ns: 110,
2297 removal_hash: [111; 32],
2298 },
2299 ),
2300 };
2301
2302 let request_bytes =
2303 candid::encode_one(&request).expect("encode Component final inventory request");
2304 let response_bytes =
2305 candid::encode_one(&response).expect("encode Component final inventory response");
2306 let deletion_request_bytes =
2307 candid::encode_one(deletion_request).expect("encode Component deletion request");
2308 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2309 .expect("encode Component deletion status request");
2310 let deletion_response_bytes =
2311 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2312 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2313 .expect("encode Component membership-removal response");
2314 assert_eq!(
2315 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2316 .expect("decode Component final inventory request"),
2317 request
2318 );
2319 assert_eq!(
2320 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2321 .expect("decode Component final inventory response"),
2322 response
2323 );
2324 assert_eq!(
2325 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2326 .expect("decode Component deletion request"),
2327 deletion_request
2328 );
2329 assert_eq!(
2330 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2331 .expect("decode Component deletion status request"),
2332 deletion_status_request
2333 );
2334 assert_eq!(
2335 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2336 .expect("decode Component deletion response"),
2337 deletion_response
2338 );
2339 assert_eq!(
2340 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2341 .expect("decode Component membership-removal response"),
2342 membership_removed_response
2343 );
2344 }
2345
2346 #[test]
2347 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2348 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2349 operation_id: [50; 32],
2350 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2351 expected_traversal_steps: 3,
2352 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2353 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2354 };
2355 let request = RootComponentSubtreeRemovalDeleteRequest {
2356 operation_id: prepare.operation_id,
2357 component: prepare.component,
2358 expected_traversal_steps: prepare.expected_traversal_steps,
2359 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2360 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2361 };
2362 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2363 operation_id: prepare.operation_id,
2364 component: prepare.component,
2365 expected_traversal_steps: prepare.expected_traversal_steps,
2366 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2367 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2368 };
2369 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2370 operation_id: prepare.operation_id,
2371 component: prepare.component,
2372 expected_traversal_steps: prepare.expected_traversal_steps,
2373 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2374 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2375 };
2376 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2377 operation_id: prepare.operation_id,
2378 component: prepare.component,
2379 expected_traversal_steps: prepare.expected_traversal_steps,
2380 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2381 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2382 };
2383 let prepare_bytes = candid::encode_one(prepare)
2384 .expect("encode subtree removal deletion preparation request");
2385 let request_bytes =
2386 candid::encode_one(request).expect("encode subtree removal deletion request");
2387 let membership_request_bytes = candid::encode_one(membership_request)
2388 .expect("encode subtree removal membership-removal request");
2389 let directory_request_bytes = candid::encode_one(directory_request)
2390 .expect("encode subtree removal Directory synchronization request");
2391 let finalization_request_bytes = candid::encode_one(finalization_request)
2392 .expect("encode subtree removal leaf-finalization request");
2393
2394 assert_eq!(
2395 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2396 &prepare_bytes
2397 )
2398 .expect("decode subtree removal deletion preparation request"),
2399 prepare
2400 );
2401 assert_eq!(
2402 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2403 .expect("decode subtree removal deletion request"),
2404 request
2405 );
2406 assert_eq!(
2407 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2408 &membership_request_bytes
2409 )
2410 .expect("decode subtree removal membership-removal request"),
2411 membership_request
2412 );
2413 assert_eq!(
2414 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2415 &directory_request_bytes
2416 )
2417 .expect("decode subtree removal Directory synchronization request"),
2418 directory_request
2419 );
2420 assert_eq!(
2421 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2422 &finalization_request_bytes
2423 )
2424 .expect("decode subtree removal leaf-finalization request"),
2425 finalization_request
2426 );
2427 }
2428
2429 #[test]
2430 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2431 let request = RootComponentSubtreeRemovalStopRequest {
2432 operation_id: [50; 32],
2433 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2434 expected_traversal_steps: 3,
2435 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2436 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2437 };
2438 let bytes =
2439 candid::encode_one(request).expect("encode subtree removal stop execution request");
2440
2441 assert_eq!(
2442 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2443 .expect("decode subtree removal stop execution request"),
2444 request
2445 );
2446 }
2447
2448 #[test]
2449 #[expect(
2450 clippy::too_many_lines,
2451 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2452 )]
2453 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2454 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2455 let registry = ComponentRegistryHead {
2456 component,
2457 revision: 2,
2458 content_hash: [12; 32],
2459 };
2460 let request = RootComponentChildAllocationRequest {
2461 operation_id: [13; 32],
2462 component,
2463 expected_registry: registry.clone(),
2464 child_role: CanisterRole::new("project_instance"),
2465 application_init_args: Some(vec![9, 8, 7]),
2466 };
2467 let status_request = RootComponentChildAllocationStatusRequest {
2468 operation_id: request.operation_id,
2469 component,
2470 };
2471 let creation_request = RootComponentChildCreationRequest {
2472 operation_id: request.operation_id,
2473 component,
2474 };
2475 let install_request = RootComponentChildInstallRequest {
2476 operation_id: request.operation_id,
2477 component,
2478 };
2479 let commit_request = RootComponentChildCommitRequest {
2480 operation_id: request.operation_id,
2481 component,
2482 };
2483 let directory_request = RootComponentChildDirectoryPreparationRequest {
2484 operation_id: request.operation_id,
2485 component,
2486 };
2487 let activation_request = RootComponentChildRuntimeActivationRequest {
2488 operation_id: request.operation_id,
2489 component,
2490 };
2491 let membership_request = RootComponentChildMembershipActivationRequest {
2492 operation_id: request.operation_id,
2493 component,
2494 };
2495 let root = Principal::from_slice(&[17; 29]);
2496 let parent = Principal::from_slice(&[14; 29]);
2497 let child = Principal::from_slice(&[18; 29]);
2498 let child_binding = ComponentChildBinding {
2499 component: ComponentBinding {
2500 authority: fleet_registry_authority(),
2501 component,
2502 component_spec: "projects".parse().expect("Component Spec"),
2503 spec_hash: [19; 32],
2504 role: CanisterRole::new("project_hub"),
2505 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2506 fleet_subnet_root: root,
2507 canister_id: parent,
2508 },
2509 parent_canister_id: parent,
2510 role: request.child_role.clone(),
2511 canister_id: child,
2512 };
2513 let response = RootComponentChildAllocationResponse {
2514 operation_id: request.operation_id,
2515 component,
2516 parent_canister_id: parent,
2517 parent_role: CanisterRole::new("project_hub"),
2518 child_role: request.child_role.clone(),
2519 child_kind: ComponentChildKind::Instance,
2520 maximum_instances_per_parent: 10_000,
2521 maximum_descendants: 20_000,
2522 maximum_registry_bytes: 16_777_216,
2523 reserved_against_registry: registry,
2524 release_set: FleetSubnetRootReleaseSet {
2525 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2526 [15; 32],
2527 )),
2528 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2529 },
2530 phase: RootComponentAllocationPhase::Verified,
2531 creation: Some(RootComponentCreationEvidence {
2532 wasm_store: Principal::from_slice(&[21; 29]),
2533 payload_hash: [22; 32],
2534 payload_size_bytes: 4_096,
2535 initial_cycles: Cycles::new(5_000_000_000_000),
2536 controller: root,
2537 canister: Some(child),
2538 }),
2539 installation: Some(RootComponentChildInstallEvidence {
2540 raw_module_hash: [23; 32],
2541 chunk_hashes: vec![vec![24; 32]],
2542 binding: child_binding.clone(),
2543 }),
2544 };
2545 let commit_response = RootComponentChildCommitResponse {
2546 allocation: response.clone(),
2547 registry: ComponentRegistryPartitionResponse {
2548 head: ComponentRegistryHead {
2549 component,
2550 revision: 3,
2551 content_hash: [25; 32],
2552 },
2553 binding: child_binding.component.clone(),
2554 protocol_profile_digest: ProtocolProfileDigest::from_bytes([27; 32]),
2555 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2556 caller: Principal::from_slice(&[26; 29]),
2557 },
2558 release_set: response.release_set,
2559 status: ComponentLifecycleStatus::Active,
2560 reserved_descendants: 0,
2561 committed_descendants: 1,
2562 encoded_bytes: 8_192,
2563 },
2564 directory: ComponentDirectoryHead {
2565 provenance: ComponentDirectoryProvenance {
2566 component: child_binding.component.clone(),
2567 source_fleet_subnet_root: root,
2568 component_registry_revision: 3,
2569 component_registry_content_hash: [25; 32],
2570 synchronized_at_ns: 27,
2571 },
2572 descendant_count: 1,
2573 },
2574 };
2575 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2576 fleet: FleetDirectorySnapshot {
2577 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2578 registry: FleetRegistryVersion {
2579 authority: fleet_registry_authority(),
2580 revision: 4,
2581 content_hash: [28; 32],
2582 },
2583 source_fleet_subnet_root: root,
2584 },
2585 fleet_subnet_roots: vec![
2586 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2587 placement_subnet: commit_response.registry.binding.placement_subnet,
2588 fleet_subnet_root: root,
2589 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2590 },
2591 ],
2592 services: vec![],
2593 },
2594 component: commit_response.directory.clone(),
2595 component_group: None,
2596 };
2597 let activation = ComponentRuntimeActivationEvidence {
2598 directory_authority_hash: [29; 32],
2599 activated_at_ns: 30,
2600 };
2601 let directory_response = RootComponentChildDirectoryPreparationResponse {
2602 committed: commit_response.clone(),
2603 child: ComponentRuntimeStatusResponse {
2604 operation_id: request.operation_id,
2605 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2606 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2607 binding: child_binding.component.clone(),
2608 }),
2609 phase: ComponentRuntimePhase::DirectoryPrepared,
2610 authority: Some(runtime_authority.clone()),
2611 authority_hash: Some([31; 32]),
2612 direct_children_hash: Some([37; 32]),
2613 activation: None,
2614 },
2615 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2616 operation_id: [32; 32],
2617 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2618 covered_authority: runtime_authority.clone(),
2619 covered_authority_hash: [31; 32],
2620 activation,
2621 },
2622 parent: None,
2623 };
2624 let activation_response = RootComponentChildRuntimeActivationResponse {
2625 committed: commit_response.clone(),
2626 child: ComponentRuntimeStatusResponse {
2627 operation_id: request.operation_id,
2628 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2629 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2630 binding: child_binding.component.clone(),
2631 }),
2632 phase: ComponentRuntimePhase::Active,
2633 authority: Some(runtime_authority.clone()),
2634 authority_hash: Some([31; 32]),
2635 direct_children_hash: Some([37; 32]),
2636 activation: Some(ComponentRuntimeActivationEvidence {
2637 directory_authority_hash: [31; 32],
2638 activated_at_ns: 33,
2639 }),
2640 },
2641 };
2642 let active_directory = ComponentDirectoryHead {
2643 provenance: ComponentDirectoryProvenance {
2644 component: child_binding.component.clone(),
2645 source_fleet_subnet_root: root,
2646 component_registry_revision: 4,
2647 component_registry_content_hash: [34; 32],
2648 synchronized_at_ns: 35,
2649 },
2650 descendant_count: 1,
2651 };
2652 let active_authority = ComponentRuntimeDirectoryAuthority {
2653 fleet: runtime_authority.fleet,
2654 component: active_directory.clone(),
2655 component_group: None,
2656 };
2657 let membership_response = RootComponentChildMembershipActivationResponse {
2658 committed: commit_response.clone(),
2659 registry: ComponentRegistryPartitionResponse {
2660 head: ComponentRegistryHead {
2661 component,
2662 revision: 4,
2663 content_hash: [34; 32],
2664 },
2665 binding: child_binding.component.clone(),
2666 protocol_profile_digest: ProtocolProfileDigest::from_bytes([36; 32]),
2667 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2668 release_set: commit_response.registry.release_set,
2669 status: ComponentLifecycleStatus::Active,
2670 reserved_descendants: 0,
2671 committed_descendants: 1,
2672 encoded_bytes: 8_256,
2673 },
2674 directory: active_directory,
2675 child: ComponentRuntimeStatusResponse {
2676 operation_id: request.operation_id,
2677 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2678 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2679 binding: child_binding.component,
2680 }),
2681 phase: ComponentRuntimePhase::Active,
2682 authority: Some(active_authority),
2683 authority_hash: Some([36; 32]),
2684 direct_children_hash: Some([38; 32]),
2685 activation: Some(ComponentRuntimeActivationEvidence {
2686 directory_authority_hash: [31; 32],
2687 activated_at_ns: 33,
2688 }),
2689 },
2690 };
2691
2692 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2693 let status_bytes =
2694 candid::encode_one(status_request).expect("encode child reservation status");
2695 let creation_bytes =
2696 candid::encode_one(creation_request).expect("encode child creation request");
2697 let install_bytes =
2698 candid::encode_one(install_request).expect("encode child install request");
2699 let response_bytes = candid::encode_one(&response).expect("encode child response");
2700 let commit_request_bytes =
2701 candid::encode_one(commit_request).expect("encode child commit request");
2702 let directory_request_bytes =
2703 candid::encode_one(directory_request).expect("encode child Directory request");
2704 let activation_request_bytes =
2705 candid::encode_one(activation_request).expect("encode child activation request");
2706 let membership_request_bytes =
2707 candid::encode_one(membership_request).expect("encode child membership request");
2708 let commit_response_bytes =
2709 candid::encode_one(&commit_response).expect("encode child commit response");
2710 let directory_response_bytes =
2711 candid::encode_one(&directory_response).expect("encode child Directory response");
2712 let activation_response_bytes =
2713 candid::encode_one(&activation_response).expect("encode child activation response");
2714 let membership_response_bytes =
2715 candid::encode_one(&membership_response).expect("encode child membership response");
2716
2717 assert_eq!(
2718 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2719 .expect("decode child reservation"),
2720 request
2721 );
2722 assert_eq!(
2723 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2724 .expect("decode child reservation status"),
2725 status_request
2726 );
2727 assert_eq!(
2728 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2729 .expect("decode child creation request"),
2730 creation_request
2731 );
2732 assert_eq!(
2733 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2734 .expect("decode child install request"),
2735 install_request
2736 );
2737 assert_eq!(
2738 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2739 .expect("decode child response"),
2740 response
2741 );
2742 assert_eq!(
2743 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2744 .expect("decode child commit request"),
2745 commit_request
2746 );
2747 assert_eq!(
2748 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2749 &directory_request_bytes
2750 )
2751 .expect("decode child Directory request"),
2752 directory_request
2753 );
2754 assert_eq!(
2755 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2756 .expect("decode child commit response"),
2757 commit_response
2758 );
2759 assert_eq!(
2760 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2761 &directory_response_bytes
2762 )
2763 .expect("decode child Directory response"),
2764 directory_response
2765 );
2766 assert_eq!(
2767 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2768 &activation_request_bytes
2769 )
2770 .expect("decode child activation request"),
2771 activation_request
2772 );
2773 assert_eq!(
2774 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2775 &activation_response_bytes
2776 )
2777 .expect("decode child activation response"),
2778 activation_response
2779 );
2780 assert_eq!(
2781 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2782 &membership_request_bytes
2783 )
2784 .expect("decode child membership request"),
2785 membership_request
2786 );
2787 assert_eq!(
2788 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2789 &membership_response_bytes
2790 )
2791 .expect("decode child membership response"),
2792 membership_response
2793 );
2794 }
2795
2796 #[test]
2797 fn component_install_request_round_trips_through_candid() {
2798 let request = RootComponentInstallRequest {
2799 operation_id: [10; 32],
2800 };
2801 let bytes = candid::encode_one(request).expect("encode install request");
2802
2803 assert_eq!(
2804 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2805 .expect("decode install request"),
2806 request
2807 );
2808 }
2809
2810 #[test]
2811 fn component_commit_request_round_trips_through_candid() {
2812 let request = RootComponentCommitRequest {
2813 operation_id: [10; 32],
2814 };
2815 let bytes = candid::encode_one(request).expect("encode commit request");
2816
2817 assert_eq!(
2818 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2819 .expect("decode commit request"),
2820 request
2821 );
2822 }
2823
2824 #[test]
2825 fn component_runtime_activation_requests_round_trip_through_candid() {
2826 let root_request = RootComponentRuntimeActivationRequest {
2827 operation_id: [22; 32],
2828 };
2829 let target_request = ComponentRuntimeActivationRequest {
2830 operation_id: root_request.operation_id,
2831 directory_authority_hash: [23; 32],
2832 };
2833 let membership_request = RootComponentMembershipActivationRequest {
2834 operation_id: root_request.operation_id,
2835 };
2836 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2837 let target_bytes =
2838 candid::encode_one(target_request).expect("encode target activation request");
2839 let membership_bytes =
2840 candid::encode_one(membership_request).expect("encode membership activation request");
2841
2842 assert_eq!(
2843 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2844 .expect("decode root activation request"),
2845 root_request
2846 );
2847 assert_eq!(
2848 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2849 .expect("decode target activation request"),
2850 target_request
2851 );
2852 assert_eq!(
2853 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2854 .expect("decode membership activation request"),
2855 membership_request
2856 );
2857 }
2858}