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 },
1758 epoch: 1,
1759 }
1760 }
1761
1762 #[test]
1763 fn component_creation_request_round_trips_through_candid() {
1764 let request = RootComponentCreationRequest {
1765 operation_id: [10; 32],
1766 };
1767 let bytes = candid::encode_one(request).expect("encode creation request");
1768
1769 assert_eq!(
1770 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1771 .expect("decode creation request"),
1772 request
1773 );
1774 }
1775
1776 #[test]
1777 fn peer_component_provisioning_origin_round_trips_through_candid() {
1778 let authority = fleet_registry_authority();
1779 let requester_spec: ComponentSpecId =
1780 "projects".parse().expect("requester Component Spec ID");
1781 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1782 let origin = ComponentProvisioningOrigin::Component {
1783 requester: Box::new(ComponentBinding {
1784 authority,
1785 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1786 component_spec: requester_spec.clone(),
1787 spec_hash: [21; 32],
1788 role: CanisterRole::new("project_hub"),
1789 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1790 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1791 canister_id: Principal::from_slice(&[24; 29]),
1792 }),
1793 grant: Box::new(crate::config::ComponentProvisioningGrant {
1794 requester_component_spec: requester_spec,
1795 target_component_spec: target_spec,
1796 maximum_instances_per_requester_per_root: 3,
1797 }),
1798 };
1799 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1800
1801 assert_eq!(
1802 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1803 .expect("decode peer provisioning origin"),
1804 origin
1805 );
1806 }
1807
1808 #[test]
1809 fn cross_root_peer_request_and_origin_round_trip_through_candid() {
1810 let authority = fleet_registry_authority();
1811 let requester_spec: ComponentSpecId =
1812 "projects".parse().expect("requester Component Spec ID");
1813 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1814 let registry = FleetRegistryVersion {
1815 authority: authority.clone(),
1816 revision: 7,
1817 content_hash: [30; 32],
1818 };
1819 let request = RootPeerComponentAllocationRequest {
1820 operation_id: [31; 32],
1821 component_spec: target_spec.clone(),
1822 requester: PeerComponentRequester::FleetService {
1823 service: "projects".parse().expect("Fleet service ID"),
1824 expected_registry: Box::new(registry.clone()),
1825 },
1826 };
1827 let origin = ComponentProvisioningOrigin::FleetServiceComponent {
1828 requester: Box::new(FleetServiceComponentRequester {
1829 service: "projects".parse().expect("Fleet service ID"),
1830 member_purpose: FleetServiceMemberPurpose::PoolMember,
1831 group_placement: ComponentGroupPlacementId {
1832 deployment: "project_hubs".parse().expect("deployment ID"),
1833 ordinal: 2,
1834 },
1835 member_path: ComponentGroupMemberPath::try_from(vec![
1836 "hub".parse().expect("member ID"),
1837 ])
1838 .expect("member path"),
1839 component: ComponentBinding {
1840 authority,
1841 component: ComponentInstanceId::from_generated_bytes([32; 32]),
1842 component_spec: requester_spec.clone(),
1843 spec_hash: [33; 32],
1844 role: CanisterRole::new("project_hub"),
1845 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[34; 29])),
1846 fleet_subnet_root: Principal::from_slice(&[35; 29]),
1847 canister_id: Principal::from_slice(&[36; 29]),
1848 },
1849 }),
1850 registry: Box::new(registry),
1851 grant: Box::new(crate::config::ComponentProvisioningGrant {
1852 requester_component_spec: requester_spec,
1853 target_component_spec: target_spec,
1854 maximum_instances_per_requester_per_root: 3,
1855 }),
1856 };
1857 let request_bytes = candid::encode_one(&request).expect("encode cross-root request");
1858 let origin_bytes = candid::encode_one(&origin).expect("encode cross-root origin");
1859
1860 assert_eq!(
1861 candid::decode_one::<RootPeerComponentAllocationRequest>(&request_bytes)
1862 .expect("decode cross-root request"),
1863 request
1864 );
1865 assert_eq!(
1866 candid::decode_one::<ComponentProvisioningOrigin>(&origin_bytes)
1867 .expect("decode cross-root origin"),
1868 origin
1869 );
1870 }
1871
1872 #[test]
1873 fn component_group_provisioning_origin_round_trips_through_candid() {
1874 let origin = ComponentProvisioningOrigin::ComponentGroup {
1875 operation_id: [25; 32],
1876 plan_hash: [26; 32],
1877 group_placement: ComponentGroupPlacementId {
1878 deployment: "cells".parse().expect("deployment ID"),
1879 ordinal: 3,
1880 },
1881 member_path: ComponentGroupMemberPath::try_from(vec![
1882 "api".parse().expect("member ID"),
1883 ])
1884 .expect("member path"),
1885 };
1886 let bytes = candid::encode_one(&origin).expect("encode group provisioning origin");
1887
1888 assert_eq!(
1889 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1890 .expect("decode group provisioning origin"),
1891 origin
1892 );
1893 }
1894
1895 #[test]
1896 #[expect(
1897 clippy::too_many_lines,
1898 reason = "one Candid contract test covers every subtree-removal phase receipt"
1899 )]
1900 fn component_subtree_removal_contracts_round_trip_through_candid() {
1901 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1902 let registry = ComponentRegistryHead {
1903 component,
1904 revision: 7,
1905 content_hash: [42; 32],
1906 };
1907 let request = RootComponentSubtreeRemovalRequest {
1908 operation_id: [43; 32],
1909 component,
1910 target_canister_id: Principal::from_slice(&[44; 29]),
1911 expected_registry: registry.clone(),
1912 };
1913 let status_request = RootComponentSubtreeRemovalStatusRequest {
1914 operation_id: request.operation_id,
1915 component,
1916 };
1917 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1918 operation_id: request.operation_id,
1919 component,
1920 expected_traversal_steps: 1,
1921 };
1922 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1923 operation_id: request.operation_id,
1924 component,
1925 expected_traversal_steps: 2,
1926 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1927 expected_leaf_parent_canister_id: request.target_canister_id,
1928 };
1929 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1930 observed_module_hash: [49; 32],
1931 stop: RootComponentSubtreeRemovalStopIntent {
1932 controller: Principal::from_slice(&[48; 29]),
1933 leaf: RootComponentSubtreeRemovalNode {
1934 canister_id: Principal::from_slice(&[46; 29]),
1935 parent_canister_id: request.target_canister_id,
1936 role: CanisterRole::new("project_ledger"),
1937 kind: ComponentChildKind::Singleton,
1938 installed_artifact_hash: [47; 32],
1939 status: ComponentLifecycleStatus::Active,
1940 },
1941 },
1942 };
1943 let response = RootComponentSubtreeRemovalResponse {
1944 operation_id: request.operation_id,
1945 component,
1946 target_canister_id: request.target_canister_id,
1947 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1948 target_role: CanisterRole::new("project_instance"),
1949 target_status: ComponentLifecycleStatus::Active,
1950 reserved_against_registry: registry,
1951 maximum_completed_leaves: 4,
1952 completed_leaves: 1,
1953 traversal_steps: 2,
1954 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1955 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1956 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1957 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1958 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1959 },
1960 removed_from_registry: ComponentRegistryHead {
1961 component,
1962 revision: 8,
1963 content_hash: [50; 32],
1964 },
1965 previous_descendant_content_hash: [51; 32],
1966 previous_committed_descendants: 4,
1967 registry: ComponentRegistryHead {
1968 component,
1969 revision: 9,
1970 content_hash: [52; 32],
1971 },
1972 descendant_content_hash: [53; 32],
1973 registry_encoded_bytes: 4_096,
1974 reserved_descendants: 1,
1975 committed_descendants: 3,
1976 directory_synchronized_at_ns: 54,
1977 directory_authority_hash: [55; 32],
1978 parent_role_instances: 0,
1979 root_managed_descendants: 4,
1980 root_known_created_component_canisters: 4,
1981 },
1982 covered_fleet_registry_revision: 6,
1983 covered_fleet_registry_content_hash: [56; 32],
1984 covered_component_registry: ComponentRegistryHead {
1985 component,
1986 revision: 9,
1987 content_hash: [52; 32],
1988 },
1989 covered_authority_hash: [55; 32],
1990 owning_component: Some(
1991 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1992 operation_id: [57; 32],
1993 canister_id: Principal::from_slice(&[58; 29]),
1994 activation: ComponentRuntimeActivationEvidence {
1995 directory_authority_hash: [59; 32],
1996 activated_at_ns: 60,
1997 },
1998 },
1999 ),
2000 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
2001 operation_id: [61; 32],
2002 canister_id: request.target_canister_id,
2003 activation: ComponentRuntimeActivationEvidence {
2004 directory_authority_hash: [62; 32],
2005 activated_at_ns: 63,
2006 },
2007 }),
2008 },
2009 ),
2010 };
2011
2012 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
2013 let advance_bytes =
2014 candid::encode_one(advance_request).expect("encode subtree removal advance request");
2015 let stop_bytes =
2016 candid::encode_one(stop_request).expect("encode subtree removal stop request");
2017 let status_bytes =
2018 candid::encode_one(status_request).expect("encode subtree removal status request");
2019 let response_bytes =
2020 candid::encode_one(&response).expect("encode subtree removal response");
2021
2022 assert_eq!(
2023 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
2024 .expect("decode subtree removal request"),
2025 request
2026 );
2027 assert_eq!(
2028 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
2029 .expect("decode subtree removal advance request"),
2030 advance_request
2031 );
2032 assert_eq!(
2033 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
2034 .expect("decode subtree removal stop request"),
2035 stop_request
2036 );
2037 assert_eq!(
2038 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
2039 .expect("decode subtree removal status request"),
2040 status_request
2041 );
2042 assert_eq!(
2043 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
2044 .expect("decode subtree removal response"),
2045 response
2046 );
2047
2048 let mut quiescent_owner_response = response;
2049 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
2050 &mut quiescent_owner_response.phase
2051 else {
2052 panic!("Directory-synchronized response");
2053 };
2054 receipt.owning_component = None;
2055 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
2056 .expect("encode quiescent-owner subtree response");
2057 assert_eq!(
2058 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
2059 .expect("decode quiescent-owner subtree response"),
2060 quiescent_owner_response
2061 );
2062 }
2063
2064 #[test]
2065 fn component_draining_contracts_round_trip_through_candid() {
2066 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
2067 let previous_registry = ComponentRegistryHead {
2068 component,
2069 revision: 7,
2070 content_hash: [61; 32],
2071 };
2072 let request = RootComponentDrainingRequest {
2073 operation_id: [62; 32],
2074 component,
2075 expected_registry: previous_registry.clone(),
2076 };
2077 let status_request = RootComponentDrainingStatusRequest {
2078 operation_id: request.operation_id,
2079 component,
2080 };
2081 let response = RootComponentDrainingResponse {
2082 operation_id: request.operation_id,
2083 component,
2084 previous_registry,
2085 registry: ComponentRegistryHead {
2086 component,
2087 revision: 8,
2088 content_hash: [63; 32],
2089 },
2090 descendant_count: 20_000,
2091 descendant_content_hash: [64; 32],
2092 directory_authority_hash: [65; 32],
2093 started_at_ns: 66,
2094 };
2095
2096 let request_bytes =
2097 candid::encode_one(&request).expect("encode Component draining request");
2098 let status_bytes =
2099 candid::encode_one(status_request).expect("encode Component draining status request");
2100 let response_bytes =
2101 candid::encode_one(&response).expect("encode Component draining response");
2102
2103 assert_eq!(
2104 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
2105 .expect("decode Component draining request"),
2106 request
2107 );
2108 assert_eq!(
2109 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
2110 .expect("decode Component draining status request"),
2111 status_request
2112 );
2113 assert_eq!(
2114 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
2115 .expect("decode Component draining response"),
2116 response
2117 );
2118 }
2119
2120 #[test]
2121 fn component_quiescence_contracts_round_trip_through_candid() {
2122 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
2123 let registry = ComponentRegistryHead {
2124 component,
2125 revision: 9,
2126 content_hash: [68; 32],
2127 };
2128 let request = RootComponentQuiescenceRequest {
2129 operation_id: [69; 32],
2130 component,
2131 expected_registry: registry.clone(),
2132 };
2133 let status_request = RootComponentQuiescenceStatusRequest {
2134 operation_id: request.operation_id,
2135 component,
2136 };
2137 let stop = RootComponentQuiescenceStopIntent {
2138 registry,
2139 descendant_count: 20_000,
2140 descendant_content_hash: [70; 32],
2141 canister_id: Principal::from_slice(&[71; 29]),
2142 controller: Principal::from_slice(&[72; 29]),
2143 expected_module_hash: [73; 32],
2144 covered_fleet_registry_revision: 10,
2145 covered_fleet_registry_content_hash: [74; 32],
2146 covered_authority_hash: [75; 32],
2147 runtime_operation_id: [76; 32],
2148 activation: ComponentRuntimeActivationEvidence {
2149 directory_authority_hash: [77; 32],
2150 activated_at_ns: 78,
2151 },
2152 prepared_at_ns: 79,
2153 };
2154 let response = RootComponentQuiescenceResponse {
2155 operation_id: request.operation_id,
2156 component,
2157 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
2158 stop,
2159 observed_module_hash: [73; 32],
2160 quiesced_at_ns: 80,
2161 }),
2162 };
2163
2164 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
2165 let status_bytes =
2166 candid::encode_one(status_request).expect("encode quiescence status request");
2167 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
2168 assert_eq!(
2169 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
2170 .expect("decode quiescence request"),
2171 request
2172 );
2173 assert_eq!(
2174 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
2175 .expect("decode quiescence status request"),
2176 status_request
2177 );
2178 assert_eq!(
2179 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
2180 .expect("decode quiescence response"),
2181 response
2182 );
2183 }
2184
2185 #[test]
2186 fn component_draining_advance_contracts_round_trip_through_candid() {
2187 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
2188 let registry = ComponentRegistryHead {
2189 component,
2190 revision: 12,
2191 content_hash: [82; 32],
2192 };
2193 let request = RootComponentDrainingAdvanceRequest {
2194 operation_id: [83; 32],
2195 component,
2196 };
2197 let descendant_removal = RootComponentDrainingAdvanceResponse {
2198 operation_id: request.operation_id,
2199 component,
2200 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2201 RootComponentSubtreeRemovalResponse {
2202 operation_id: [84; 32],
2203 component,
2204 target_canister_id: Principal::from_slice(&[85; 29]),
2205 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2206 target_role: CanisterRole::new("project_instance"),
2207 target_status: ComponentLifecycleStatus::Active,
2208 reserved_against_registry: registry.clone(),
2209 maximum_completed_leaves: 20_000,
2210 completed_leaves: 0,
2211 traversal_steps: 0,
2212 phase: RootComponentSubtreeRemovalPhase::Fenced,
2213 },
2214 ),
2215 };
2216 let descendants_empty = RootComponentDrainingAdvanceResponse {
2217 operation_id: request.operation_id,
2218 component,
2219 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2220 RootComponentDrainingDescendantsEmpty {
2221 registry,
2222 descendant_content_hash: [87; 32],
2223 },
2224 ),
2225 };
2226
2227 let request_bytes =
2228 candid::encode_one(request).expect("encode Component draining advance request");
2229 let removal_bytes = candid::encode_one(&descendant_removal)
2230 .expect("encode Component draining removal response");
2231 let empty_bytes = candid::encode_one(&descendants_empty)
2232 .expect("encode Component draining empty response");
2233
2234 assert_eq!(
2235 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2236 .expect("decode Component draining advance request"),
2237 request
2238 );
2239 assert_eq!(
2240 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2241 .expect("decode Component draining removal response"),
2242 descendant_removal
2243 );
2244 assert_eq!(
2245 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2246 .expect("decode Component draining empty response"),
2247 descendants_empty
2248 );
2249 }
2250
2251 #[test]
2252 #[expect(
2253 clippy::too_many_lines,
2254 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2255 )]
2256 fn component_final_inventory_contracts_round_trip_through_candid() {
2257 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2258 let registry = ComponentRegistryHead {
2259 component,
2260 revision: 21,
2261 content_hash: [89; 32],
2262 };
2263 let request = RootComponentFinalInventoryRequest {
2264 operation_id: [90; 32],
2265 component,
2266 expected_registry: registry.clone(),
2267 };
2268 let inventory = RootComponentFinalInventory {
2269 registry,
2270 descendant_content_hash: [91; 32],
2271 registry_encoded_bytes: 4_096,
2272 directory_synchronized_at_ns: 92,
2273 covered_fleet_registry_revision: 93,
2274 covered_fleet_registry_content_hash: [94; 32],
2275 directory_authority_hash: [95; 32],
2276 inventory_hash: [96; 32],
2277 finalized_at_ns: 97,
2278 };
2279 let response = RootComponentFinalInventoryResponse {
2280 operation_id: request.operation_id,
2281 component,
2282 inventory: inventory.clone(),
2283 };
2284 let deletion_request = RootComponentDeletionRequest {
2285 operation_id: request.operation_id,
2286 component,
2287 expected_inventory_hash: inventory.inventory_hash,
2288 };
2289 let deletion_status_request = RootComponentDeletionStatusRequest {
2290 operation_id: request.operation_id,
2291 component,
2292 };
2293 let deletion = RootComponentDeletionIntent {
2294 final_inventory: inventory,
2295 quiescence: RootComponentQuiescentReceipt {
2296 stop: RootComponentQuiescenceStopIntent {
2297 registry: response.inventory.registry.clone(),
2298 descendant_count: 0,
2299 descendant_content_hash: response.inventory.descendant_content_hash,
2300 canister_id: Principal::from_slice(&[98; 29]),
2301 controller: Principal::from_slice(&[99; 29]),
2302 expected_module_hash: [100; 32],
2303 covered_fleet_registry_revision: 93,
2304 covered_fleet_registry_content_hash: [94; 32],
2305 covered_authority_hash: [101; 32],
2306 runtime_operation_id: [102; 32],
2307 activation: ComponentRuntimeActivationEvidence {
2308 directory_authority_hash: [103; 32],
2309 activated_at_ns: 104,
2310 },
2311 prepared_at_ns: 105,
2312 },
2313 observed_module_hash: [100; 32],
2314 quiesced_at_ns: 106,
2315 },
2316 prepared_at_ns: 107,
2317 };
2318 let deleted_receipt = RootComponentDeletedReceipt {
2319 deletion,
2320 deleted_at_ns: 108,
2321 };
2322 let deletion_response = RootComponentDeletionResponse {
2323 operation_id: request.operation_id,
2324 component,
2325 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2326 };
2327 let membership_removed_response = RootComponentDeletionResponse {
2328 operation_id: request.operation_id,
2329 component,
2330 phase: RootComponentDeletionPhase::MembershipRemoved(
2331 RootComponentMembershipRemovedReceipt {
2332 deleted: deleted_receipt,
2333 allocation_operation_id: [109; 32],
2334 remaining_spec_committed_instances: 2,
2335 root_committed_component_instances: 3,
2336 root_known_created_component_canisters: 4,
2337 root_registry_encoded_bytes: 5_000,
2338 removed_at_ns: 110,
2339 removal_hash: [111; 32],
2340 },
2341 ),
2342 };
2343
2344 let request_bytes =
2345 candid::encode_one(&request).expect("encode Component final inventory request");
2346 let response_bytes =
2347 candid::encode_one(&response).expect("encode Component final inventory response");
2348 let deletion_request_bytes =
2349 candid::encode_one(deletion_request).expect("encode Component deletion request");
2350 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2351 .expect("encode Component deletion status request");
2352 let deletion_response_bytes =
2353 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2354 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2355 .expect("encode Component membership-removal response");
2356 assert_eq!(
2357 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2358 .expect("decode Component final inventory request"),
2359 request
2360 );
2361 assert_eq!(
2362 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2363 .expect("decode Component final inventory response"),
2364 response
2365 );
2366 assert_eq!(
2367 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2368 .expect("decode Component deletion request"),
2369 deletion_request
2370 );
2371 assert_eq!(
2372 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2373 .expect("decode Component deletion status request"),
2374 deletion_status_request
2375 );
2376 assert_eq!(
2377 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2378 .expect("decode Component deletion response"),
2379 deletion_response
2380 );
2381 assert_eq!(
2382 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2383 .expect("decode Component membership-removal response"),
2384 membership_removed_response
2385 );
2386 }
2387
2388 #[test]
2389 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2390 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2391 operation_id: [50; 32],
2392 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2393 expected_traversal_steps: 3,
2394 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2395 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2396 };
2397 let request = RootComponentSubtreeRemovalDeleteRequest {
2398 operation_id: prepare.operation_id,
2399 component: prepare.component,
2400 expected_traversal_steps: prepare.expected_traversal_steps,
2401 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2402 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2403 };
2404 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2405 operation_id: prepare.operation_id,
2406 component: prepare.component,
2407 expected_traversal_steps: prepare.expected_traversal_steps,
2408 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2409 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2410 };
2411 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2412 operation_id: prepare.operation_id,
2413 component: prepare.component,
2414 expected_traversal_steps: prepare.expected_traversal_steps,
2415 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2416 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2417 };
2418 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2419 operation_id: prepare.operation_id,
2420 component: prepare.component,
2421 expected_traversal_steps: prepare.expected_traversal_steps,
2422 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2423 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2424 };
2425 let prepare_bytes = candid::encode_one(prepare)
2426 .expect("encode subtree removal deletion preparation request");
2427 let request_bytes =
2428 candid::encode_one(request).expect("encode subtree removal deletion request");
2429 let membership_request_bytes = candid::encode_one(membership_request)
2430 .expect("encode subtree removal membership-removal request");
2431 let directory_request_bytes = candid::encode_one(directory_request)
2432 .expect("encode subtree removal Directory synchronization request");
2433 let finalization_request_bytes = candid::encode_one(finalization_request)
2434 .expect("encode subtree removal leaf-finalization request");
2435
2436 assert_eq!(
2437 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2438 &prepare_bytes
2439 )
2440 .expect("decode subtree removal deletion preparation request"),
2441 prepare
2442 );
2443 assert_eq!(
2444 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2445 .expect("decode subtree removal deletion request"),
2446 request
2447 );
2448 assert_eq!(
2449 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2450 &membership_request_bytes
2451 )
2452 .expect("decode subtree removal membership-removal request"),
2453 membership_request
2454 );
2455 assert_eq!(
2456 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2457 &directory_request_bytes
2458 )
2459 .expect("decode subtree removal Directory synchronization request"),
2460 directory_request
2461 );
2462 assert_eq!(
2463 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2464 &finalization_request_bytes
2465 )
2466 .expect("decode subtree removal leaf-finalization request"),
2467 finalization_request
2468 );
2469 }
2470
2471 #[test]
2472 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2473 let request = RootComponentSubtreeRemovalStopRequest {
2474 operation_id: [50; 32],
2475 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2476 expected_traversal_steps: 3,
2477 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2478 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2479 };
2480 let bytes =
2481 candid::encode_one(request).expect("encode subtree removal stop execution request");
2482
2483 assert_eq!(
2484 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2485 .expect("decode subtree removal stop execution request"),
2486 request
2487 );
2488 }
2489
2490 #[test]
2491 #[expect(
2492 clippy::too_many_lines,
2493 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2494 )]
2495 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2496 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2497 let registry = ComponentRegistryHead {
2498 component,
2499 revision: 2,
2500 content_hash: [12; 32],
2501 };
2502 let request = RootComponentChildAllocationRequest {
2503 operation_id: [13; 32],
2504 component,
2505 expected_registry: registry.clone(),
2506 child_role: CanisterRole::new("project_instance"),
2507 application_init_args: Some(vec![9, 8, 7]),
2508 };
2509 let status_request = RootComponentChildAllocationStatusRequest {
2510 operation_id: request.operation_id,
2511 component,
2512 };
2513 let creation_request = RootComponentChildCreationRequest {
2514 operation_id: request.operation_id,
2515 component,
2516 };
2517 let install_request = RootComponentChildInstallRequest {
2518 operation_id: request.operation_id,
2519 component,
2520 };
2521 let commit_request = RootComponentChildCommitRequest {
2522 operation_id: request.operation_id,
2523 component,
2524 };
2525 let directory_request = RootComponentChildDirectoryPreparationRequest {
2526 operation_id: request.operation_id,
2527 component,
2528 };
2529 let activation_request = RootComponentChildRuntimeActivationRequest {
2530 operation_id: request.operation_id,
2531 component,
2532 };
2533 let membership_request = RootComponentChildMembershipActivationRequest {
2534 operation_id: request.operation_id,
2535 component,
2536 };
2537 let root = Principal::from_slice(&[17; 29]);
2538 let parent = Principal::from_slice(&[14; 29]);
2539 let child = Principal::from_slice(&[18; 29]);
2540 let child_binding = ComponentChildBinding {
2541 component: ComponentBinding {
2542 authority: fleet_registry_authority(),
2543 component,
2544 component_spec: "projects".parse().expect("Component Spec"),
2545 spec_hash: [19; 32],
2546 role: CanisterRole::new("project_hub"),
2547 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2548 fleet_subnet_root: root,
2549 canister_id: parent,
2550 },
2551 parent_canister_id: parent,
2552 role: request.child_role.clone(),
2553 canister_id: child,
2554 };
2555 let response = RootComponentChildAllocationResponse {
2556 last_failure: None,
2557 operation_id: request.operation_id,
2558 component,
2559 parent_canister_id: parent,
2560 parent_role: CanisterRole::new("project_hub"),
2561 child_role: request.child_role.clone(),
2562 child_kind: ComponentChildKind::Instance,
2563 maximum_instances_per_parent: 10_000,
2564 maximum_descendants: 20_000,
2565 maximum_registry_bytes: 16_777_216,
2566 reserved_against_registry: registry,
2567 release_set: FleetSubnetRootReleaseSet {
2568 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2569 [15; 32],
2570 )),
2571 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2572 },
2573 phase: RootComponentAllocationPhase::Verified,
2574 creation: Some(RootComponentCreationEvidence {
2575 wasm_store: Principal::from_slice(&[21; 29]),
2576 payload_hash: [22; 32],
2577 payload_size_bytes: 4_096,
2578 initial_cycles: Cycles::new(5_000_000_000_000),
2579 controller: root,
2580 canister: Some(child),
2581 }),
2582 installation: Some(RootComponentChildInstallEvidence {
2583 raw_module_hash: [23; 32],
2584 chunk_hashes: vec![vec![24; 32]],
2585 binding: child_binding.clone(),
2586 }),
2587 };
2588 let commit_response = RootComponentChildCommitResponse {
2589 allocation: response.clone(),
2590 registry: ComponentRegistryPartitionResponse {
2591 head: ComponentRegistryHead {
2592 component,
2593 revision: 3,
2594 content_hash: [25; 32],
2595 },
2596 binding: child_binding.component.clone(),
2597 protocol_profile_digest: ProtocolProfileDigest::from_bytes([27; 32]),
2598 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2599 caller: Principal::from_slice(&[26; 29]),
2600 },
2601 release_set: response.release_set,
2602 status: ComponentLifecycleStatus::Active,
2603 reserved_descendants: 0,
2604 committed_descendants: 1,
2605 encoded_bytes: 8_192,
2606 },
2607 directory: ComponentDirectoryHead {
2608 provenance: ComponentDirectoryProvenance {
2609 component: child_binding.component.clone(),
2610 source_fleet_subnet_root: root,
2611 component_registry_revision: 3,
2612 component_registry_content_hash: [25; 32],
2613 synchronized_at_ns: 27,
2614 },
2615 descendant_count: 1,
2616 },
2617 };
2618 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2619 fleet: FleetDirectorySnapshot {
2620 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2621 registry: FleetRegistryVersion {
2622 authority: fleet_registry_authority(),
2623 revision: 4,
2624 content_hash: [28; 32],
2625 },
2626 source_fleet_subnet_root: root,
2627 },
2628 fleet_subnet_roots: vec![
2629 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2630 placement_subnet: commit_response.registry.binding.placement_subnet,
2631 fleet_subnet_root: root,
2632 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2633 },
2634 ],
2635 services: vec![],
2636 },
2637 component: commit_response.directory.clone(),
2638 component_group: None,
2639 };
2640 let activation = ComponentRuntimeActivationEvidence {
2641 directory_authority_hash: [29; 32],
2642 activated_at_ns: 30,
2643 };
2644 let directory_response = RootComponentChildDirectoryPreparationResponse {
2645 committed: commit_response.clone(),
2646 child: ComponentRuntimeStatusResponse {
2647 fixture: None,
2648 operation_id: request.operation_id,
2649 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2650 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2651 binding: child_binding.component.clone(),
2652 }),
2653 phase: ComponentRuntimePhase::DirectoryPrepared,
2654 authority: Some(runtime_authority.clone()),
2655 authority_hash: Some([31; 32]),
2656 direct_children_hash: Some([37; 32]),
2657 activation: None,
2658 },
2659 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2660 operation_id: [32; 32],
2661 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2662 covered_authority: runtime_authority.clone(),
2663 covered_authority_hash: [31; 32],
2664 activation,
2665 },
2666 parent: None,
2667 };
2668 let activation_response = RootComponentChildRuntimeActivationResponse {
2669 committed: commit_response.clone(),
2670 child: ComponentRuntimeStatusResponse {
2671 fixture: None,
2672 operation_id: request.operation_id,
2673 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2674 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2675 binding: child_binding.component.clone(),
2676 }),
2677 phase: ComponentRuntimePhase::Active,
2678 authority: Some(runtime_authority.clone()),
2679 authority_hash: Some([31; 32]),
2680 direct_children_hash: Some([37; 32]),
2681 activation: Some(ComponentRuntimeActivationEvidence {
2682 directory_authority_hash: [31; 32],
2683 activated_at_ns: 33,
2684 }),
2685 },
2686 };
2687 let active_directory = ComponentDirectoryHead {
2688 provenance: ComponentDirectoryProvenance {
2689 component: child_binding.component.clone(),
2690 source_fleet_subnet_root: root,
2691 component_registry_revision: 4,
2692 component_registry_content_hash: [34; 32],
2693 synchronized_at_ns: 35,
2694 },
2695 descendant_count: 1,
2696 };
2697 let active_authority = ComponentRuntimeDirectoryAuthority {
2698 fleet: runtime_authority.fleet,
2699 component: active_directory.clone(),
2700 component_group: None,
2701 };
2702 let membership_response = RootComponentChildMembershipActivationResponse {
2703 committed: commit_response.clone(),
2704 registry: ComponentRegistryPartitionResponse {
2705 head: ComponentRegistryHead {
2706 component,
2707 revision: 4,
2708 content_hash: [34; 32],
2709 },
2710 binding: child_binding.component.clone(),
2711 protocol_profile_digest: ProtocolProfileDigest::from_bytes([36; 32]),
2712 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2713 release_set: commit_response.registry.release_set,
2714 status: ComponentLifecycleStatus::Active,
2715 reserved_descendants: 0,
2716 committed_descendants: 1,
2717 encoded_bytes: 8_256,
2718 },
2719 directory: active_directory,
2720 child: ComponentRuntimeStatusResponse {
2721 fixture: None,
2722 operation_id: request.operation_id,
2723 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2724 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2725 binding: child_binding.component,
2726 }),
2727 phase: ComponentRuntimePhase::Active,
2728 authority: Some(active_authority),
2729 authority_hash: Some([36; 32]),
2730 direct_children_hash: Some([38; 32]),
2731 activation: Some(ComponentRuntimeActivationEvidence {
2732 directory_authority_hash: [31; 32],
2733 activated_at_ns: 33,
2734 }),
2735 },
2736 };
2737
2738 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2739 let status_bytes =
2740 candid::encode_one(status_request).expect("encode child reservation status");
2741 let creation_bytes =
2742 candid::encode_one(creation_request).expect("encode child creation request");
2743 let install_bytes =
2744 candid::encode_one(install_request).expect("encode child install request");
2745 let response_bytes = candid::encode_one(&response).expect("encode child response");
2746 let commit_request_bytes =
2747 candid::encode_one(commit_request).expect("encode child commit request");
2748 let directory_request_bytes =
2749 candid::encode_one(directory_request).expect("encode child Directory request");
2750 let activation_request_bytes =
2751 candid::encode_one(activation_request).expect("encode child activation request");
2752 let membership_request_bytes =
2753 candid::encode_one(membership_request).expect("encode child membership request");
2754 let commit_response_bytes =
2755 candid::encode_one(&commit_response).expect("encode child commit response");
2756 let directory_response_bytes =
2757 candid::encode_one(&directory_response).expect("encode child Directory response");
2758 let activation_response_bytes =
2759 candid::encode_one(&activation_response).expect("encode child activation response");
2760 let membership_response_bytes =
2761 candid::encode_one(&membership_response).expect("encode child membership response");
2762
2763 assert_eq!(
2764 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2765 .expect("decode child reservation"),
2766 request
2767 );
2768 assert_eq!(
2769 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2770 .expect("decode child reservation status"),
2771 status_request
2772 );
2773 assert_eq!(
2774 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2775 .expect("decode child creation request"),
2776 creation_request
2777 );
2778 assert_eq!(
2779 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2780 .expect("decode child install request"),
2781 install_request
2782 );
2783 assert_eq!(
2784 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2785 .expect("decode child response"),
2786 response
2787 );
2788 assert_eq!(
2789 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2790 .expect("decode child commit request"),
2791 commit_request
2792 );
2793 assert_eq!(
2794 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2795 &directory_request_bytes
2796 )
2797 .expect("decode child Directory request"),
2798 directory_request
2799 );
2800 assert_eq!(
2801 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2802 .expect("decode child commit response"),
2803 commit_response
2804 );
2805 assert_eq!(
2806 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2807 &directory_response_bytes
2808 )
2809 .expect("decode child Directory response"),
2810 directory_response
2811 );
2812 assert_eq!(
2813 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2814 &activation_request_bytes
2815 )
2816 .expect("decode child activation request"),
2817 activation_request
2818 );
2819 assert_eq!(
2820 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2821 &activation_response_bytes
2822 )
2823 .expect("decode child activation response"),
2824 activation_response
2825 );
2826 assert_eq!(
2827 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2828 &membership_request_bytes
2829 )
2830 .expect("decode child membership request"),
2831 membership_request
2832 );
2833 assert_eq!(
2834 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2835 &membership_response_bytes
2836 )
2837 .expect("decode child membership response"),
2838 membership_response
2839 );
2840 }
2841
2842 #[test]
2843 fn component_install_request_round_trips_through_candid() {
2844 let request = RootComponentInstallRequest {
2845 operation_id: [10; 32],
2846 };
2847 let bytes = candid::encode_one(request).expect("encode install request");
2848
2849 assert_eq!(
2850 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2851 .expect("decode install request"),
2852 request
2853 );
2854 }
2855
2856 #[test]
2857 fn component_commit_request_round_trips_through_candid() {
2858 let request = RootComponentCommitRequest {
2859 operation_id: [10; 32],
2860 };
2861 let bytes = candid::encode_one(request).expect("encode commit request");
2862
2863 assert_eq!(
2864 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2865 .expect("decode commit request"),
2866 request
2867 );
2868 }
2869
2870 #[test]
2871 fn component_runtime_activation_requests_round_trip_through_candid() {
2872 let root_request = RootComponentRuntimeActivationRequest {
2873 operation_id: [22; 32],
2874 };
2875 let target_request = ComponentRuntimeActivationRequest {
2876 operation_id: root_request.operation_id,
2877 directory_authority_hash: [23; 32],
2878 };
2879 let membership_request = RootComponentMembershipActivationRequest {
2880 operation_id: root_request.operation_id,
2881 };
2882 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2883 let target_bytes =
2884 candid::encode_one(target_request).expect("encode target activation request");
2885 let membership_bytes =
2886 candid::encode_one(membership_request).expect("encode membership activation request");
2887
2888 assert_eq!(
2889 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2890 .expect("decode root activation request"),
2891 root_request
2892 );
2893 assert_eq!(
2894 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2895 .expect("decode target activation request"),
2896 target_request
2897 );
2898 assert_eq!(
2899 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2900 .expect("decode membership activation request"),
2901 membership_request
2902 );
2903 }
2904}