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