1use crate::{
8 cdk::types::Cycles,
9 config::schema::ComponentChildKind,
10 dto::{
11 component_deployment::ProtectedComponentDeployment,
12 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
13 root_store::RootStoreBootstrapRequest,
14 },
15 ids::{
16 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
17 ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
18 ManagedCanisterBinding,
19 },
20};
21use candid::{CandidType, Principal};
22use serde::{Deserialize, Serialize};
23
24#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct RootComponentRegistryPreparationRequest {
32 pub store_bootstrap: RootStoreBootstrapRequest,
33 pub expected_fleet_registry: FleetRegistryVersion,
34}
35
36#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
43pub struct RootComponentInitialInventoryStatus {
44 pub fleet_activation_operation_id: [u8; 32],
45 pub component_count: u32,
46 pub inventory_hash: [u8; 32],
47 pub sealed_at_ns: u64,
48 pub directories_converged: bool,
49 pub root_runtime_activated: bool,
50}
51
52#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
59pub struct RootComponentRegistryStatusResponse {
60 pub fleet_subnet_root: Principal,
61 pub prepared_against_registry: FleetRegistryVersion,
62 pub release_set: FleetSubnetRootReleaseSet,
63 pub component_topology_digest: ComponentTopologyDigest,
64 pub next_allocation_sequence: u64,
65 pub reserved_component_instances: u32,
66 pub committed_component_instances: u32,
67 pub managed_descendants: u32,
68 pub known_created_component_canisters: u32,
69 pub encoded_bytes: u64,
70 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
71}
72
73#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
80pub struct RootComponentAllocationRequest {
81 pub operation_id: [u8; 32],
82 pub component_spec: ComponentSpecId,
83}
84
85#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
92pub struct RootComponentAllocationStatusRequest {
93 pub operation_id: [u8; 32],
94}
95
96#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103pub struct RootComponentChildAllocationRequest {
104 pub operation_id: [u8; 32],
105 pub component: ComponentInstanceId,
106 pub expected_registry: ComponentRegistryHead,
107 pub child_role: CanisterRole,
108 pub application_init_args: Option<Vec<u8>>,
109}
110
111#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
118pub struct RootComponentChildAllocationStatusRequest {
119 pub operation_id: [u8; 32],
120 pub component: ComponentInstanceId,
121}
122
123#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
130pub struct RootComponentSubtreeRemovalRequest {
131 pub operation_id: [u8; 32],
132 pub component: ComponentInstanceId,
133 pub target_canister_id: Principal,
134 pub expected_registry: ComponentRegistryHead,
135}
136
137#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
144pub struct RootComponentSubtreeRemovalAdvanceRequest {
145 pub operation_id: [u8; 32],
146 pub component: ComponentInstanceId,
147 pub expected_traversal_steps: u32,
148}
149
150#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
157pub struct RootComponentSubtreeRemovalStopPreparationRequest {
158 pub operation_id: [u8; 32],
159 pub component: ComponentInstanceId,
160 pub expected_traversal_steps: u32,
161 pub expected_leaf_canister_id: Principal,
162 pub expected_leaf_parent_canister_id: Principal,
163}
164
165#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
172pub struct RootComponentSubtreeRemovalStopRequest {
173 pub operation_id: [u8; 32],
174 pub component: ComponentInstanceId,
175 pub expected_traversal_steps: u32,
176 pub expected_leaf_canister_id: Principal,
177 pub expected_leaf_parent_canister_id: Principal,
178}
179
180#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
188 pub operation_id: [u8; 32],
189 pub component: ComponentInstanceId,
190 pub expected_traversal_steps: u32,
191 pub expected_leaf_canister_id: Principal,
192 pub expected_leaf_parent_canister_id: Principal,
193}
194
195#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
202pub struct RootComponentSubtreeRemovalDeleteRequest {
203 pub operation_id: [u8; 32],
204 pub component: ComponentInstanceId,
205 pub expected_traversal_steps: u32,
206 pub expected_leaf_canister_id: Principal,
207 pub expected_leaf_parent_canister_id: Principal,
208}
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
217pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
218 pub operation_id: [u8; 32],
219 pub component: ComponentInstanceId,
220 pub expected_traversal_steps: u32,
221 pub expected_leaf_canister_id: Principal,
222 pub expected_leaf_parent_canister_id: Principal,
223}
224
225#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
232pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
233 pub operation_id: [u8; 32],
234 pub component: ComponentInstanceId,
235 pub expected_traversal_steps: u32,
236 pub expected_leaf_canister_id: Principal,
237 pub expected_leaf_parent_canister_id: Principal,
238}
239
240#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
247pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
248 pub operation_id: [u8; 32],
249 pub component: ComponentInstanceId,
250 pub expected_traversal_steps: u32,
251 pub expected_leaf_canister_id: Principal,
252 pub expected_leaf_parent_canister_id: Principal,
253}
254
255#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
262pub struct RootComponentSubtreeRemovalStatusRequest {
263 pub operation_id: [u8; 32],
264 pub component: ComponentInstanceId,
265}
266
267#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
274pub struct RootComponentDrainingRequest {
275 pub operation_id: [u8; 32],
276 pub component: ComponentInstanceId,
277 pub expected_registry: ComponentRegistryHead,
278}
279
280#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
287pub struct RootComponentDrainingStatusRequest {
288 pub operation_id: [u8; 32],
289 pub component: ComponentInstanceId,
290}
291
292#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
299pub struct RootComponentQuiescenceRequest {
300 pub operation_id: [u8; 32],
301 pub component: ComponentInstanceId,
302 pub expected_registry: ComponentRegistryHead,
303}
304
305#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
312pub struct RootComponentQuiescenceStatusRequest {
313 pub operation_id: [u8; 32],
314 pub component: ComponentInstanceId,
315}
316
317#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
324pub struct RootComponentDrainingAdvanceRequest {
325 pub operation_id: [u8; 32],
326 pub component: ComponentInstanceId,
327}
328
329#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
336pub struct RootComponentFinalInventoryRequest {
337 pub operation_id: [u8; 32],
338 pub component: ComponentInstanceId,
339 pub expected_registry: ComponentRegistryHead,
340}
341
342#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
349pub struct RootComponentDeletionRequest {
350 pub operation_id: [u8; 32],
351 pub component: ComponentInstanceId,
352 pub expected_inventory_hash: [u8; 32],
353}
354
355#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
362pub struct RootComponentDeletionStatusRequest {
363 pub operation_id: [u8; 32],
364 pub component: ComponentInstanceId,
365}
366
367#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
374pub struct RootComponentChildCreationRequest {
375 pub operation_id: [u8; 32],
376 pub component: ComponentInstanceId,
377}
378
379#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
386pub struct RootComponentChildInstallRequest {
387 pub operation_id: [u8; 32],
388 pub component: ComponentInstanceId,
389}
390
391#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
398pub struct RootComponentChildCommitRequest {
399 pub operation_id: [u8; 32],
400 pub component: ComponentInstanceId,
401}
402
403#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
410pub struct RootComponentChildDirectoryPreparationRequest {
411 pub operation_id: [u8; 32],
412 pub component: ComponentInstanceId,
413}
414
415#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
422pub struct RootComponentChildRuntimeActivationRequest {
423 pub operation_id: [u8; 32],
424 pub component: ComponentInstanceId,
425}
426
427#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
434pub struct RootComponentChildMembershipActivationRequest {
435 pub operation_id: [u8; 32],
436 pub component: ComponentInstanceId,
437}
438
439#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub struct RootComponentCreationRequest {
447 pub operation_id: [u8; 32],
448}
449
450#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
457pub struct RootComponentInstallRequest {
458 pub operation_id: [u8; 32],
459}
460
461#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
468pub struct RootComponentCommitRequest {
469 pub operation_id: [u8; 32],
470}
471
472#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
479pub struct RootComponentDirectoryPreparationRequest {
480 pub operation_id: [u8; 32],
481}
482
483#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
490pub struct RootComponentRuntimeActivationRequest {
491 pub operation_id: [u8; 32],
492}
493
494#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
501pub struct RootComponentMembershipActivationRequest {
502 pub operation_id: [u8; 32],
503}
504
505#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
512pub enum ComponentProvisioningOrigin {
513 FleetAdministrator {
514 caller: Principal,
515 },
516 Component {
517 requester: Box<ComponentBinding>,
518 grant: Box<crate::config::ComponentProvisioningGrant>,
519 },
520}
521
522#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
529pub enum RootComponentAllocationPhase {
530 Reserved,
531 CreationIntent,
532 Created,
533 InstallIntent,
534 Installed,
535 Verified,
536 Committed,
537 Removed,
538}
539
540#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
547#[expect(
548 clippy::large_enum_variant,
549 reason = "wire phases retain complete inline receipts for deterministic Candid responses"
550)]
551pub enum RootComponentSubtreeRemovalPhase {
552 Fenced,
553 Traversing(RootComponentSubtreeRemovalNode),
554 LeafSelected(RootComponentSubtreeRemovalNode),
555 StopIntent(RootComponentSubtreeRemovalStopIntent),
556 Stopped(RootComponentSubtreeRemovalStoppedReceipt),
557 DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
558 Deleted(RootComponentSubtreeRemovalDeletedReceipt),
559 MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
560 DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
561 Completed(RootComponentSubtreeRemovalCompletedReceipt),
562}
563
564#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
571pub struct RootComponentSubtreeRemovalNode {
572 pub canister_id: Principal,
573 pub parent_canister_id: Principal,
574 pub role: CanisterRole,
575 pub kind: ComponentChildKind,
576 pub installed_artifact_hash: [u8; 32],
577 pub status: ComponentLifecycleStatus,
578}
579
580#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
587pub struct RootComponentSubtreeRemovalStopIntent {
588 pub leaf: RootComponentSubtreeRemovalNode,
589 pub controller: Principal,
590}
591
592#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
599pub struct RootComponentSubtreeRemovalStoppedReceipt {
600 pub stop: RootComponentSubtreeRemovalStopIntent,
601 pub observed_module_hash: [u8; 32],
602}
603
604#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
611pub struct RootComponentSubtreeRemovalDeleteIntent {
612 pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
613}
614
615#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
622pub struct RootComponentSubtreeRemovalDeletedReceipt {
623 pub deletion: RootComponentSubtreeRemovalDeleteIntent,
624}
625
626#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
633pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
634 pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
635 pub removed_from_registry: ComponentRegistryHead,
636 pub previous_descendant_content_hash: [u8; 32],
637 pub previous_committed_descendants: u32,
638 pub registry: ComponentRegistryHead,
639 pub descendant_content_hash: [u8; 32],
640 pub registry_encoded_bytes: u64,
641 pub reserved_descendants: u32,
642 pub committed_descendants: u32,
643 pub directory_synchronized_at_ns: u64,
644 pub directory_authority_hash: [u8; 32],
645 pub parent_role_instances: u32,
646 pub root_managed_descendants: u32,
647 pub root_known_created_component_canisters: u32,
648}
649
650#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
657pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
658 pub operation_id: [u8; 32],
659 pub canister_id: Principal,
660 pub activation: ComponentRuntimeActivationEvidence,
661}
662
663#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
672pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
673 pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
674 pub covered_fleet_registry_revision: u64,
675 pub covered_fleet_registry_content_hash: [u8; 32],
676 pub covered_component_registry: ComponentRegistryHead,
677 pub covered_authority_hash: [u8; 32],
678 pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
679 pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
680}
681
682#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
689pub struct RootComponentSubtreeRemovalCompletedReceipt {
690 pub registry: ComponentRegistryHead,
691 pub directory_authority_hash: [u8; 32],
692}
693
694#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
701pub enum ComponentLifecycleStatus {
702 Prepared,
703 Active,
704 Draining,
705 Removed,
706}
707
708#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
715pub struct ComponentRegistryHead {
716 pub component: ComponentInstanceId,
717 pub revision: u64,
718 pub content_hash: [u8; 32],
719}
720
721#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
728pub struct ComponentRegistryPartitionRequest {
729 pub component: ComponentInstanceId,
730}
731
732#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
739pub struct ComponentRegistryPartitionResponse {
740 pub head: ComponentRegistryHead,
741 pub binding: ComponentBinding,
742 pub provisioning_origin: ComponentProvisioningOrigin,
743 pub release_set: FleetSubnetRootReleaseSet,
744 pub status: ComponentLifecycleStatus,
745 pub reserved_descendants: u32,
746 pub committed_descendants: u32,
747 pub encoded_bytes: u64,
748}
749
750#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
757pub struct ComponentDirectoryProvenance {
758 pub component: ComponentBinding,
759 pub source_fleet_subnet_root: Principal,
760 pub component_registry_revision: u64,
761 pub component_registry_content_hash: [u8; 32],
762 pub synchronized_at_ns: u64,
763}
764
765#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
772pub struct ComponentDirectoryHead {
773 pub provenance: ComponentDirectoryProvenance,
774 pub descendant_count: u32,
775}
776
777#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
784pub struct ComponentDirectoryHeadRequest {
785 pub component: ComponentInstanceId,
786}
787
788#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
795pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
796
797#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
804pub struct ComponentDirectoryPageRequest {
805 pub directory: ComponentDirectoryHead,
806 pub parent_canister_id: Option<Principal>,
807 pub role: Option<CanisterRole>,
808 pub status: Option<ComponentLifecycleStatus>,
809 pub cursor: Option<ComponentDirectoryPageCursor>,
810 pub limit: u16,
811}
812
813#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
820pub struct ComponentDirectoryChildEntry {
821 pub binding: ComponentChildBinding,
822 pub kind: ComponentChildKind,
823 pub installed_artifact_hash: [u8; 32],
824 pub status: ComponentLifecycleStatus,
825}
826
827#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
834pub struct ComponentDirectoryPageResponse {
835 pub directory: ComponentDirectoryHead,
836 pub entries: Vec<ComponentDirectoryChildEntry>,
837 pub next_cursor: Option<ComponentDirectoryPageCursor>,
838}
839
840#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
847pub struct ComponentRuntimeDirectoryAuthority {
848 pub fleet: FleetDirectorySnapshot,
849 pub component: ComponentDirectoryHead,
850}
851
852#[derive(CandidType, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
859pub struct ComponentRuntimeDirectChild {
860 pub canister_id: Principal,
861 pub role: CanisterRole,
862}
863
864#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
871pub struct ComponentRuntimeDirectoryPreparationRequest {
872 pub operation_id: [u8; 32],
873 pub authority: ComponentRuntimeDirectoryAuthority,
874 pub direct_children: Vec<ComponentRuntimeDirectChild>,
875}
876
877#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
884pub struct ComponentRuntimeDirectorySynchronizationRequest {
885 pub operation_id: [u8; 32],
886 pub authority: ComponentRuntimeDirectoryAuthority,
887 pub direct_children: Vec<ComponentRuntimeDirectChild>,
888}
889
890#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
897pub enum ComponentRuntimePhase {
898 AwaitingDirectory,
899 DirectoryPrepared,
900 Active,
901}
902
903#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
910pub struct ComponentRuntimeActivationEvidence {
911 pub directory_authority_hash: [u8; 32],
912 pub activated_at_ns: u64,
913}
914
915#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
922pub struct ComponentRuntimeActivationRequest {
923 pub operation_id: [u8; 32],
924 pub directory_authority_hash: [u8; 32],
925}
926
927#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
934pub struct ComponentRuntimeStatusResponse {
935 pub operation_id: [u8; 32],
936 pub binding: ManagedCanisterBinding,
937 pub deployment: Box<ProtectedComponentDeployment>,
938 pub phase: ComponentRuntimePhase,
939 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
940 pub authority_hash: Option<[u8; 32]>,
941 pub direct_children_hash: Option<[u8; 32]>,
942 pub activation: Option<ComponentRuntimeActivationEvidence>,
943}
944
945#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
952pub struct ComponentRuntimeDirectoryConvergenceEvidence {
953 pub operation_id: [u8; 32],
954 pub binding: ManagedCanisterBinding,
955 pub covered_authority: ComponentRuntimeDirectoryAuthority,
956 pub covered_authority_hash: [u8; 32],
957 pub activation: ComponentRuntimeActivationEvidence,
958}
959
960#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
967pub struct RootComponentCreationEvidence {
968 pub wasm_store: Principal,
969 pub payload_hash: [u8; 32],
970 pub payload_size_bytes: u64,
971 pub initial_cycles: Cycles,
972 pub controller: Principal,
973 pub canister: Option<Principal>,
974}
975
976#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
983pub struct RootComponentInstallEvidence {
984 pub raw_module_hash: [u8; 32],
985 pub chunk_hashes: Vec<Vec<u8>>,
986 pub binding: ComponentBinding,
987}
988
989#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
996pub struct RootComponentChildInstallEvidence {
997 pub raw_module_hash: [u8; 32],
998 pub chunk_hashes: Vec<Vec<u8>>,
999 pub binding: ComponentChildBinding,
1000}
1001
1002#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1009pub struct RootComponentAllocationResponse {
1010 pub operation_id: [u8; 32],
1011 pub allocation_sequence: u64,
1012 pub component: ComponentInstanceId,
1013 pub component_spec: ComponentSpecId,
1014 pub spec_hash: [u8; 32],
1015 pub role: CanisterRole,
1016 pub provisioning_origin: ComponentProvisioningOrigin,
1017 pub release_set: FleetSubnetRootReleaseSet,
1018 pub phase: RootComponentAllocationPhase,
1019 pub creation: Option<RootComponentCreationEvidence>,
1020 pub installation: Option<RootComponentInstallEvidence>,
1021}
1022
1023#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030pub struct RootComponentChildAllocationResponse {
1031 pub operation_id: [u8; 32],
1032 pub component: ComponentInstanceId,
1033 pub parent_canister_id: Principal,
1034 pub parent_role: CanisterRole,
1035 pub child_role: CanisterRole,
1036 pub child_kind: ComponentChildKind,
1037 pub maximum_instances_per_parent: u32,
1038 pub maximum_descendants: u32,
1039 pub maximum_registry_bytes: u64,
1040 pub reserved_against_registry: ComponentRegistryHead,
1041 pub release_set: FleetSubnetRootReleaseSet,
1042 pub phase: RootComponentAllocationPhase,
1043 pub creation: Option<RootComponentCreationEvidence>,
1044 pub installation: Option<RootComponentChildInstallEvidence>,
1045}
1046
1047#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1054pub struct RootComponentSubtreeRemovalResponse {
1055 pub operation_id: [u8; 32],
1056 pub component: ComponentInstanceId,
1057 pub target_canister_id: Principal,
1058 pub target_parent_canister_id: Principal,
1059 pub target_role: CanisterRole,
1060 pub target_status: ComponentLifecycleStatus,
1061 pub reserved_against_registry: ComponentRegistryHead,
1062 pub maximum_completed_leaves: u32,
1063 pub completed_leaves: u32,
1064 pub traversal_steps: u32,
1065 pub phase: RootComponentSubtreeRemovalPhase,
1066}
1067
1068#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1075pub struct RootComponentDrainingResponse {
1076 pub operation_id: [u8; 32],
1077 pub component: ComponentInstanceId,
1078 pub previous_registry: ComponentRegistryHead,
1079 pub registry: ComponentRegistryHead,
1080 pub descendant_count: u32,
1081 pub descendant_content_hash: [u8; 32],
1082 pub directory_authority_hash: [u8; 32],
1083 pub started_at_ns: u64,
1084}
1085
1086#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1093pub struct RootComponentQuiescenceStopIntent {
1094 pub registry: ComponentRegistryHead,
1095 pub descendant_count: u32,
1096 pub descendant_content_hash: [u8; 32],
1097 pub canister_id: Principal,
1098 pub controller: Principal,
1099 pub expected_module_hash: [u8; 32],
1100 pub covered_fleet_registry_revision: u64,
1101 pub covered_fleet_registry_content_hash: [u8; 32],
1102 pub covered_authority_hash: [u8; 32],
1103 pub runtime_operation_id: [u8; 32],
1104 pub activation: ComponentRuntimeActivationEvidence,
1105 pub prepared_at_ns: u64,
1106}
1107
1108#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1115pub struct RootComponentQuiescentReceipt {
1116 pub stop: RootComponentQuiescenceStopIntent,
1117 pub observed_module_hash: [u8; 32],
1118 pub quiesced_at_ns: u64,
1119}
1120
1121#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1128pub enum RootComponentQuiescencePhase {
1129 StopIntent(RootComponentQuiescenceStopIntent),
1130 Quiescent(RootComponentQuiescentReceipt),
1131}
1132
1133#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1140pub struct RootComponentQuiescenceResponse {
1141 pub operation_id: [u8; 32],
1142 pub component: ComponentInstanceId,
1143 pub phase: RootComponentQuiescencePhase,
1144}
1145
1146#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1153pub struct RootComponentDrainingDescendantsEmpty {
1154 pub registry: ComponentRegistryHead,
1155 pub descendant_content_hash: [u8; 32],
1156}
1157
1158#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1165#[expect(
1166 clippy::large_enum_variant,
1167 reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1168)]
1169pub enum RootComponentDrainingAdvancePhase {
1170 DescendantRemoval(RootComponentSubtreeRemovalResponse),
1171 DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1172}
1173
1174#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1181pub struct RootComponentDrainingAdvanceResponse {
1182 pub operation_id: [u8; 32],
1183 pub component: ComponentInstanceId,
1184 pub phase: RootComponentDrainingAdvancePhase,
1185}
1186
1187#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1194pub struct RootComponentFinalInventory {
1195 pub registry: ComponentRegistryHead,
1196 pub descendant_content_hash: [u8; 32],
1197 pub registry_encoded_bytes: u64,
1198 pub directory_synchronized_at_ns: u64,
1199 pub covered_fleet_registry_revision: u64,
1200 pub covered_fleet_registry_content_hash: [u8; 32],
1201 pub directory_authority_hash: [u8; 32],
1202 pub inventory_hash: [u8; 32],
1203 pub finalized_at_ns: u64,
1204}
1205
1206#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1213pub struct RootComponentFinalInventoryResponse {
1214 pub operation_id: [u8; 32],
1215 pub component: ComponentInstanceId,
1216 pub inventory: RootComponentFinalInventory,
1217}
1218
1219#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1226pub struct RootComponentDeletionIntent {
1227 pub final_inventory: RootComponentFinalInventory,
1228 pub quiescence: RootComponentQuiescentReceipt,
1229 pub prepared_at_ns: u64,
1230}
1231
1232#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1239pub struct RootComponentDeletedReceipt {
1240 pub deletion: RootComponentDeletionIntent,
1241 pub deleted_at_ns: u64,
1242}
1243
1244#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1251pub struct RootComponentMembershipRemovedReceipt {
1252 pub deleted: RootComponentDeletedReceipt,
1253 pub allocation_operation_id: [u8; 32],
1254 pub remaining_spec_committed_instances: u32,
1255 pub root_committed_component_instances: u32,
1256 pub root_known_created_component_canisters: u32,
1257 pub root_registry_encoded_bytes: u64,
1258 pub removed_at_ns: u64,
1259 pub removal_hash: [u8; 32],
1260}
1261
1262#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1269pub enum RootComponentDeletionPhase {
1270 DeleteIntent(RootComponentDeletionIntent),
1271 Deleted(RootComponentDeletedReceipt),
1272 MembershipRemoved(RootComponentMembershipRemovedReceipt),
1273}
1274
1275#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1282pub struct RootComponentDeletionResponse {
1283 pub operation_id: [u8; 32],
1284 pub component: ComponentInstanceId,
1285 pub phase: RootComponentDeletionPhase,
1286}
1287
1288#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1295pub struct RootComponentChildCommitResponse {
1296 pub allocation: RootComponentChildAllocationResponse,
1297 pub registry: ComponentRegistryPartitionResponse,
1298 pub directory: ComponentDirectoryHead,
1299}
1300
1301#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1308pub struct RootComponentChildDirectoryPreparationResponse {
1309 pub committed: RootComponentChildCommitResponse,
1310 pub child: ComponentRuntimeStatusResponse,
1311 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1312 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1313}
1314
1315#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1322pub struct RootComponentChildRuntimeActivationResponse {
1323 pub committed: RootComponentChildCommitResponse,
1324 pub child: ComponentRuntimeStatusResponse,
1325}
1326
1327#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1334pub struct RootComponentChildMembershipActivationResponse {
1335 pub committed: RootComponentChildCommitResponse,
1336 pub registry: ComponentRegistryPartitionResponse,
1337 pub directory: ComponentDirectoryHead,
1338 pub child: ComponentRuntimeStatusResponse,
1339}
1340
1341#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1348pub struct RootComponentCommitResponse {
1349 pub allocation: RootComponentAllocationResponse,
1350 pub registry: ComponentRegistryPartitionResponse,
1351 pub directory: ComponentDirectoryHead,
1352}
1353
1354#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1361pub struct RootComponentDirectoryPreparationResponse {
1362 pub committed: RootComponentCommitResponse,
1363 pub target: ComponentRuntimeStatusResponse,
1364}
1365
1366#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1373pub struct RootComponentRuntimeActivationResponse {
1374 pub committed: RootComponentCommitResponse,
1375 pub target: ComponentRuntimeStatusResponse,
1376}
1377
1378#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1385pub struct RootComponentMembershipActivationResponse {
1386 pub allocation: RootComponentAllocationResponse,
1387 pub registry: ComponentRegistryPartitionResponse,
1388 pub directory: ComponentDirectoryHead,
1389 pub target: ComponentRuntimeStatusResponse,
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394 use super::*;
1395 use crate::{
1396 dto::root_store::RootStoreBootstrapRequest,
1397 ids::{
1398 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1399 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1400 },
1401 };
1402
1403 #[test]
1404 fn component_registry_contracts_round_trip_through_candid() {
1405 let request = RootComponentRegistryPreparationRequest {
1406 store_bootstrap: RootStoreBootstrapRequest {
1407 manifest_payload_size_bytes: 128,
1408 },
1409 expected_fleet_registry: FleetRegistryVersion {
1410 authority: fleet_registry_authority(),
1411 revision: 4,
1412 content_hash: [5; 32],
1413 },
1414 };
1415 let response = RootComponentRegistryStatusResponse {
1416 fleet_subnet_root: Principal::from_slice(&[6; 29]),
1417 prepared_against_registry: request.expected_fleet_registry.clone(),
1418 release_set: FleetSubnetRootReleaseSet {
1419 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1420 [7; 32],
1421 )),
1422 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1423 },
1424 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1425 next_allocation_sequence: 1,
1426 reserved_component_instances: 0,
1427 committed_component_instances: 0,
1428 managed_descendants: 0,
1429 known_created_component_canisters: 0,
1430 encoded_bytes: 0,
1431 initial_inventory: Some(RootComponentInitialInventoryStatus {
1432 fleet_activation_operation_id: [10; 32],
1433 component_count: 0,
1434 inventory_hash: [11; 32],
1435 sealed_at_ns: 12,
1436 directories_converged: true,
1437 root_runtime_activated: true,
1438 }),
1439 };
1440 let allocation = RootComponentAllocationResponse {
1441 operation_id: [10; 32],
1442 allocation_sequence: 1,
1443 component: ComponentInstanceId::from_generated_bytes([11; 32]),
1444 component_spec: "projects".parse().expect("Component Spec ID"),
1445 spec_hash: [12; 32],
1446 role: CanisterRole::new("project_hub"),
1447 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1448 caller: Principal::from_slice(&[13; 29]),
1449 },
1450 release_set: response.release_set,
1451 phase: RootComponentAllocationPhase::Reserved,
1452 creation: None,
1453 installation: None,
1454 };
1455 let created = RootComponentAllocationResponse {
1456 phase: RootComponentAllocationPhase::Created,
1457 creation: Some(RootComponentCreationEvidence {
1458 wasm_store: Principal::from_slice(&[14; 29]),
1459 payload_hash: [15; 32],
1460 payload_size_bytes: 4_096,
1461 initial_cycles: Cycles::new(5_000_000_000_000),
1462 controller: Principal::from_slice(&[6; 29]),
1463 canister: Some(Principal::from_slice(&[16; 29])),
1464 }),
1465 installation: None,
1466 ..allocation.clone()
1467 };
1468 let request_bytes = candid::encode_one(&request).expect("encode request");
1469 let response_bytes = candid::encode_one(&response).expect("encode response");
1470 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1471 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1472
1473 assert_eq!(
1474 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1475 .expect("decode request"),
1476 request
1477 );
1478 assert_eq!(
1479 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1480 .expect("decode response"),
1481 response
1482 );
1483 assert_eq!(
1484 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1485 .expect("decode allocation"),
1486 allocation
1487 );
1488 assert_eq!(
1489 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1490 .expect("decode created allocation"),
1491 created
1492 );
1493 }
1494
1495 #[test]
1496 fn component_commit_response_round_trips_through_candid() {
1497 let root = Principal::from_slice(&[6; 29]);
1498 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1499 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1500 let release_set = FleetSubnetRootReleaseSet {
1501 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1502 [7; 32],
1503 )),
1504 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1505 };
1506 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1507 caller: Principal::from_slice(&[13; 29]),
1508 };
1509 let binding = ComponentBinding {
1510 authority: fleet_registry_authority(),
1511 component,
1512 component_spec: component_spec.clone(),
1513 spec_hash: [12; 32],
1514 role: CanisterRole::new("project_hub"),
1515 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1516 fleet_subnet_root: root,
1517 canister_id: Principal::from_slice(&[16; 29]),
1518 };
1519 let head = ComponentRegistryHead {
1520 component,
1521 revision: 1,
1522 content_hash: [18; 32],
1523 };
1524 let committed = RootComponentCommitResponse {
1525 allocation: RootComponentAllocationResponse {
1526 operation_id: [10; 32],
1527 allocation_sequence: 1,
1528 component,
1529 component_spec,
1530 spec_hash: binding.spec_hash,
1531 role: binding.role.clone(),
1532 provisioning_origin: provisioning_origin.clone(),
1533 release_set,
1534 phase: RootComponentAllocationPhase::Committed,
1535 creation: Some(RootComponentCreationEvidence {
1536 wasm_store: Principal::from_slice(&[14; 29]),
1537 payload_hash: [15; 32],
1538 payload_size_bytes: 4_096,
1539 initial_cycles: Cycles::new(5_000_000_000_000),
1540 controller: root,
1541 canister: Some(binding.canister_id),
1542 }),
1543 installation: Some(RootComponentInstallEvidence {
1544 raw_module_hash: [20; 32],
1545 chunk_hashes: vec![vec![21; 32]],
1546 binding: binding.clone(),
1547 }),
1548 },
1549 registry: ComponentRegistryPartitionResponse {
1550 head: head.clone(),
1551 binding: binding.clone(),
1552 provisioning_origin,
1553 release_set,
1554 status: ComponentLifecycleStatus::Prepared,
1555 reserved_descendants: 0,
1556 committed_descendants: 0,
1557 encoded_bytes: 2_048,
1558 },
1559 directory: ComponentDirectoryHead {
1560 provenance: ComponentDirectoryProvenance {
1561 component: binding,
1562 source_fleet_subnet_root: root,
1563 component_registry_revision: head.revision,
1564 component_registry_content_hash: head.content_hash,
1565 synchronized_at_ns: 19,
1566 },
1567 descendant_count: 0,
1568 },
1569 };
1570 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1571
1572 assert_eq!(
1573 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1574 .expect("decode committed allocation"),
1575 committed
1576 );
1577 }
1578
1579 #[test]
1580 fn component_directory_page_contracts_round_trip_through_candid() {
1581 let root = Principal::from_slice(&[6; 29]);
1582 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1583 let binding = ComponentBinding {
1584 authority: fleet_registry_authority(),
1585 component,
1586 component_spec: "projects".parse().expect("Component Spec ID"),
1587 spec_hash: [12; 32],
1588 role: CanisterRole::new("project_hub"),
1589 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1590 fleet_subnet_root: root,
1591 canister_id: Principal::from_slice(&[16; 29]),
1592 };
1593 let directory = ComponentDirectoryHead {
1594 provenance: ComponentDirectoryProvenance {
1595 component: binding.clone(),
1596 source_fleet_subnet_root: root,
1597 component_registry_revision: 3,
1598 component_registry_content_hash: [18; 32],
1599 synchronized_at_ns: 19,
1600 },
1601 descendant_count: 1,
1602 };
1603 let request = ComponentDirectoryPageRequest {
1604 directory: directory.clone(),
1605 parent_canister_id: Some(binding.canister_id),
1606 role: Some(CanisterRole::new("project_instance")),
1607 status: Some(ComponentLifecycleStatus::Active),
1608 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1609 limit: 50,
1610 };
1611 let response = ComponentDirectoryPageResponse {
1612 directory,
1613 entries: vec![ComponentDirectoryChildEntry {
1614 binding: ComponentChildBinding {
1615 component: binding.clone(),
1616 parent_canister_id: binding.canister_id,
1617 role: CanisterRole::new("project_instance"),
1618 canister_id: Principal::from_slice(&[21; 29]),
1619 },
1620 kind: ComponentChildKind::Instance,
1621 installed_artifact_hash: [22; 32],
1622 status: ComponentLifecycleStatus::Active,
1623 }],
1624 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1625 };
1626 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1627 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1628
1629 assert_eq!(
1630 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1631 .expect("decode Directory page request"),
1632 request
1633 );
1634 assert_eq!(
1635 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1636 .expect("decode Directory page response"),
1637 response
1638 );
1639 }
1640
1641 fn fleet_registry_authority() -> FleetRegistryAuthority {
1642 FleetRegistryAuthority {
1643 binding: FleetCoordinatorBinding {
1644 fleet: crate::ids::FleetBinding {
1645 fleet: FleetKey {
1646 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1647 fleet_id: FleetId::from_generated_bytes([1; 32]),
1648 },
1649 app: AppId::from("toko"),
1650 },
1651 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1652 coordinator: Principal::from_slice(&[3; 29]),
1653 },
1654 epoch: 1,
1655 }
1656 }
1657
1658 #[test]
1659 fn component_creation_request_round_trips_through_candid() {
1660 let request = RootComponentCreationRequest {
1661 operation_id: [10; 32],
1662 };
1663 let bytes = candid::encode_one(request).expect("encode creation request");
1664
1665 assert_eq!(
1666 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1667 .expect("decode creation request"),
1668 request
1669 );
1670 }
1671
1672 #[test]
1673 fn peer_component_provisioning_origin_round_trips_through_candid() {
1674 let authority = fleet_registry_authority();
1675 let requester_spec: ComponentSpecId =
1676 "projects".parse().expect("requester Component Spec ID");
1677 let target_spec: ComponentSpecId = "users".parse().expect("target Component Spec ID");
1678 let origin = ComponentProvisioningOrigin::Component {
1679 requester: Box::new(ComponentBinding {
1680 authority,
1681 component: ComponentInstanceId::from_generated_bytes([20; 32]),
1682 component_spec: requester_spec.clone(),
1683 spec_hash: [21; 32],
1684 role: CanisterRole::new("project_hub"),
1685 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[22; 29])),
1686 fleet_subnet_root: Principal::from_slice(&[23; 29]),
1687 canister_id: Principal::from_slice(&[24; 29]),
1688 }),
1689 grant: Box::new(crate::config::ComponentProvisioningGrant {
1690 requester_component_spec: requester_spec,
1691 target_component_spec: target_spec,
1692 maximum_instances_per_requester_per_root: 3,
1693 }),
1694 };
1695 let bytes = candid::encode_one(&origin).expect("encode peer provisioning origin");
1696
1697 assert_eq!(
1698 candid::decode_one::<ComponentProvisioningOrigin>(&bytes)
1699 .expect("decode peer provisioning origin"),
1700 origin
1701 );
1702 }
1703
1704 #[test]
1705 #[expect(
1706 clippy::too_many_lines,
1707 reason = "one Candid contract test covers every subtree-removal phase receipt"
1708 )]
1709 fn component_subtree_removal_contracts_round_trip_through_candid() {
1710 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1711 let registry = ComponentRegistryHead {
1712 component,
1713 revision: 7,
1714 content_hash: [42; 32],
1715 };
1716 let request = RootComponentSubtreeRemovalRequest {
1717 operation_id: [43; 32],
1718 component,
1719 target_canister_id: Principal::from_slice(&[44; 29]),
1720 expected_registry: registry.clone(),
1721 };
1722 let status_request = RootComponentSubtreeRemovalStatusRequest {
1723 operation_id: request.operation_id,
1724 component,
1725 };
1726 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1727 operation_id: request.operation_id,
1728 component,
1729 expected_traversal_steps: 1,
1730 };
1731 let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1732 operation_id: request.operation_id,
1733 component,
1734 expected_traversal_steps: 2,
1735 expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1736 expected_leaf_parent_canister_id: request.target_canister_id,
1737 };
1738 let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1739 observed_module_hash: [49; 32],
1740 stop: RootComponentSubtreeRemovalStopIntent {
1741 controller: Principal::from_slice(&[48; 29]),
1742 leaf: RootComponentSubtreeRemovalNode {
1743 canister_id: Principal::from_slice(&[46; 29]),
1744 parent_canister_id: request.target_canister_id,
1745 role: CanisterRole::new("project_ledger"),
1746 kind: ComponentChildKind::Singleton,
1747 installed_artifact_hash: [47; 32],
1748 status: ComponentLifecycleStatus::Active,
1749 },
1750 },
1751 };
1752 let response = RootComponentSubtreeRemovalResponse {
1753 operation_id: request.operation_id,
1754 component,
1755 target_canister_id: request.target_canister_id,
1756 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1757 target_role: CanisterRole::new("project_instance"),
1758 target_status: ComponentLifecycleStatus::Active,
1759 reserved_against_registry: registry,
1760 maximum_completed_leaves: 4,
1761 completed_leaves: 1,
1762 traversal_steps: 2,
1763 phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1764 RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1765 membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1766 deleted: RootComponentSubtreeRemovalDeletedReceipt {
1767 deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1768 },
1769 removed_from_registry: ComponentRegistryHead {
1770 component,
1771 revision: 8,
1772 content_hash: [50; 32],
1773 },
1774 previous_descendant_content_hash: [51; 32],
1775 previous_committed_descendants: 4,
1776 registry: ComponentRegistryHead {
1777 component,
1778 revision: 9,
1779 content_hash: [52; 32],
1780 },
1781 descendant_content_hash: [53; 32],
1782 registry_encoded_bytes: 4_096,
1783 reserved_descendants: 1,
1784 committed_descendants: 3,
1785 directory_synchronized_at_ns: 54,
1786 directory_authority_hash: [55; 32],
1787 parent_role_instances: 0,
1788 root_managed_descendants: 4,
1789 root_known_created_component_canisters: 4,
1790 },
1791 covered_fleet_registry_revision: 6,
1792 covered_fleet_registry_content_hash: [56; 32],
1793 covered_component_registry: ComponentRegistryHead {
1794 component,
1795 revision: 9,
1796 content_hash: [52; 32],
1797 },
1798 covered_authority_hash: [55; 32],
1799 owning_component: Some(
1800 RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1801 operation_id: [57; 32],
1802 canister_id: Principal::from_slice(&[58; 29]),
1803 activation: ComponentRuntimeActivationEvidence {
1804 directory_authority_hash: [59; 32],
1805 activated_at_ns: 60,
1806 },
1807 },
1808 ),
1809 parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1810 operation_id: [61; 32],
1811 canister_id: request.target_canister_id,
1812 activation: ComponentRuntimeActivationEvidence {
1813 directory_authority_hash: [62; 32],
1814 activated_at_ns: 63,
1815 },
1816 }),
1817 },
1818 ),
1819 };
1820
1821 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1822 let advance_bytes =
1823 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1824 let stop_bytes =
1825 candid::encode_one(stop_request).expect("encode subtree removal stop request");
1826 let status_bytes =
1827 candid::encode_one(status_request).expect("encode subtree removal status request");
1828 let response_bytes =
1829 candid::encode_one(&response).expect("encode subtree removal response");
1830
1831 assert_eq!(
1832 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1833 .expect("decode subtree removal request"),
1834 request
1835 );
1836 assert_eq!(
1837 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1838 .expect("decode subtree removal advance request"),
1839 advance_request
1840 );
1841 assert_eq!(
1842 candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1843 .expect("decode subtree removal stop request"),
1844 stop_request
1845 );
1846 assert_eq!(
1847 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1848 .expect("decode subtree removal status request"),
1849 status_request
1850 );
1851 assert_eq!(
1852 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1853 .expect("decode subtree removal response"),
1854 response
1855 );
1856
1857 let mut quiescent_owner_response = response;
1858 let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
1859 &mut quiescent_owner_response.phase
1860 else {
1861 panic!("Directory-synchronized response");
1862 };
1863 receipt.owning_component = None;
1864 let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
1865 .expect("encode quiescent-owner subtree response");
1866 assert_eq!(
1867 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
1868 .expect("decode quiescent-owner subtree response"),
1869 quiescent_owner_response
1870 );
1871 }
1872
1873 #[test]
1874 fn component_draining_contracts_round_trip_through_candid() {
1875 let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1876 let previous_registry = ComponentRegistryHead {
1877 component,
1878 revision: 7,
1879 content_hash: [61; 32],
1880 };
1881 let request = RootComponentDrainingRequest {
1882 operation_id: [62; 32],
1883 component,
1884 expected_registry: previous_registry.clone(),
1885 };
1886 let status_request = RootComponentDrainingStatusRequest {
1887 operation_id: request.operation_id,
1888 component,
1889 };
1890 let response = RootComponentDrainingResponse {
1891 operation_id: request.operation_id,
1892 component,
1893 previous_registry,
1894 registry: ComponentRegistryHead {
1895 component,
1896 revision: 8,
1897 content_hash: [63; 32],
1898 },
1899 descendant_count: 20_000,
1900 descendant_content_hash: [64; 32],
1901 directory_authority_hash: [65; 32],
1902 started_at_ns: 66,
1903 };
1904
1905 let request_bytes =
1906 candid::encode_one(&request).expect("encode Component draining request");
1907 let status_bytes =
1908 candid::encode_one(status_request).expect("encode Component draining status request");
1909 let response_bytes =
1910 candid::encode_one(&response).expect("encode Component draining response");
1911
1912 assert_eq!(
1913 candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1914 .expect("decode Component draining request"),
1915 request
1916 );
1917 assert_eq!(
1918 candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1919 .expect("decode Component draining status request"),
1920 status_request
1921 );
1922 assert_eq!(
1923 candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1924 .expect("decode Component draining response"),
1925 response
1926 );
1927 }
1928
1929 #[test]
1930 fn component_quiescence_contracts_round_trip_through_candid() {
1931 let component = ComponentInstanceId::from_generated_bytes([67; 32]);
1932 let registry = ComponentRegistryHead {
1933 component,
1934 revision: 9,
1935 content_hash: [68; 32],
1936 };
1937 let request = RootComponentQuiescenceRequest {
1938 operation_id: [69; 32],
1939 component,
1940 expected_registry: registry.clone(),
1941 };
1942 let status_request = RootComponentQuiescenceStatusRequest {
1943 operation_id: request.operation_id,
1944 component,
1945 };
1946 let stop = RootComponentQuiescenceStopIntent {
1947 registry,
1948 descendant_count: 20_000,
1949 descendant_content_hash: [70; 32],
1950 canister_id: Principal::from_slice(&[71; 29]),
1951 controller: Principal::from_slice(&[72; 29]),
1952 expected_module_hash: [73; 32],
1953 covered_fleet_registry_revision: 10,
1954 covered_fleet_registry_content_hash: [74; 32],
1955 covered_authority_hash: [75; 32],
1956 runtime_operation_id: [76; 32],
1957 activation: ComponentRuntimeActivationEvidence {
1958 directory_authority_hash: [77; 32],
1959 activated_at_ns: 78,
1960 },
1961 prepared_at_ns: 79,
1962 };
1963 let response = RootComponentQuiescenceResponse {
1964 operation_id: request.operation_id,
1965 component,
1966 phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
1967 stop,
1968 observed_module_hash: [73; 32],
1969 quiesced_at_ns: 80,
1970 }),
1971 };
1972
1973 let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
1974 let status_bytes =
1975 candid::encode_one(status_request).expect("encode quiescence status request");
1976 let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
1977 assert_eq!(
1978 candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
1979 .expect("decode quiescence request"),
1980 request
1981 );
1982 assert_eq!(
1983 candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
1984 .expect("decode quiescence status request"),
1985 status_request
1986 );
1987 assert_eq!(
1988 candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
1989 .expect("decode quiescence response"),
1990 response
1991 );
1992 }
1993
1994 #[test]
1995 fn component_draining_advance_contracts_round_trip_through_candid() {
1996 let component = ComponentInstanceId::from_generated_bytes([81; 32]);
1997 let registry = ComponentRegistryHead {
1998 component,
1999 revision: 12,
2000 content_hash: [82; 32],
2001 };
2002 let request = RootComponentDrainingAdvanceRequest {
2003 operation_id: [83; 32],
2004 component,
2005 };
2006 let descendant_removal = RootComponentDrainingAdvanceResponse {
2007 operation_id: request.operation_id,
2008 component,
2009 phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
2010 RootComponentSubtreeRemovalResponse {
2011 operation_id: [84; 32],
2012 component,
2013 target_canister_id: Principal::from_slice(&[85; 29]),
2014 target_parent_canister_id: Principal::from_slice(&[86; 29]),
2015 target_role: CanisterRole::new("project_instance"),
2016 target_status: ComponentLifecycleStatus::Active,
2017 reserved_against_registry: registry.clone(),
2018 maximum_completed_leaves: 20_000,
2019 completed_leaves: 0,
2020 traversal_steps: 0,
2021 phase: RootComponentSubtreeRemovalPhase::Fenced,
2022 },
2023 ),
2024 };
2025 let descendants_empty = RootComponentDrainingAdvanceResponse {
2026 operation_id: request.operation_id,
2027 component,
2028 phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
2029 RootComponentDrainingDescendantsEmpty {
2030 registry,
2031 descendant_content_hash: [87; 32],
2032 },
2033 ),
2034 };
2035
2036 let request_bytes =
2037 candid::encode_one(request).expect("encode Component draining advance request");
2038 let removal_bytes = candid::encode_one(&descendant_removal)
2039 .expect("encode Component draining removal response");
2040 let empty_bytes = candid::encode_one(&descendants_empty)
2041 .expect("encode Component draining empty response");
2042
2043 assert_eq!(
2044 candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
2045 .expect("decode Component draining advance request"),
2046 request
2047 );
2048 assert_eq!(
2049 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
2050 .expect("decode Component draining removal response"),
2051 descendant_removal
2052 );
2053 assert_eq!(
2054 candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
2055 .expect("decode Component draining empty response"),
2056 descendants_empty
2057 );
2058 }
2059
2060 #[test]
2061 #[expect(
2062 clippy::too_many_lines,
2063 reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2064 )]
2065 fn component_final_inventory_contracts_round_trip_through_candid() {
2066 let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2067 let registry = ComponentRegistryHead {
2068 component,
2069 revision: 21,
2070 content_hash: [89; 32],
2071 };
2072 let request = RootComponentFinalInventoryRequest {
2073 operation_id: [90; 32],
2074 component,
2075 expected_registry: registry.clone(),
2076 };
2077 let inventory = RootComponentFinalInventory {
2078 registry,
2079 descendant_content_hash: [91; 32],
2080 registry_encoded_bytes: 4_096,
2081 directory_synchronized_at_ns: 92,
2082 covered_fleet_registry_revision: 93,
2083 covered_fleet_registry_content_hash: [94; 32],
2084 directory_authority_hash: [95; 32],
2085 inventory_hash: [96; 32],
2086 finalized_at_ns: 97,
2087 };
2088 let response = RootComponentFinalInventoryResponse {
2089 operation_id: request.operation_id,
2090 component,
2091 inventory: inventory.clone(),
2092 };
2093 let deletion_request = RootComponentDeletionRequest {
2094 operation_id: request.operation_id,
2095 component,
2096 expected_inventory_hash: inventory.inventory_hash,
2097 };
2098 let deletion_status_request = RootComponentDeletionStatusRequest {
2099 operation_id: request.operation_id,
2100 component,
2101 };
2102 let deletion = RootComponentDeletionIntent {
2103 final_inventory: inventory,
2104 quiescence: RootComponentQuiescentReceipt {
2105 stop: RootComponentQuiescenceStopIntent {
2106 registry: response.inventory.registry.clone(),
2107 descendant_count: 0,
2108 descendant_content_hash: response.inventory.descendant_content_hash,
2109 canister_id: Principal::from_slice(&[98; 29]),
2110 controller: Principal::from_slice(&[99; 29]),
2111 expected_module_hash: [100; 32],
2112 covered_fleet_registry_revision: 93,
2113 covered_fleet_registry_content_hash: [94; 32],
2114 covered_authority_hash: [101; 32],
2115 runtime_operation_id: [102; 32],
2116 activation: ComponentRuntimeActivationEvidence {
2117 directory_authority_hash: [103; 32],
2118 activated_at_ns: 104,
2119 },
2120 prepared_at_ns: 105,
2121 },
2122 observed_module_hash: [100; 32],
2123 quiesced_at_ns: 106,
2124 },
2125 prepared_at_ns: 107,
2126 };
2127 let deleted_receipt = RootComponentDeletedReceipt {
2128 deletion,
2129 deleted_at_ns: 108,
2130 };
2131 let deletion_response = RootComponentDeletionResponse {
2132 operation_id: request.operation_id,
2133 component,
2134 phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2135 };
2136 let membership_removed_response = RootComponentDeletionResponse {
2137 operation_id: request.operation_id,
2138 component,
2139 phase: RootComponentDeletionPhase::MembershipRemoved(
2140 RootComponentMembershipRemovedReceipt {
2141 deleted: deleted_receipt,
2142 allocation_operation_id: [109; 32],
2143 remaining_spec_committed_instances: 2,
2144 root_committed_component_instances: 3,
2145 root_known_created_component_canisters: 4,
2146 root_registry_encoded_bytes: 5_000,
2147 removed_at_ns: 110,
2148 removal_hash: [111; 32],
2149 },
2150 ),
2151 };
2152
2153 let request_bytes =
2154 candid::encode_one(&request).expect("encode Component final inventory request");
2155 let response_bytes =
2156 candid::encode_one(&response).expect("encode Component final inventory response");
2157 let deletion_request_bytes =
2158 candid::encode_one(deletion_request).expect("encode Component deletion request");
2159 let deletion_status_bytes = candid::encode_one(deletion_status_request)
2160 .expect("encode Component deletion status request");
2161 let deletion_response_bytes =
2162 candid::encode_one(&deletion_response).expect("encode Component deletion response");
2163 let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2164 .expect("encode Component membership-removal response");
2165 assert_eq!(
2166 candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2167 .expect("decode Component final inventory request"),
2168 request
2169 );
2170 assert_eq!(
2171 candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2172 .expect("decode Component final inventory response"),
2173 response
2174 );
2175 assert_eq!(
2176 candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2177 .expect("decode Component deletion request"),
2178 deletion_request
2179 );
2180 assert_eq!(
2181 candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2182 .expect("decode Component deletion status request"),
2183 deletion_status_request
2184 );
2185 assert_eq!(
2186 candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2187 .expect("decode Component deletion response"),
2188 deletion_response
2189 );
2190 assert_eq!(
2191 candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2192 .expect("decode Component membership-removal response"),
2193 membership_removed_response
2194 );
2195 }
2196
2197 #[test]
2198 fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2199 let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2200 operation_id: [50; 32],
2201 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2202 expected_traversal_steps: 3,
2203 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2204 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2205 };
2206 let request = RootComponentSubtreeRemovalDeleteRequest {
2207 operation_id: prepare.operation_id,
2208 component: prepare.component,
2209 expected_traversal_steps: prepare.expected_traversal_steps,
2210 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2211 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2212 };
2213 let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2214 operation_id: prepare.operation_id,
2215 component: prepare.component,
2216 expected_traversal_steps: prepare.expected_traversal_steps,
2217 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2218 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2219 };
2220 let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2221 operation_id: prepare.operation_id,
2222 component: prepare.component,
2223 expected_traversal_steps: prepare.expected_traversal_steps,
2224 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2225 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2226 };
2227 let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2228 operation_id: prepare.operation_id,
2229 component: prepare.component,
2230 expected_traversal_steps: prepare.expected_traversal_steps,
2231 expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2232 expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2233 };
2234 let prepare_bytes = candid::encode_one(prepare)
2235 .expect("encode subtree removal deletion preparation request");
2236 let request_bytes =
2237 candid::encode_one(request).expect("encode subtree removal deletion request");
2238 let membership_request_bytes = candid::encode_one(membership_request)
2239 .expect("encode subtree removal membership-removal request");
2240 let directory_request_bytes = candid::encode_one(directory_request)
2241 .expect("encode subtree removal Directory synchronization request");
2242 let finalization_request_bytes = candid::encode_one(finalization_request)
2243 .expect("encode subtree removal leaf-finalization request");
2244
2245 assert_eq!(
2246 candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2247 &prepare_bytes
2248 )
2249 .expect("decode subtree removal deletion preparation request"),
2250 prepare
2251 );
2252 assert_eq!(
2253 candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2254 .expect("decode subtree removal deletion request"),
2255 request
2256 );
2257 assert_eq!(
2258 candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2259 &membership_request_bytes
2260 )
2261 .expect("decode subtree removal membership-removal request"),
2262 membership_request
2263 );
2264 assert_eq!(
2265 candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2266 &directory_request_bytes
2267 )
2268 .expect("decode subtree removal Directory synchronization request"),
2269 directory_request
2270 );
2271 assert_eq!(
2272 candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2273 &finalization_request_bytes
2274 )
2275 .expect("decode subtree removal leaf-finalization request"),
2276 finalization_request
2277 );
2278 }
2279
2280 #[test]
2281 fn component_subtree_removal_stop_request_round_trips_through_candid() {
2282 let request = RootComponentSubtreeRemovalStopRequest {
2283 operation_id: [50; 32],
2284 component: ComponentInstanceId::from_generated_bytes([51; 32]),
2285 expected_traversal_steps: 3,
2286 expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2287 expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2288 };
2289 let bytes =
2290 candid::encode_one(request).expect("encode subtree removal stop execution request");
2291
2292 assert_eq!(
2293 candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2294 .expect("decode subtree removal stop execution request"),
2295 request
2296 );
2297 }
2298
2299 #[test]
2300 #[expect(
2301 clippy::too_many_lines,
2302 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2303 )]
2304 fn component_child_lifecycle_contracts_round_trip_through_candid() {
2305 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2306 let registry = ComponentRegistryHead {
2307 component,
2308 revision: 2,
2309 content_hash: [12; 32],
2310 };
2311 let request = RootComponentChildAllocationRequest {
2312 operation_id: [13; 32],
2313 component,
2314 expected_registry: registry.clone(),
2315 child_role: CanisterRole::new("project_instance"),
2316 application_init_args: Some(vec![9, 8, 7]),
2317 };
2318 let status_request = RootComponentChildAllocationStatusRequest {
2319 operation_id: request.operation_id,
2320 component,
2321 };
2322 let creation_request = RootComponentChildCreationRequest {
2323 operation_id: request.operation_id,
2324 component,
2325 };
2326 let install_request = RootComponentChildInstallRequest {
2327 operation_id: request.operation_id,
2328 component,
2329 };
2330 let commit_request = RootComponentChildCommitRequest {
2331 operation_id: request.operation_id,
2332 component,
2333 };
2334 let directory_request = RootComponentChildDirectoryPreparationRequest {
2335 operation_id: request.operation_id,
2336 component,
2337 };
2338 let activation_request = RootComponentChildRuntimeActivationRequest {
2339 operation_id: request.operation_id,
2340 component,
2341 };
2342 let membership_request = RootComponentChildMembershipActivationRequest {
2343 operation_id: request.operation_id,
2344 component,
2345 };
2346 let root = Principal::from_slice(&[17; 29]);
2347 let parent = Principal::from_slice(&[14; 29]);
2348 let child = Principal::from_slice(&[18; 29]);
2349 let child_binding = ComponentChildBinding {
2350 component: ComponentBinding {
2351 authority: fleet_registry_authority(),
2352 component,
2353 component_spec: "projects".parse().expect("Component Spec"),
2354 spec_hash: [19; 32],
2355 role: CanisterRole::new("project_hub"),
2356 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2357 fleet_subnet_root: root,
2358 canister_id: parent,
2359 },
2360 parent_canister_id: parent,
2361 role: request.child_role.clone(),
2362 canister_id: child,
2363 };
2364 let response = RootComponentChildAllocationResponse {
2365 operation_id: request.operation_id,
2366 component,
2367 parent_canister_id: parent,
2368 parent_role: CanisterRole::new("project_hub"),
2369 child_role: request.child_role.clone(),
2370 child_kind: ComponentChildKind::Instance,
2371 maximum_instances_per_parent: 10_000,
2372 maximum_descendants: 20_000,
2373 maximum_registry_bytes: 16_777_216,
2374 reserved_against_registry: registry,
2375 release_set: FleetSubnetRootReleaseSet {
2376 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2377 [15; 32],
2378 )),
2379 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2380 },
2381 phase: RootComponentAllocationPhase::Verified,
2382 creation: Some(RootComponentCreationEvidence {
2383 wasm_store: Principal::from_slice(&[21; 29]),
2384 payload_hash: [22; 32],
2385 payload_size_bytes: 4_096,
2386 initial_cycles: Cycles::new(5_000_000_000_000),
2387 controller: root,
2388 canister: Some(child),
2389 }),
2390 installation: Some(RootComponentChildInstallEvidence {
2391 raw_module_hash: [23; 32],
2392 chunk_hashes: vec![vec![24; 32]],
2393 binding: child_binding.clone(),
2394 }),
2395 };
2396 let commit_response = RootComponentChildCommitResponse {
2397 allocation: response.clone(),
2398 registry: ComponentRegistryPartitionResponse {
2399 head: ComponentRegistryHead {
2400 component,
2401 revision: 3,
2402 content_hash: [25; 32],
2403 },
2404 binding: child_binding.component.clone(),
2405 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2406 caller: Principal::from_slice(&[26; 29]),
2407 },
2408 release_set: response.release_set,
2409 status: ComponentLifecycleStatus::Active,
2410 reserved_descendants: 0,
2411 committed_descendants: 1,
2412 encoded_bytes: 8_192,
2413 },
2414 directory: ComponentDirectoryHead {
2415 provenance: ComponentDirectoryProvenance {
2416 component: child_binding.component.clone(),
2417 source_fleet_subnet_root: root,
2418 component_registry_revision: 3,
2419 component_registry_content_hash: [25; 32],
2420 synchronized_at_ns: 27,
2421 },
2422 descendant_count: 1,
2423 },
2424 };
2425 let runtime_authority = ComponentRuntimeDirectoryAuthority {
2426 fleet: FleetDirectorySnapshot {
2427 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2428 registry: FleetRegistryVersion {
2429 authority: fleet_registry_authority(),
2430 revision: 4,
2431 content_hash: [28; 32],
2432 },
2433 source_fleet_subnet_root: root,
2434 },
2435 fleet_subnet_roots: vec![
2436 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2437 placement_subnet: commit_response.registry.binding.placement_subnet,
2438 fleet_subnet_root: root,
2439 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2440 },
2441 ],
2442 },
2443 component: commit_response.directory.clone(),
2444 };
2445 let activation = ComponentRuntimeActivationEvidence {
2446 directory_authority_hash: [29; 32],
2447 activated_at_ns: 30,
2448 };
2449 let directory_response = RootComponentChildDirectoryPreparationResponse {
2450 committed: commit_response.clone(),
2451 child: ComponentRuntimeStatusResponse {
2452 operation_id: request.operation_id,
2453 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2454 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2455 binding: child_binding.component.clone(),
2456 }),
2457 phase: ComponentRuntimePhase::DirectoryPrepared,
2458 authority: Some(runtime_authority.clone()),
2459 authority_hash: Some([31; 32]),
2460 direct_children_hash: Some([37; 32]),
2461 activation: None,
2462 },
2463 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2464 operation_id: [32; 32],
2465 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2466 covered_authority: runtime_authority.clone(),
2467 covered_authority_hash: [31; 32],
2468 activation,
2469 },
2470 parent: None,
2471 };
2472 let activation_response = RootComponentChildRuntimeActivationResponse {
2473 committed: commit_response.clone(),
2474 child: ComponentRuntimeStatusResponse {
2475 operation_id: request.operation_id,
2476 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2477 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2478 binding: child_binding.component.clone(),
2479 }),
2480 phase: ComponentRuntimePhase::Active,
2481 authority: Some(runtime_authority.clone()),
2482 authority_hash: Some([31; 32]),
2483 direct_children_hash: Some([37; 32]),
2484 activation: Some(ComponentRuntimeActivationEvidence {
2485 directory_authority_hash: [31; 32],
2486 activated_at_ns: 33,
2487 }),
2488 },
2489 };
2490 let active_directory = ComponentDirectoryHead {
2491 provenance: ComponentDirectoryProvenance {
2492 component: child_binding.component.clone(),
2493 source_fleet_subnet_root: root,
2494 component_registry_revision: 4,
2495 component_registry_content_hash: [34; 32],
2496 synchronized_at_ns: 35,
2497 },
2498 descendant_count: 1,
2499 };
2500 let active_authority = ComponentRuntimeDirectoryAuthority {
2501 fleet: runtime_authority.fleet,
2502 component: active_directory.clone(),
2503 };
2504 let membership_response = RootComponentChildMembershipActivationResponse {
2505 committed: commit_response.clone(),
2506 registry: ComponentRegistryPartitionResponse {
2507 head: ComponentRegistryHead {
2508 component,
2509 revision: 4,
2510 content_hash: [34; 32],
2511 },
2512 binding: child_binding.component.clone(),
2513 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2514 release_set: commit_response.registry.release_set,
2515 status: ComponentLifecycleStatus::Active,
2516 reserved_descendants: 0,
2517 committed_descendants: 1,
2518 encoded_bytes: 8_256,
2519 },
2520 directory: active_directory,
2521 child: ComponentRuntimeStatusResponse {
2522 operation_id: request.operation_id,
2523 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2524 deployment: Box::new(ProtectedComponentDeployment::UngroupedOrdinary {
2525 binding: child_binding.component,
2526 }),
2527 phase: ComponentRuntimePhase::Active,
2528 authority: Some(active_authority),
2529 authority_hash: Some([36; 32]),
2530 direct_children_hash: Some([38; 32]),
2531 activation: Some(ComponentRuntimeActivationEvidence {
2532 directory_authority_hash: [31; 32],
2533 activated_at_ns: 33,
2534 }),
2535 },
2536 };
2537
2538 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2539 let status_bytes =
2540 candid::encode_one(status_request).expect("encode child reservation status");
2541 let creation_bytes =
2542 candid::encode_one(creation_request).expect("encode child creation request");
2543 let install_bytes =
2544 candid::encode_one(install_request).expect("encode child install request");
2545 let response_bytes = candid::encode_one(&response).expect("encode child response");
2546 let commit_request_bytes =
2547 candid::encode_one(commit_request).expect("encode child commit request");
2548 let directory_request_bytes =
2549 candid::encode_one(directory_request).expect("encode child Directory request");
2550 let activation_request_bytes =
2551 candid::encode_one(activation_request).expect("encode child activation request");
2552 let membership_request_bytes =
2553 candid::encode_one(membership_request).expect("encode child membership request");
2554 let commit_response_bytes =
2555 candid::encode_one(&commit_response).expect("encode child commit response");
2556 let directory_response_bytes =
2557 candid::encode_one(&directory_response).expect("encode child Directory response");
2558 let activation_response_bytes =
2559 candid::encode_one(&activation_response).expect("encode child activation response");
2560 let membership_response_bytes =
2561 candid::encode_one(&membership_response).expect("encode child membership response");
2562
2563 assert_eq!(
2564 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2565 .expect("decode child reservation"),
2566 request
2567 );
2568 assert_eq!(
2569 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2570 .expect("decode child reservation status"),
2571 status_request
2572 );
2573 assert_eq!(
2574 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2575 .expect("decode child creation request"),
2576 creation_request
2577 );
2578 assert_eq!(
2579 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2580 .expect("decode child install request"),
2581 install_request
2582 );
2583 assert_eq!(
2584 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2585 .expect("decode child response"),
2586 response
2587 );
2588 assert_eq!(
2589 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2590 .expect("decode child commit request"),
2591 commit_request
2592 );
2593 assert_eq!(
2594 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2595 &directory_request_bytes
2596 )
2597 .expect("decode child Directory request"),
2598 directory_request
2599 );
2600 assert_eq!(
2601 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2602 .expect("decode child commit response"),
2603 commit_response
2604 );
2605 assert_eq!(
2606 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2607 &directory_response_bytes
2608 )
2609 .expect("decode child Directory response"),
2610 directory_response
2611 );
2612 assert_eq!(
2613 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2614 &activation_request_bytes
2615 )
2616 .expect("decode child activation request"),
2617 activation_request
2618 );
2619 assert_eq!(
2620 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2621 &activation_response_bytes
2622 )
2623 .expect("decode child activation response"),
2624 activation_response
2625 );
2626 assert_eq!(
2627 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2628 &membership_request_bytes
2629 )
2630 .expect("decode child membership request"),
2631 membership_request
2632 );
2633 assert_eq!(
2634 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2635 &membership_response_bytes
2636 )
2637 .expect("decode child membership response"),
2638 membership_response
2639 );
2640 }
2641
2642 #[test]
2643 fn component_install_request_round_trips_through_candid() {
2644 let request = RootComponentInstallRequest {
2645 operation_id: [10; 32],
2646 };
2647 let bytes = candid::encode_one(request).expect("encode install request");
2648
2649 assert_eq!(
2650 candid::decode_one::<RootComponentInstallRequest>(&bytes)
2651 .expect("decode install request"),
2652 request
2653 );
2654 }
2655
2656 #[test]
2657 fn component_commit_request_round_trips_through_candid() {
2658 let request = RootComponentCommitRequest {
2659 operation_id: [10; 32],
2660 };
2661 let bytes = candid::encode_one(request).expect("encode commit request");
2662
2663 assert_eq!(
2664 candid::decode_one::<RootComponentCommitRequest>(&bytes)
2665 .expect("decode commit request"),
2666 request
2667 );
2668 }
2669
2670 #[test]
2671 fn component_runtime_activation_requests_round_trip_through_candid() {
2672 let root_request = RootComponentRuntimeActivationRequest {
2673 operation_id: [22; 32],
2674 };
2675 let target_request = ComponentRuntimeActivationRequest {
2676 operation_id: root_request.operation_id,
2677 directory_authority_hash: [23; 32],
2678 };
2679 let membership_request = RootComponentMembershipActivationRequest {
2680 operation_id: root_request.operation_id,
2681 };
2682 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2683 let target_bytes =
2684 candid::encode_one(target_request).expect("encode target activation request");
2685 let membership_bytes =
2686 candid::encode_one(membership_request).expect("encode membership activation request");
2687
2688 assert_eq!(
2689 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2690 .expect("decode root activation request"),
2691 root_request
2692 );
2693 assert_eq!(
2694 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2695 .expect("decode target activation request"),
2696 target_request
2697 );
2698 assert_eq!(
2699 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2700 .expect("decode membership activation request"),
2701 membership_request
2702 );
2703 }
2704}