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)]
814pub struct ComponentRegistryActivePartitionRequest {
815 pub component: ComponentInstanceId,
816 pub provisioning_operation_id: [u8; 32],
817 pub plan_hash: [u8; 32],
818 pub group_placement: ComponentGroupPlacementId,
819 pub member_path: ComponentGroupMemberPath,
820}
821
822#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
830pub struct ComponentRegistryActivePartitionResponse {
831 pub allocation_operation_id: [u8; 32],
832 pub prepared: ComponentRegistryPartitionResponse,
833 pub activation: ComponentRegistryPartitionResponse,
834 pub current: ComponentRegistryPartitionResponse,
835}
836
837#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
844pub struct ComponentDirectoryProvenance {
845 pub component: ComponentBinding,
846 pub source_fleet_subnet_root: Principal,
847 pub component_registry_revision: u64,
848 pub component_registry_content_hash: [u8; 32],
849 pub synchronized_at_ns: u64,
850}
851
852#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
859pub struct ComponentDirectoryHead {
860 pub provenance: ComponentDirectoryProvenance,
861 pub descendant_count: u32,
862}
863
864#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
871pub struct ComponentDirectoryHeadRequest {
872 pub component: ComponentInstanceId,
873}
874
875#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
882pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
883
884#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
891pub struct ComponentDirectoryPageRequest {
892 pub directory: ComponentDirectoryHead,
893 pub parent_canister_id: Option<Principal>,
894 pub role: Option<CanisterRole>,
895 pub status: Option<ComponentLifecycleStatus>,
896 pub cursor: Option<ComponentDirectoryPageCursor>,
897 pub limit: u16,
898}
899
900#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
907pub struct ComponentDirectoryChildEntry {
908 pub binding: ComponentChildBinding,
909 pub kind: ComponentChildKind,
910 pub installed_artifact_hash: [u8; 32],
911 pub protocol_profile_digest: ProtocolProfileDigest,
912 pub status: ComponentLifecycleStatus,
913}
914
915#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
922pub struct ComponentDirectoryPageResponse {
923 pub directory: ComponentDirectoryHead,
924 pub entries: Vec<ComponentDirectoryChildEntry>,
925 pub next_cursor: Option<ComponentDirectoryPageCursor>,
926}
927
928#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
935pub struct ComponentRuntimeDirectoryAuthority {
936 pub fleet: FleetDirectorySnapshot,
937 pub component: ComponentDirectoryHead,
938 pub component_group: Option<crate::dto::component_provisioning::ComponentGroupDirectory>,
939}
940
941#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
948pub struct ComponentRuntimeDirectChild {
949 pub canister_id: Principal,
950 pub allocation_operation_id: [u8; 32],
951 pub role: CanisterRole,
952 pub protocol_profile_digest: ProtocolProfileDigest,
953}
954
955#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
962pub struct ComponentRuntimeDirectoryPreparationRequest {
963 pub operation_id: [u8; 32],
964 pub authority: ComponentRuntimeDirectoryAuthority,
965 pub direct_children: Vec<ComponentRuntimeDirectChild>,
966}
967
968#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
975pub struct ComponentRuntimeDirectorySynchronizationRequest {
976 pub operation_id: [u8; 32],
977 pub authority: ComponentRuntimeDirectoryAuthority,
978 pub direct_children: Vec<ComponentRuntimeDirectChild>,
979}
980
981#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
988pub enum ComponentRuntimePhase {
989 AwaitingDirectory,
990 DirectoryPrepared,
991 Active,
992}
993
994#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1001pub struct ComponentRuntimeActivationEvidence {
1002 pub directory_authority_hash: [u8; 32],
1003 pub activated_at_ns: u64,
1004}
1005
1006#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1013pub struct ComponentRuntimeActivationRequest {
1014 pub operation_id: [u8; 32],
1015 pub directory_authority_hash: [u8; 32],
1016}
1017
1018#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1025pub struct ComponentRuntimeStatusResponse {
1026 pub fixture: Option<Box<crate::dto::fixture_provisioning::FixtureAssignment>>,
1027 pub operation_id: [u8; 32],
1028 pub binding: ManagedCanisterBinding,
1029 pub deployment: Box<ProtectedComponentDeployment>,
1030 pub phase: ComponentRuntimePhase,
1031 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
1032 pub authority_hash: Option<[u8; 32]>,
1033 pub direct_children_hash: Option<[u8; 32]>,
1034 pub activation: Option<ComponentRuntimeActivationEvidence>,
1035}
1036
1037#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1044pub struct ComponentRuntimeDirectoryConvergenceEvidence {
1045 pub operation_id: [u8; 32],
1046 pub binding: ManagedCanisterBinding,
1047 pub covered_authority: ComponentRuntimeDirectoryAuthority,
1048 pub covered_authority_hash: [u8; 32],
1049 pub activation: ComponentRuntimeActivationEvidence,
1050}
1051
1052#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1059pub struct RootComponentCreationEvidence {
1060 pub wasm_store: Principal,
1061 pub payload_hash: [u8; 32],
1062 pub payload_size_bytes: u64,
1063 pub initial_cycles: Cycles,
1064 pub controller: Principal,
1065 pub canister: Option<Principal>,
1066}
1067
1068#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1075pub struct RootComponentInstallEvidence {
1076 pub raw_module_hash: [u8; 32],
1077 pub chunk_hashes: Vec<Vec<u8>>,
1078 pub binding: ComponentBinding,
1079}
1080
1081#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1088pub struct RootComponentChildInstallEvidence {
1089 pub raw_module_hash: [u8; 32],
1090 pub chunk_hashes: Vec<Vec<u8>>,
1091 pub binding: ComponentChildBinding,
1092}
1093
1094#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1101pub struct RootComponentAllocationResponse {
1102 pub operation_id: [u8; 32],
1103 pub allocation_sequence: u64,
1104 pub component: ComponentInstanceId,
1105 pub component_spec: ComponentSpecId,
1106 pub spec_hash: [u8; 32],
1107 pub role: CanisterRole,
1108 pub provisioning_origin: ComponentProvisioningOrigin,
1109 pub release_set: FleetSubnetRootReleaseSet,
1110 pub phase: RootComponentAllocationPhase,
1111 pub creation: Option<RootComponentCreationEvidence>,
1112 pub installation: Option<RootComponentInstallEvidence>,
1113}
1114
1115#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1122pub struct RootComponentChildAllocationResponse {
1123 pub last_failure: Option<RootComponentChildAllocationFailure>,
1124 pub operation_id: [u8; 32],
1125 pub component: ComponentInstanceId,
1126 pub parent_canister_id: Principal,
1127 pub parent_role: CanisterRole,
1128 pub child_role: CanisterRole,
1129 pub child_kind: ComponentChildKind,
1130 pub maximum_instances_per_parent: u32,
1131 pub maximum_descendants: u32,
1132 pub maximum_registry_bytes: u64,
1133 pub reserved_against_registry: ComponentRegistryHead,
1134 pub release_set: FleetSubnetRootReleaseSet,
1135 pub phase: RootComponentAllocationPhase,
1136 pub creation: Option<RootComponentCreationEvidence>,
1137 pub installation: Option<RootComponentChildInstallEvidence>,
1138}
1139
1140#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1142pub struct RootComponentChildAllocationFailure {
1143 pub diagnostic_code: u16,
1144 pub failed_at_ns: u64,
1145 pub consecutive_failures: u32,
1146 pub retry_at_ns: u64,
1147 #[serde(deserialize_with = "crate::cdk::serialize::required_option")]
1148 pub platform_rejection: Option<String>,
1149}
1150
1151#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1158pub struct RootComponentSubtreeRemovalResponse {
1159 pub operation_id: [u8; 32],
1160 pub component: ComponentInstanceId,
1161 pub target_canister_id: Principal,
1162 pub target_parent_canister_id: Principal,
1163 pub target_role: CanisterRole,
1164 pub target_status: ComponentLifecycleStatus,
1165 pub reserved_against_registry: ComponentRegistryHead,
1166 pub maximum_completed_leaves: u32,
1167 pub completed_leaves: u32,
1168 pub traversal_steps: u32,
1169 pub phase: RootComponentSubtreeRemovalPhase,
1170}
1171
1172#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1179pub struct RootComponentDrainingResponse {
1180 pub operation_id: [u8; 32],
1181 pub component: ComponentInstanceId,
1182 pub previous_registry: ComponentRegistryHead,
1183 pub registry: ComponentRegistryHead,
1184 pub descendant_count: u32,
1185 pub descendant_content_hash: [u8; 32],
1186 pub directory_authority_hash: [u8; 32],
1187 pub started_at_ns: u64,
1188}
1189
1190#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1197pub struct RootComponentQuiescenceStopIntent {
1198 pub registry: ComponentRegistryHead,
1199 pub descendant_count: u32,
1200 pub descendant_content_hash: [u8; 32],
1201 pub canister_id: Principal,
1202 pub controller: Principal,
1203 pub expected_module_hash: [u8; 32],
1204 pub covered_fleet_registry_revision: u64,
1205 pub covered_fleet_registry_content_hash: [u8; 32],
1206 pub covered_authority_hash: [u8; 32],
1207 pub runtime_operation_id: [u8; 32],
1208 pub activation: ComponentRuntimeActivationEvidence,
1209 pub prepared_at_ns: u64,
1210}
1211
1212#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1219pub struct RootComponentQuiescentReceipt {
1220 pub stop: RootComponentQuiescenceStopIntent,
1221 pub observed_module_hash: [u8; 32],
1222 pub quiesced_at_ns: u64,
1223}
1224
1225#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1232pub enum RootComponentQuiescencePhase {
1233 StopIntent(RootComponentQuiescenceStopIntent),
1234 Quiescent(RootComponentQuiescentReceipt),
1235}
1236
1237#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1244pub struct RootComponentQuiescenceResponse {
1245 pub operation_id: [u8; 32],
1246 pub component: ComponentInstanceId,
1247 pub phase: RootComponentQuiescencePhase,
1248}
1249
1250#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1257pub struct RootComponentDrainingDescendantsEmpty {
1258 pub registry: ComponentRegistryHead,
1259 pub descendant_content_hash: [u8; 32],
1260}
1261
1262#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1269#[expect(
1270 clippy::large_enum_variant,
1271 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1272)]
1273pub enum RootComponentDrainingAdvancePhase {
1274 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1275 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1276}
1277
1278#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1285pub struct RootComponentDrainingAdvanceResponse {
1286 pub operation_id: [u8; 32],
1287 pub component: ComponentInstanceId,
1288 pub phase: RootComponentDrainingAdvancePhase,
1289}
1290
1291#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1298pub struct RootComponentFinalInventory {
1299 pub registry: ComponentRegistryHead,
1300 pub descendant_content_hash: [u8; 32],
1301 pub registry_encoded_bytes: u64,
1302 pub directory_synchronized_at_ns: u64,
1303 pub covered_fleet_registry_revision: u64,
1304 pub covered_fleet_registry_content_hash: [u8; 32],
1305 pub directory_authority_hash: [u8; 32],
1306 pub inventory_hash: [u8; 32],
1307 pub finalized_at_ns: u64,
1308}
1309
1310#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1317pub struct RootComponentFinalInventoryResponse {
1318 pub operation_id: [u8; 32],
1319 pub component: ComponentInstanceId,
1320 pub inventory: RootComponentFinalInventory,
1321}
1322
1323#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1330pub struct RootComponentDeletionIntent {
1331 pub final_inventory: RootComponentFinalInventory,
1332 pub quiescence: RootComponentQuiescentReceipt,
1333 pub prepared_at_ns: u64,
1334}
1335
1336#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1343pub struct RootComponentDeletedReceipt {
1344 pub deletion: RootComponentDeletionIntent,
1345 pub deleted_at_ns: u64,
1346}
1347
1348#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1355pub struct RootComponentMembershipRemovedReceipt {
1356 pub deleted: RootComponentDeletedReceipt,
1357 pub allocation_operation_id: [u8; 32],
1358 pub remaining_spec_committed_instances: u32,
1359 pub root_committed_component_instances: u32,
1360 pub root_known_created_component_canisters: u32,
1361 pub root_registry_encoded_bytes: u64,
1362 pub removed_at_ns: u64,
1363 pub removal_hash: [u8; 32],
1364}
1365
1366#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1373pub enum RootComponentDeletionPhase {
1374 DeleteIntent(RootComponentDeletionIntent),
1375 Deleted(RootComponentDeletedReceipt),
1376 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1377}
1378
1379#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1386pub struct RootComponentDeletionResponse {
1387 pub operation_id: [u8; 32],
1388 pub component: ComponentInstanceId,
1389 pub phase: RootComponentDeletionPhase,
1390}
1391
1392#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1399pub struct RootComponentChildCommitResponse {
1400 pub allocation: RootComponentChildAllocationResponse,
1401 pub registry: ComponentRegistryPartitionResponse,
1402 pub directory: ComponentDirectoryHead,
1403}
1404
1405#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1412pub struct RootComponentChildDirectoryPreparationResponse {
1413 pub committed: RootComponentChildCommitResponse,
1414 pub child: ComponentRuntimeStatusResponse,
1415 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1416 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1417}
1418
1419#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1426pub struct RootComponentChildRuntimeActivationResponse {
1427 pub committed: RootComponentChildCommitResponse,
1428 pub child: ComponentRuntimeStatusResponse,
1429}
1430
1431#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1438pub struct RootComponentChildMembershipActivationResponse {
1439 pub committed: RootComponentChildCommitResponse,
1440 pub registry: ComponentRegistryPartitionResponse,
1441 pub directory: ComponentDirectoryHead,
1442 pub child: ComponentRuntimeStatusResponse,
1443}
1444
1445#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1452pub struct RootComponentCommitResponse {
1453 pub allocation: RootComponentAllocationResponse,
1454 pub registry: ComponentRegistryPartitionResponse,
1455 pub directory: ComponentDirectoryHead,
1456}
1457
1458#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1465pub struct RootComponentDirectoryPreparationResponse {
1466 pub committed: RootComponentCommitResponse,
1467 pub target: ComponentRuntimeStatusResponse,
1468}
1469
1470#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1477pub struct RootComponentRuntimeActivationResponse {
1478 pub committed: RootComponentCommitResponse,
1479 pub target: ComponentRuntimeStatusResponse,
1480}
1481
1482#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1489pub struct RootComponentMembershipActivationResponse {
1490 pub allocation: RootComponentAllocationResponse,
1491 pub registry: ComponentRegistryPartitionResponse,
1492 pub directory: ComponentDirectoryHead,
1493 pub target: ComponentRuntimeStatusResponse,
1494}
1495
1496#[cfg(test)]
1497mod tests {
1498 use super::*;
1499 use crate::{
1500 dto::root_store::RootStoreBootstrapRequest,
1501 ids::{
1502 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1503 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1504 },
1505 };
1506
1507 #[test]
1508 fn component_registry_contracts_round_trip_through_candid() {
1509 let request = RootComponentRegistryPreparationRequest {
1510 store_bootstrap: RootStoreBootstrapRequest {
1511 operation_id: [8; 32],
1512 manifest_payload_size_bytes: 128,
1513 },
1514 expected_fleet_registry: FleetRegistryVersion {
1515 authority: fleet_registry_authority(),
1516 revision: 4,
1517 content_hash: [5; 32],
1518 },
1519 };
1520 let response = RootComponentRegistryStatusResponse {
1521 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1522 prepared_against_registry: request.expected_fleet_registry.clone(),
1523 release_set: FleetSubnetRootReleaseSet {
1524 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1525 [7; 32],
1526 )),
1527 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1528 },
1529 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1530 next_allocation_sequence: 1,
1531 reserved_component_instances: 0,
1532 committed_component_instances: 0,
1533 managed_descendants: 0,
1534 known_created_component_canisters: 0,
1535 encoded_bytes: 0,
1536 initial_inventory: Some(RootComponentInitialInventoryStatus {
1537 fleet_activation_operation_id: [10; 32],
1538 component_count: 0,
1539 inventory_hash: [11; 32],
1540 sealed_at_ns: 12,
1541 directories_converged: true,
1542 root_runtime_activated: true,
1543 }),
1544 };
1545 let allocation = RootComponentAllocationResponse {
1546 operation_id: [10; 32],
1547 allocation_sequence: 1,
1548 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1549 component_spec: "projects".parse().expect("Component Spec ID"),
1550 spec_hash: [12; 32],
1551 role: CanisterRole::new("project_hub"),
1552 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1553 caller: Principal::from_slice(&[13; 29]),
1554 },
1555 release_set: response.release_set,
1556 phase: RootComponentAllocationPhase::Reserved,
1557 creation: None,
1558 installation: None,
1559 };
1560 let created = RootComponentAllocationResponse {
1561 phase: RootComponentAllocationPhase::Created,
1562 creation: Some(RootComponentCreationEvidence {
1563 wasm_store: Principal::from_slice(&[14; 29]),
1564 payload_hash: [15; 32],
1565 payload_size_bytes: 4_096,
1566 initial_cycles: Cycles::new(5_000_000_000_000),
1567 controller: Principal::from_slice(&[6; 29]),
1568 canister: Some(Principal::from_slice(&[16; 29])),
1569 }),
1570 installation: None,
1571 ..allocation.clone()
1572 };
1573 let request_bytes = candid::encode_one(&request).expect("encode request");
1574 let response_bytes = candid::encode_one(&response).expect("encode response");
1575 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1576 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1577
1578 assert_eq!(
1579 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1580 .expect("decode request"),
1581 request
1582 );
1583 assert_eq!(
1584 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1585 .expect("decode response"),
1586 response
1587 );
1588 assert_eq!(
1589 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1590 .expect("decode allocation"),
1591 allocation
1592 );
1593 assert_eq!(
1594 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1595 .expect("decode created allocation"),
1596 created
1597 );
1598 }
1599
1600 #[test]
1601 fn component_commit_response_round_trips_through_candid() {
1602 let root = Principal::from_slice(&[6; 29]);
1603 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1604 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1605 let release_set = FleetSubnetRootReleaseSet {
1606 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1607 [7; 32],
1608 )),
1609 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1610 };
1611 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1612 caller: Principal::from_slice(&[13; 29]),
1613 };
1614 let binding = ComponentBinding {
1615 authority: fleet_registry_authority(),
1616 component,
1617 component_spec: component_spec.clone(),
1618 spec_hash: [12; 32],
1619 role: CanisterRole::new("project_hub"),
1620 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1621 fleet_subnet_root: root,
1622 canister_id: Principal::from_slice(&[16; 29]),
1623 };
1624 let head = ComponentRegistryHead {
1625 component,
1626 revision: 1,
1627 content_hash: [18; 32],
1628 };
1629 let committed = RootComponentCommitResponse {
1630 allocation: RootComponentAllocationResponse {
1631 operation_id: [10; 32],
1632 allocation_sequence: 1,
1633 component,
1634 component_spec,
1635 spec_hash: binding.spec_hash,
1636 role: binding.role.clone(),
1637 provisioning_origin: provisioning_origin.clone(),
1638 release_set,
1639 phase: RootComponentAllocationPhase::Committed,
1640 creation: Some(RootComponentCreationEvidence {
1641 wasm_store: Principal::from_slice(&[14; 29]),
1642 payload_hash: [15; 32],
1643 payload_size_bytes: 4_096,
1644 initial_cycles: Cycles::new(5_000_000_000_000),
1645 controller: root,
1646 canister: Some(binding.canister_id),
1647 }),
1648 installation: Some(RootComponentInstallEvidence {
1649 raw_module_hash: [20; 32],
1650 chunk_hashes: vec![vec![21; 32]],
1651 binding: binding.clone(),
1652 }),
1653 },
1654 registry: ComponentRegistryPartitionResponse {
1655 head: head.clone(),
1656 binding: binding.clone(),
1657 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1658 provisioning_origin,
1659 release_set,
1660 status: ComponentLifecycleStatus::Prepared,
1661 reserved_descendants: 0,
1662 committed_descendants: 0,
1663 encoded_bytes: 2_048,
1664 },
1665 directory: ComponentDirectoryHead {
1666 provenance: ComponentDirectoryProvenance {
1667 component: binding,
1668 source_fleet_subnet_root: root,
1669 component_registry_revision: head.revision,
1670 component_registry_content_hash: head.content_hash,
1671 synchronized_at_ns: 19,
1672 },
1673 descendant_count: 0,
1674 },
1675 };
1676 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1677
1678 assert_eq!(
1679 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1680 .expect("decode committed allocation"),
1681 committed
1682 );
1683 }
1684
1685 #[test]
1686 fn component_directory_page_contracts_round_trip_through_candid() {
1687 let root = Principal::from_slice(&[6; 29]);
1688 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1689 let binding = ComponentBinding {
1690 authority: fleet_registry_authority(),
1691 component,
1692 component_spec: "projects".parse().expect("Component Spec ID"),
1693 spec_hash: [12; 32],
1694 role: CanisterRole::new("project_hub"),
1695 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1696 fleet_subnet_root: root,
1697 canister_id: Principal::from_slice(&[16; 29]),
1698 };
1699 let directory = ComponentDirectoryHead {
1700 provenance: ComponentDirectoryProvenance {
1701 component: binding.clone(),
1702 source_fleet_subnet_root: root,
1703 component_registry_revision: 3,
1704 component_registry_content_hash: [18; 32],
1705 synchronized_at_ns: 19,
1706 },
1707 descendant_count: 1,
1708 };
1709 let request = ComponentDirectoryPageRequest {
1710 directory: directory.clone(),
1711 parent_canister_id: Some(binding.canister_id),
1712 role: Some(CanisterRole::new("project_instance")),
1713 status: Some(ComponentLifecycleStatus::Active),
1714 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1715 limit: 50,
1716 };
1717 let response = ComponentDirectoryPageResponse {
1718 directory,
1719 entries: vec![ComponentDirectoryChildEntry {
1720 binding: ComponentChildBinding {
1721 component: binding.clone(),
1722 parent_canister_id: binding.canister_id,
1723 role: CanisterRole::new("project_instance"),
1724 canister_id: Principal::from_slice(&[21; 29]),
1725 },
1726 kind: ComponentChildKind::Instance,
1727 installed_artifact_hash: [22; 32],
1728 protocol_profile_digest: ProtocolProfileDigest::from_bytes([24; 32]),
1729 status: ComponentLifecycleStatus::Active,
1730 }],
1731 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1732 };
1733 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1734 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1735
1736 assert_eq!(
1737 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1738 .expect("decode Directory page request"),
1739 request
1740 );
1741 assert_eq!(
1742 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1743 .expect("decode Directory page response"),
1744 response
1745 );
1746 }
1747
1748 fn fleet_registry_authority() -> FleetRegistryAuthority {
1749 FleetRegistryAuthority {
1750 binding: FleetCoordinatorBinding {
1751 fleet: crate::ids::FleetBinding {
1752 fleet: FleetKey {
1753 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1754 fleet_id: FleetId::from_generated_bytes([1; 32]),
1755 },
1756 app: AppId::from("toko"),
1757 },
1758 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1759 coordinator: Principal::from_slice(&[3; 29]),
1760 recovery_controllers: Vec::new(),
1761 },
1762 epoch: 1,
1763 }
1764 }
1765
1766 #[test]
1767 fn component_creation_request_round_trips_through_candid() {
1768 let request = RootComponentCreationRequest {
1769 operation_id: [10; 32],
1770 };
1771 let bytes = candid::encode_one(request).expect("encode creation request");
1772
1773 assert_eq!(
1774 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1775 .expect("decode creation request"),
1776 request
1777 );
1778 }
1779
1780 #[test]
1781 fn peer_component_provisioning_origin_round_trips_through_candid() {
1782 let authority = fleet_registry_authority();
1783 let requester_spec: ComponentSpecId =
1784 "projects".parse().expect("requester Component Spec ID");
1785 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1786 let origin = ComponentProvisioningOrigin::Component {
1787 requester: Box::new(ComponentBinding {
1788 authority,
1789 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1790 component_spec: requester_spec.clone(),
1791 spec_hash: [21; 32],
1792 role: CanisterRole::new("project_hub"),
1793 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1794 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1795 canister_id: Principal::from_slice(&[24; 29]),
1796 }),
1797 grant: Box::new(crate::config::ComponentProvisioningGrant {
1798 requester_component_spec: requester_spec,
1799 target_component_spec: target_spec,
1800 maximum_instances_per_requester_per_root: 3,
1801 }),
1802 };
1803 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1804
1805 assert_eq!(
1806 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1807 .expect("decode peer provisioning origin"),
1808 origin
1809 );
1810 }
1811
1812 #[test]
1813 fn cross_root_peer_request_and_origin_round_trip_through_candid() {
1814 let authority = fleet_registry_authority();
1815 let requester_spec: ComponentSpecId =
1816 "projects".parse().expect("requester Component Spec ID");
1817 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1818 let registry = FleetRegistryVersion {
1819 authority: authority.clone(),
1820 revision: 7,
1821 content_hash: [30; 32],
1822 };
1823 let request = RootPeerComponentAllocationRequest {
1824 operation_id: [31; 32],
1825 component_spec: target_spec.clone(),
1826 requester: PeerComponentRequester::FleetService {
1827 service: "projects".parse().expect("Fleet service ID"),
1828 expected_registry: Box::new(registry.clone()),
1829 },
1830 };
1831 let origin = ComponentProvisioningOrigin::FleetServiceComponent {
1832 requester: Box::new(FleetServiceComponentRequester {
1833 service: "projects".parse().expect("Fleet service ID"),
1834 member_purpose: FleetServiceMemberPurpose::PoolMember,
1835 group_placement: ComponentGroupPlacementId {
1836 deployment: "project_hubs".parse().expect("deployment ID"),
1837 ordinal: 2,
1838 },
1839 member_path: ComponentGroupMemberPath::try_from(vec![
1840 "hub".parse().expect("member ID"),
1841 ])
1842 .expect("member path"),
1843 component: ComponentBinding {
1844 authority,
1845 component: ComponentInstanceId::from_generated_bytes([32; 32]),
1846 component_spec: requester_spec.clone(),
1847 spec_hash: [33; 32],
1848 role: CanisterRole::new("project_hub"),
1849 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[34; 29])),
1850 fleet_subnet_root: Principal::from_slice(&[35; 29]),
1851 canister_id: Principal::from_slice(&[36; 29]),
1852 },
1853 }),
1854 registry: Box::new(registry),
1855 grant: Box::new(crate::config::ComponentProvisioningGrant {
1856 requester_component_spec: requester_spec,
1857 target_component_spec: target_spec,
1858 maximum_instances_per_requester_per_root: 3,
1859 }),
1860 };
1861 let request_bytes = candid::encode_one(&request).expect("encode cross-root request");
1862 let origin_bytes = candid::encode_one(&origin).expect("encode cross-root origin");
1863
1864 assert_eq!(
1865 candid::decode_one::<RootPeerComponentAllocationRequest>(&request_bytes)
1866 .expect("decode cross-root request"),
1867 request
1868 );
1869 assert_eq!(
1870 candid::decode_one::<ComponentProvisioningOrigin>(&origin_bytes)
1871 .expect("decode cross-root origin"),
1872 origin
1873 );
1874 }
1875
1876 #[test]
1877 fn component_group_provisioning_origin_round_trips_through_candid() {
1878 let origin = ComponentProvisioningOrigin::ComponentGroup {
1879 operation_id: [25; 32],
1880 plan_hash: [26; 32],
1881 group_placement: ComponentGroupPlacementId {
1882 deployment: "cells".parse().expect("deployment ID"),
1883 ordinal: 3,
1884 },
1885 member_path: ComponentGroupMemberPath::try_from(vec![
1886 "api".parse().expect("member ID"),
1887 ])
1888 .expect("member path"),
1889 };
1890 let bytes = candid::encode_one(&origin).expect("encode group provisioning origin");
1891
1892 assert_eq!(
1893 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1894 .expect("decode group provisioning origin"),
1895 origin
1896 );
1897 }
1898
1899 #[test]
1900 #[expect(
1901 clippy::too_many_lines,
1902 reason = "one Candid contract test covers every subtree-removal phase receipt"
1903 )]
1904 fn component_subtree_removal_contracts_round_trip_through_candid() {
1905 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1906 let registry = ComponentRegistryHead {
1907 component,
1908 revision: 7,
1909 content_hash: [42; 32],
1910 };
1911 let request = RootComponentSubtreeRemovalRequest {
1912 operation_id: [43; 32],
1913 component,
1914 target_canister_id: Principal::from_slice(&[44; 29]),
1915 expected_registry: registry.clone(),
1916 };
1917 let status_request = RootComponentSubtreeRemovalStatusRequest {
1918 operation_id: request.operation_id,
1919 component,
1920 };
1921 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1922 operation_id: request.operation_id,
1923 component,
1924 expected_traversal_steps: 1,
1925 };
1926 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1927 operation_id: request.operation_id,
1928 component,
1929 expected_traversal_steps: 2,
1930 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1931 expected_leaf_parent_canister_id: request.target_canister_id,
1932 };
1933 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1934 observed_module_hash: [49; 32],
1935 stop: RootComponentSubtreeRemovalStopIntent {
1936 controller: Principal::from_slice(&[48; 29]),
1937 leaf: RootComponentSubtreeRemovalNode {
1938 canister_id: Principal::from_slice(&[46; 29]),
1939 parent_canister_id: request.target_canister_id,
1940 role: CanisterRole::new("project_ledger"),
1941 kind: ComponentChildKind::Singleton,
1942 installed_artifact_hash: [47; 32],
1943 status: ComponentLifecycleStatus::Active,
1944 },
1945 },
1946 };
1947 let response = RootComponentSubtreeRemovalResponse {
1948 operation_id: request.operation_id,
1949 component,
1950 target_canister_id: request.target_canister_id,
1951 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1952 target_role: CanisterRole::new("project_instance"),
1953 target_status: ComponentLifecycleStatus::Active,
1954 reserved_against_registry: registry,
1955 maximum_completed_leaves: 4,
1956 completed_leaves: 1,
1957 traversal_steps: 2,
1958 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1959 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1960 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1961 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1962 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1963 },
1964 removed_from_registry: ComponentRegistryHead {
1965 component,
1966 revision: 8,
1967 content_hash: [50; 32],
1968 },
1969 previous_descendant_content_hash: [51; 32],
1970 previous_committed_descendants: 4,
1971 registry: ComponentRegistryHead {
1972 component,
1973 revision: 9,
1974 content_hash: [52; 32],
1975 },
1976 descendant_content_hash: [53; 32],
1977 registry_encoded_bytes: 4_096,
1978 reserved_descendants: 1,
1979 committed_descendants: 3,
1980 directory_synchronized_at_ns: 54,
1981 directory_authority_hash: [55; 32],
1982 parent_role_instances: 0,
1983 root_managed_descendants: 4,
1984 root_known_created_component_canisters: 4,
1985 },
1986 covered_fleet_registry_revision: 6,
1987 covered_fleet_registry_content_hash: [56; 32],
1988 covered_component_registry: ComponentRegistryHead {
1989 component,
1990 revision: 9,
1991 content_hash: [52; 32],
1992 },
1993 covered_authority_hash: [55; 32],
1994 owning_component: Some(
1995 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1996 operation_id: [57; 32],
1997 canister_id: Principal::from_slice(&[58; 29]),
1998 activation: ComponentRuntimeActivationEvidence {
1999 directory_authority_hash: [59; 32],
2000 activated_at_ns: 60,
2001 },
2002 },
2003 ),
2004 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
2005 operation_id: [61; 32],
2006 canister_id: request.target_canister_id,
2007 activation: ComponentRuntimeActivationEvidence {
2008 directory_authority_hash: [62; 32],
2009 activated_at_ns: 63,
2010 },
2011 }),
2012 },
2013 ),
2014 };
2015
2016 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
2017 let advance_bytes =
2018 candid::encode_one(advance_request).expect("encode subtree removal advance request");
2019 let stop_bytes =
2020 candid::encode_one(stop_request).expect("encode subtree removal stop request");
2021 let status_bytes =
2022 candid::encode_one(status_request).expect("encode subtree removal status request");
2023 let response_bytes =
2024 candid::encode_one(&response).expect("encode subtree removal response");
2025
2026 assert_eq!(
2027 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
2028 .expect("decode subtree removal request"),
2029 request
2030 );
2031 assert_eq!(
2032 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
2033 .expect("decode subtree removal advance request"),
2034 advance_request
2035 );
2036 assert_eq!(
2037 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
2038 .expect("decode subtree removal stop request"),
2039 stop_request
2040 );
2041 assert_eq!(
2042 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
2043 .expect("decode subtree removal status request"),
2044 status_request
2045 );
2046 assert_eq!(
2047 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
2048 .expect("decode subtree removal response"),
2049 response
2050 );
2051
2052 let mut quiescent_owner_response = response;
2053 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
2054 &mut quiescent_owner_response.phase
2055 else {
2056 panic!("Directory-synchronized response");
2057 };
2058 receipt.owning_component = None;
2059 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
2060 .expect("encode quiescent-owner subtree response");
2061 assert_eq!(
2062 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
2063 .expect("decode quiescent-owner subtree response"),
2064 quiescent_owner_response
2065 );
2066 }
2067
2068 #[test]
2069 fn component_draining_contracts_round_trip_through_candid() {
2070 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
2071 let previous_registry = ComponentRegistryHead {
2072 component,
2073 revision: 7,
2074 content_hash: [61; 32],
2075 };
2076 let request = RootComponentDrainingRequest {
2077 operation_id: [62; 32],
2078 component,
2079 expected_registry: previous_registry.clone(),
2080 };
2081 let status_request = RootComponentDrainingStatusRequest {
2082 operation_id: request.operation_id,
2083 component,
2084 };
2085 let response = RootComponentDrainingResponse {
2086 operation_id: request.operation_id,
2087 component,
2088 previous_registry,
2089 registry: ComponentRegistryHead {
2090 component,
2091 revision: 8,
2092 content_hash: [63; 32],
2093 },
2094 descendant_count: 20_000,
2095 descendant_content_hash: [64; 32],
2096 directory_authority_hash: [65; 32],
2097 started_at_ns: 66,
2098 };
2099
2100 let request_bytes =
2101 candid::encode_one(&request).expect("encode Component draining request");
2102 let status_bytes =
2103 candid::encode_one(status_request).expect("encode Component draining status request");
2104 let response_bytes =
2105 candid::encode_one(&response).expect("encode Component draining response");
2106
2107 assert_eq!(
2108 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
2109 .expect("decode Component draining request"),
2110 request
2111 );
2112 assert_eq!(
2113 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
2114 .expect("decode Component draining status request"),
2115 status_request
2116 );
2117 assert_eq!(
2118 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
2119 .expect("decode Component draining response"),
2120 response
2121 );
2122 }
2123
2124 #[test]
2125 fn component_quiescence_contracts_round_trip_through_candid() {
2126 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
2127 let registry = ComponentRegistryHead {
2128 component,
2129 revision: 9,
2130 content_hash: [68; 32],
2131 };
2132 let request = RootComponentQuiescenceRequest {
2133 operation_id: [69; 32],
2134 component,
2135 expected_registry: registry.clone(),
2136 };
2137 let status_request = RootComponentQuiescenceStatusRequest {
2138 operation_id: request.operation_id,
2139 component,
2140 };
2141 let stop = RootComponentQuiescenceStopIntent {
2142 registry,
2143 descendant_count: 20_000,
2144 descendant_content_hash: [70; 32],
2145 canister_id: Principal::from_slice(&[71; 29]),
2146 controller: Principal::from_slice(&[72; 29]),
2147 expected_module_hash: [73; 32],
2148 covered_fleet_registry_revision: 10,
2149 covered_fleet_registry_content_hash: [74; 32],
2150 covered_authority_hash: [75; 32],
2151 runtime_operation_id: [76; 32],
2152 activation: ComponentRuntimeActivationEvidence {
2153 directory_authority_hash: [77; 32],
2154 activated_at_ns: 78,
2155 },
2156 prepared_at_ns: 79,
2157 };
2158 let response = RootComponentQuiescenceResponse {
2159 operation_id: request.operation_id,
2160 component,
2161 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
2162 stop,
2163 observed_module_hash: [73; 32],
2164 quiesced_at_ns: 80,
2165 }),
2166 };
2167
2168 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
2169 let status_bytes =
2170 candid::encode_one(status_request).expect("encode quiescence status request");
2171 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
2172 assert_eq!(
2173 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
2174 .expect("decode quiescence request"),
2175 request
2176 );
2177 assert_eq!(
2178 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
2179 .expect("decode quiescence status request"),
2180 status_request
2181 );
2182 assert_eq!(
2183 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
2184 .expect("decode quiescence response"),
2185 response
2186 );
2187 }
2188
2189 #[test]
2190 fn component_draining_advance_contracts_round_trip_through_candid() {
2191 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
2192 let registry = ComponentRegistryHead {
2193 component,
2194 revision: 12,
2195 content_hash: [82; 32],
2196 };
2197 let request = RootComponentDrainingAdvanceRequest {
2198 operation_id: [83; 32],
2199 component,
2200 };
2201 let descendant_removal = RootComponentDrainingAdvanceResponse {
2202 operation_id: request.operation_id,
2203 component,
2204 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2205 RootComponentSubtreeRemovalResponse {
2206 operation_id: [84; 32],
2207 component,
2208 target_canister_id: Principal::from_slice(&[85; 29]),
2209 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2210 target_role: CanisterRole::new("project_instance"),
2211 target_status: ComponentLifecycleStatus::Active,
2212 reserved_against_registry: registry.clone(),
2213 maximum_completed_leaves: 20_000,
2214 completed_leaves: 0,
2215 traversal_steps: 0,
2216 phase: RootComponentSubtreeRemovalPhase::Fenced,
2217 },
2218 ),
2219 };
2220 let descendants_empty = RootComponentDrainingAdvanceResponse {
2221 operation_id: request.operation_id,
2222 component,
2223 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2224 RootComponentDrainingDescendantsEmpty {
2225 registry,
2226 descendant_content_hash: [87; 32],
2227 },
2228 ),
2229 };
2230
2231 let request_bytes =
2232 candid::encode_one(request).expect("encode Component draining advance request");
2233 let removal_bytes = candid::encode_one(&descendant_removal)
2234 .expect("encode Component draining removal response");
2235 let empty_bytes = candid::encode_one(&descendants_empty)
2236 .expect("encode Component draining empty response");
2237
2238 assert_eq!(
2239 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2240 .expect("decode Component draining advance request"),
2241 request
2242 );
2243 assert_eq!(
2244 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2245 .expect("decode Component draining removal response"),
2246 descendant_removal
2247 );
2248 assert_eq!(
2249 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2250 .expect("decode Component draining empty response"),
2251 descendants_empty
2252 );
2253 }
2254
2255 #[test]
2256 #[expect(
2257 clippy::too_many_lines,
2258 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2259 )]
2260 fn component_final_inventory_contracts_round_trip_through_candid() {
2261 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2262 let registry = ComponentRegistryHead {
2263 component,
2264 revision: 21,
2265 content_hash: [89; 32],
2266 };
2267 let request = RootComponentFinalInventoryRequest {
2268 operation_id: [90; 32],
2269 component,
2270 expected_registry: registry.clone(),
2271 };
2272 let inventory = RootComponentFinalInventory {
2273 registry,
2274 descendant_content_hash: [91; 32],
2275 registry_encoded_bytes: 4_096,
2276 directory_synchronized_at_ns: 92,
2277 covered_fleet_registry_revision: 93,
2278 covered_fleet_registry_content_hash: [94; 32],
2279 directory_authority_hash: [95; 32],
2280 inventory_hash: [96; 32],
2281 finalized_at_ns: 97,
2282 };
2283 let response = RootComponentFinalInventoryResponse {
2284 operation_id: request.operation_id,
2285 component,
2286 inventory: inventory.clone(),
2287 };
2288 let deletion_request = RootComponentDeletionRequest {
2289 operation_id: request.operation_id,
2290 component,
2291 expected_inventory_hash: inventory.inventory_hash,
2292 };
2293 let deletion_status_request = RootComponentDeletionStatusRequest {
2294 operation_id: request.operation_id,
2295 component,
2296 };
2297 let deletion = RootComponentDeletionIntent {
2298 final_inventory: inventory,
2299 quiescence: RootComponentQuiescentReceipt {
2300 stop: RootComponentQuiescenceStopIntent {
2301 registry: response.inventory.registry.clone(),
2302 descendant_count: 0,
2303 descendant_content_hash: response.inventory.descendant_content_hash,
2304 canister_id: Principal::from_slice(&[98; 29]),
2305 controller: Principal::from_slice(&[99; 29]),
2306 expected_module_hash: [100; 32],
2307 covered_fleet_registry_revision: 93,
2308 covered_fleet_registry_content_hash: [94; 32],
2309 covered_authority_hash: [101; 32],
2310 runtime_operation_id: [102; 32],
2311 activation: ComponentRuntimeActivationEvidence {
2312 directory_authority_hash: [103; 32],
2313 activated_at_ns: 104,
2314 },
2315 prepared_at_ns: 105,
2316 },
2317 observed_module_hash: [100; 32],
2318 quiesced_at_ns: 106,
2319 },
2320 prepared_at_ns: 107,
2321 };
2322 let deleted_receipt = RootComponentDeletedReceipt {
2323 deletion,
2324 deleted_at_ns: 108,
2325 };
2326 let deletion_response = RootComponentDeletionResponse {
2327 operation_id: request.operation_id,
2328 component,
2329 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2330 };
2331 let membership_removed_response = RootComponentDeletionResponse {
2332 operation_id: request.operation_id,
2333 component,
2334 phase: RootComponentDeletionPhase::MembershipRemoved(
2335 RootComponentMembershipRemovedReceipt {
2336 deleted: deleted_receipt,
2337 allocation_operation_id: [109; 32],
2338 remaining_spec_committed_instances: 2,
2339 root_committed_component_instances: 3,
2340 root_known_created_component_canisters: 4,
2341 root_registry_encoded_bytes: 5_000,
2342 removed_at_ns: 110,
2343 removal_hash: [111; 32],
2344 },
2345 ),
2346 };
2347
2348 let request_bytes =
2349 candid::encode_one(&request).expect("encode Component final inventory request");
2350 let response_bytes =
2351 candid::encode_one(&response).expect("encode Component final inventory response");
2352 let deletion_request_bytes =
2353 candid::encode_one(deletion_request).expect("encode Component deletion request");
2354 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2355 .expect("encode Component deletion status request");
2356 let deletion_response_bytes =
2357 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2358 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2359 .expect("encode Component membership-removal response");
2360 assert_eq!(
2361 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2362 .expect("decode Component final inventory request"),
2363 request
2364 );
2365 assert_eq!(
2366 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2367 .expect("decode Component final inventory response"),
2368 response
2369 );
2370 assert_eq!(
2371 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2372 .expect("decode Component deletion request"),
2373 deletion_request
2374 );
2375 assert_eq!(
2376 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2377 .expect("decode Component deletion status request"),
2378 deletion_status_request
2379 );
2380 assert_eq!(
2381 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2382 .expect("decode Component deletion response"),
2383 deletion_response
2384 );
2385 assert_eq!(
2386 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2387 .expect("decode Component membership-removal response"),
2388 membership_removed_response
2389 );
2390 }
2391
2392 #[test]
2393 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2394 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2395 operation_id: [50; 32],
2396 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2397 expected_traversal_steps: 3,
2398 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2399 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2400 };
2401 let request = RootComponentSubtreeRemovalDeleteRequest {
2402 operation_id: prepare.operation_id,
2403 component: prepare.component,
2404 expected_traversal_steps: prepare.expected_traversal_steps,
2405 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2406 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2407 };
2408 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2409 operation_id: prepare.operation_id,
2410 component: prepare.component,
2411 expected_traversal_steps: prepare.expected_traversal_steps,
2412 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2413 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2414 };
2415 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2416 operation_id: prepare.operation_id,
2417 component: prepare.component,
2418 expected_traversal_steps: prepare.expected_traversal_steps,
2419 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2420 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2421 };
2422 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2423 operation_id: prepare.operation_id,
2424 component: prepare.component,
2425 expected_traversal_steps: prepare.expected_traversal_steps,
2426 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2427 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2428 };
2429 let prepare_bytes = candid::encode_one(prepare)
2430 .expect("encode subtree removal deletion preparation request");
2431 let request_bytes =
2432 candid::encode_one(request).expect("encode subtree removal deletion request");
2433 let membership_request_bytes = candid::encode_one(membership_request)
2434 .expect("encode subtree removal membership-removal request");
2435 let directory_request_bytes = candid::encode_one(directory_request)
2436 .expect("encode subtree removal Directory synchronization request");
2437 let finalization_request_bytes = candid::encode_one(finalization_request)
2438 .expect("encode subtree removal leaf-finalization request");
2439
2440 assert_eq!(
2441 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2442 &prepare_bytes
2443 )
2444 .expect("decode subtree removal deletion preparation request"),
2445 prepare
2446 );
2447 assert_eq!(
2448 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2449 .expect("decode subtree removal deletion request"),
2450 request
2451 );
2452 assert_eq!(
2453 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2454 &membership_request_bytes
2455 )
2456 .expect("decode subtree removal membership-removal request"),
2457 membership_request
2458 );
2459 assert_eq!(
2460 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2461 &directory_request_bytes
2462 )
2463 .expect("decode subtree removal Directory synchronization request"),
2464 directory_request
2465 );
2466 assert_eq!(
2467 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2468 &finalization_request_bytes
2469 )
2470 .expect("decode subtree removal leaf-finalization request"),
2471 finalization_request
2472 );
2473 }
2474
2475 #[test]
2476 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2477 let request = RootComponentSubtreeRemovalStopRequest {
2478 operation_id: [50; 32],
2479 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2480 expected_traversal_steps: 3,
2481 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2482 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2483 };
2484 let bytes =
2485 candid::encode_one(request).expect("encode subtree removal stop execution request");
2486
2487 assert_eq!(
2488 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2489 .expect("decode subtree removal stop execution request"),
2490 request
2491 );
2492 }
2493
2494 #[test]
2495 #[expect(
2496 clippy::too_many_lines,
2497 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2498 )]
2499 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2500 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2501 let registry = ComponentRegistryHead {
2502 component,
2503 revision: 2,
2504 content_hash: [12; 32],
2505 };
2506 let request = RootComponentChildAllocationRequest {
2507 operation_id: [13; 32],
2508 component,
2509 expected_registry: registry.clone(),
2510 child_role: CanisterRole::new("project_instance"),
2511 application_init_args: Some(vec![9, 8, 7]),
2512 };
2513 let status_request = RootComponentChildAllocationStatusRequest {
2514 operation_id: request.operation_id,
2515 component,
2516 };
2517 let creation_request = RootComponentChildCreationRequest {
2518 operation_id: request.operation_id,
2519 component,
2520 };
2521 let install_request = RootComponentChildInstallRequest {
2522 operation_id: request.operation_id,
2523 component,
2524 };
2525 let commit_request = RootComponentChildCommitRequest {
2526 operation_id: request.operation_id,
2527 component,
2528 };
2529 let directory_request = RootComponentChildDirectoryPreparationRequest {
2530 operation_id: request.operation_id,
2531 component,
2532 };
2533 let activation_request = RootComponentChildRuntimeActivationRequest {
2534 operation_id: request.operation_id,
2535 component,
2536 };
2537 let membership_request = RootComponentChildMembershipActivationRequest {
2538 operation_id: request.operation_id,
2539 component,
2540 };
2541 let root = Principal::from_slice(&[17; 29]);
2542 let parent = Principal::from_slice(&[14; 29]);
2543 let child = Principal::from_slice(&[18; 29]);
2544 let child_binding = ComponentChildBinding {
2545 component: ComponentBinding {
2546 authority: fleet_registry_authority(),
2547 component,
2548 component_spec: "projects".parse().expect("Component Spec"),
2549 spec_hash: [19; 32],
2550 role: CanisterRole::new("project_hub"),
2551 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2552 fleet_subnet_root: root,
2553 canister_id: parent,
2554 },
2555 parent_canister_id: parent,
2556 role: request.child_role.clone(),
2557 canister_id: child,
2558 };
2559 let response = RootComponentChildAllocationResponse {
2560 last_failure: None,
2561 operation_id: request.operation_id,
2562 component,
2563 parent_canister_id: parent,
2564 parent_role: CanisterRole::new("project_hub"),
2565 child_role: request.child_role.clone(),
2566 child_kind: ComponentChildKind::Instance,
2567 maximum_instances_per_parent: 10_000,
2568 maximum_descendants: 20_000,
2569 maximum_registry_bytes: 16_777_216,
2570 reserved_against_registry: registry,
2571 release_set: FleetSubnetRootReleaseSet {
2572 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2573 [15; 32],
2574 )),
2575 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2576 },
2577 phase: RootComponentAllocationPhase::Verified,
2578 creation: Some(RootComponentCreationEvidence {
2579 wasm_store: Principal::from_slice(&[21; 29]),
2580 payload_hash: [22; 32],
2581 payload_size_bytes: 4_096,
2582 initial_cycles: Cycles::new(5_000_000_000_000),
2583 controller: root,
2584 canister: Some(child),
2585 }),
2586 installation: Some(RootComponentChildInstallEvidence {
2587 raw_module_hash: [23; 32],
2588 chunk_hashes: vec![vec![24; 32]],
2589 binding: child_binding.clone(),
2590 }),
2591 };
2592 let commit_response = RootComponentChildCommitResponse {
2593 allocation: response.clone(),
2594 registry: ComponentRegistryPartitionResponse {
2595 head: ComponentRegistryHead {
2596 component,
2597 revision: 3,
2598 content_hash: [25; 32],
2599 },
2600 binding: child_binding.component.clone(),
2601 protocol_profile_digest: ProtocolProfileDigest::from_bytes([27; 32]),
2602 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2603 caller: Principal::from_slice(&[26; 29]),
2604 },
2605 release_set: response.release_set,
2606 status: ComponentLifecycleStatus::Active,
2607 reserved_descendants: 0,
2608 committed_descendants: 1,
2609 encoded_bytes: 8_192,
2610 },
2611 directory: ComponentDirectoryHead {
2612 provenance: ComponentDirectoryProvenance {
2613 component: child_binding.component.clone(),
2614 source_fleet_subnet_root: root,
2615 component_registry_revision: 3,
2616 component_registry_content_hash: [25; 32],
2617 synchronized_at_ns: 27,
2618 },
2619 descendant_count: 1,
2620 },
2621 };
2622 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2623 fleet: FleetDirectorySnapshot {
2624 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2625 registry: FleetRegistryVersion {
2626 authority: fleet_registry_authority(),
2627 revision: 4,
2628 content_hash: [28; 32],
2629 },
2630 source_fleet_subnet_root: root,
2631 },
2632 fleet_subnet_roots: vec![
2633 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2634 placement_subnet: commit_response.registry.binding.placement_subnet,
2635 fleet_subnet_root: root,
2636 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2637 },
2638 ],
2639 services: vec![],
2640 },
2641 component: commit_response.directory.clone(),
2642 component_group: None,
2643 };
2644 let activation = ComponentRuntimeActivationEvidence {
2645 directory_authority_hash: [29; 32],
2646 activated_at_ns: 30,
2647 };
2648 let directory_response = RootComponentChildDirectoryPreparationResponse {
2649 committed: commit_response.clone(),
2650 child: ComponentRuntimeStatusResponse {
2651 fixture: None,
2652 operation_id: request.operation_id,
2653 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2654 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2655 binding: child_binding.component.clone(),
2656 }),
2657 phase: ComponentRuntimePhase::DirectoryPrepared,
2658 authority: Some(runtime_authority.clone()),
2659 authority_hash: Some([31; 32]),
2660 direct_children_hash: Some([37; 32]),
2661 activation: None,
2662 },
2663 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2664 operation_id: [32; 32],
2665 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2666 covered_authority: runtime_authority.clone(),
2667 covered_authority_hash: [31; 32],
2668 activation,
2669 },
2670 parent: None,
2671 };
2672 let activation_response = RootComponentChildRuntimeActivationResponse {
2673 committed: commit_response.clone(),
2674 child: ComponentRuntimeStatusResponse {
2675 fixture: None,
2676 operation_id: request.operation_id,
2677 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2678 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2679 binding: child_binding.component.clone(),
2680 }),
2681 phase: ComponentRuntimePhase::Active,
2682 authority: Some(runtime_authority.clone()),
2683 authority_hash: Some([31; 32]),
2684 direct_children_hash: Some([37; 32]),
2685 activation: Some(ComponentRuntimeActivationEvidence {
2686 directory_authority_hash: [31; 32],
2687 activated_at_ns: 33,
2688 }),
2689 },
2690 };
2691 let active_directory = ComponentDirectoryHead {
2692 provenance: ComponentDirectoryProvenance {
2693 component: child_binding.component.clone(),
2694 source_fleet_subnet_root: root,
2695 component_registry_revision: 4,
2696 component_registry_content_hash: [34; 32],
2697 synchronized_at_ns: 35,
2698 },
2699 descendant_count: 1,
2700 };
2701 let active_authority = ComponentRuntimeDirectoryAuthority {
2702 fleet: runtime_authority.fleet,
2703 component: active_directory.clone(),
2704 component_group: None,
2705 };
2706 let membership_response = RootComponentChildMembershipActivationResponse {
2707 committed: commit_response.clone(),
2708 registry: ComponentRegistryPartitionResponse {
2709 head: ComponentRegistryHead {
2710 component,
2711 revision: 4,
2712 content_hash: [34; 32],
2713 },
2714 binding: child_binding.component.clone(),
2715 protocol_profile_digest: ProtocolProfileDigest::from_bytes([36; 32]),
2716 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2717 release_set: commit_response.registry.release_set,
2718 status: ComponentLifecycleStatus::Active,
2719 reserved_descendants: 0,
2720 committed_descendants: 1,
2721 encoded_bytes: 8_256,
2722 },
2723 directory: active_directory,
2724 child: ComponentRuntimeStatusResponse {
2725 fixture: None,
2726 operation_id: request.operation_id,
2727 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2728 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2729 binding: child_binding.component,
2730 }),
2731 phase: ComponentRuntimePhase::Active,
2732 authority: Some(active_authority),
2733 authority_hash: Some([36; 32]),
2734 direct_children_hash: Some([38; 32]),
2735 activation: Some(ComponentRuntimeActivationEvidence {
2736 directory_authority_hash: [31; 32],
2737 activated_at_ns: 33,
2738 }),
2739 },
2740 };
2741
2742 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2743 let status_bytes =
2744 candid::encode_one(status_request).expect("encode child reservation status");
2745 let creation_bytes =
2746 candid::encode_one(creation_request).expect("encode child creation request");
2747 let install_bytes =
2748 candid::encode_one(install_request).expect("encode child install request");
2749 let response_bytes = candid::encode_one(&response).expect("encode child response");
2750 let commit_request_bytes =
2751 candid::encode_one(commit_request).expect("encode child commit request");
2752 let directory_request_bytes =
2753 candid::encode_one(directory_request).expect("encode child Directory request");
2754 let activation_request_bytes =
2755 candid::encode_one(activation_request).expect("encode child activation request");
2756 let membership_request_bytes =
2757 candid::encode_one(membership_request).expect("encode child membership request");
2758 let commit_response_bytes =
2759 candid::encode_one(&commit_response).expect("encode child commit response");
2760 let directory_response_bytes =
2761 candid::encode_one(&directory_response).expect("encode child Directory response");
2762 let activation_response_bytes =
2763 candid::encode_one(&activation_response).expect("encode child activation response");
2764 let membership_response_bytes =
2765 candid::encode_one(&membership_response).expect("encode child membership response");
2766
2767 assert_eq!(
2768 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2769 .expect("decode child reservation"),
2770 request
2771 );
2772 assert_eq!(
2773 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2774 .expect("decode child reservation status"),
2775 status_request
2776 );
2777 assert_eq!(
2778 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2779 .expect("decode child creation request"),
2780 creation_request
2781 );
2782 assert_eq!(
2783 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2784 .expect("decode child install request"),
2785 install_request
2786 );
2787 assert_eq!(
2788 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2789 .expect("decode child response"),
2790 response
2791 );
2792 assert_eq!(
2793 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2794 .expect("decode child commit request"),
2795 commit_request
2796 );
2797 assert_eq!(
2798 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2799 &directory_request_bytes
2800 )
2801 .expect("decode child Directory request"),
2802 directory_request
2803 );
2804 assert_eq!(
2805 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2806 .expect("decode child commit response"),
2807 commit_response
2808 );
2809 assert_eq!(
2810 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2811 &directory_response_bytes
2812 )
2813 .expect("decode child Directory response"),
2814 directory_response
2815 );
2816 assert_eq!(
2817 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2818 &activation_request_bytes
2819 )
2820 .expect("decode child activation request"),
2821 activation_request
2822 );
2823 assert_eq!(
2824 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2825 &activation_response_bytes
2826 )
2827 .expect("decode child activation response"),
2828 activation_response
2829 );
2830 assert_eq!(
2831 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2832 &membership_request_bytes
2833 )
2834 .expect("decode child membership request"),
2835 membership_request
2836 );
2837 assert_eq!(
2838 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2839 &membership_response_bytes
2840 )
2841 .expect("decode child membership response"),
2842 membership_response
2843 );
2844 }
2845
2846 #[test]
2847 fn component_install_request_round_trips_through_candid() {
2848 let request = RootComponentInstallRequest {
2849 operation_id: [10; 32],
2850 };
2851 let bytes = candid::encode_one(request).expect("encode install request");
2852
2853 assert_eq!(
2854 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2855 .expect("decode install request"),
2856 request
2857 );
2858 }
2859
2860 #[test]
2861 fn component_commit_request_round_trips_through_candid() {
2862 let request = RootComponentCommitRequest {
2863 operation_id: [10; 32],
2864 };
2865 let bytes = candid::encode_one(request).expect("encode commit request");
2866
2867 assert_eq!(
2868 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2869 .expect("decode commit request"),
2870 request
2871 );
2872 }
2873
2874 #[test]
2875 fn component_runtime_activation_requests_round_trip_through_candid() {
2876 let root_request = RootComponentRuntimeActivationRequest {
2877 operation_id: [22; 32],
2878 };
2879 let target_request = ComponentRuntimeActivationRequest {
2880 operation_id: root_request.operation_id,
2881 directory_authority_hash: [23; 32],
2882 };
2883 let membership_request = RootComponentMembershipActivationRequest {
2884 operation_id: root_request.operation_id,
2885 };
2886 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2887 let target_bytes =
2888 candid::encode_one(target_request).expect("encode target activation request");
2889 let membership_bytes =
2890 candid::encode_one(membership_request).expect("encode membership activation request");
2891
2892 assert_eq!(
2893 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2894 .expect("decode root activation request"),
2895 root_request
2896 );
2897 assert_eq!(
2898 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2899 .expect("decode target activation request"),
2900 target_request
2901 );
2902 assert_eq!(
2903 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2904 .expect("decode membership activation request"),
2905 membership_request
2906 );
2907 }
2908}