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