1use crate::{
8 cdk::types::Cycles,
9 config::schema::ComponentChildKind,
10 dto::{
11 fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
12 root_store::RootStoreBootstrapRequest,
13 },
14 ids::{
15 CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
16 ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
17 ManagedCanisterBinding,
18 },
19};
20use candid::{CandidType, Principal};
21use serde::{Deserialize, Serialize};
22
23#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct RootComponentRegistryPreparationRequest {
31 pub store_bootstrap: RootStoreBootstrapRequest,
32 pub expected_fleet_registry: FleetRegistryVersion,
33}
34
35#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct RootComponentInitialInventoryStatus {
43 pub fleet_activation_operation_id: [u8; 32],
44 pub component_count: u32,
45 pub inventory_hash: [u8; 32],
46 pub sealed_at_ns: u64,
47 pub directories_converged: bool,
48 pub root_runtime_activated: bool,
49}
50
51#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct RootComponentRegistryStatusResponse {
59 pub fleet_subnet_root: Principal,
60 pub prepared_against_registry: FleetRegistryVersion,
61 pub release_set: FleetSubnetRootReleaseSet,
62 pub component_topology_digest: ComponentTopologyDigest,
63 pub next_allocation_sequence: u64,
64 pub reserved_component_instances: u32,
65 pub committed_component_instances: u32,
66 pub managed_descendants: u32,
67 pub known_created_component_canisters: u32,
68 pub encoded_bytes: u64,
69 pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
70}
71
72#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct RootComponentAllocationRequest {
80 pub operation_id: [u8; 32],
81 pub component_spec: ComponentSpecId,
82}
83
84#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
91pub struct RootComponentAllocationStatusRequest {
92 pub operation_id: [u8; 32],
93}
94
95#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
102pub struct RootComponentChildAllocationRequest {
103 pub operation_id: [u8; 32],
104 pub component: ComponentInstanceId,
105 pub expected_registry: ComponentRegistryHead,
106 pub child_role: CanisterRole,
107}
108
109#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct RootComponentChildAllocationStatusRequest {
117 pub operation_id: [u8; 32],
118 pub component: ComponentInstanceId,
119}
120
121#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentSubtreeRemovalRequest {
129 pub operation_id: [u8; 32],
130 pub component: ComponentInstanceId,
131 pub target_canister_id: Principal,
132 pub expected_registry: ComponentRegistryHead,
133}
134
135#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct RootComponentSubtreeRemovalStatusRequest {
143 pub operation_id: [u8; 32],
144 pub component: ComponentInstanceId,
145}
146
147#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
154pub struct RootComponentChildCreationRequest {
155 pub operation_id: [u8; 32],
156 pub component: ComponentInstanceId,
157}
158
159#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
166pub struct RootComponentChildInstallRequest {
167 pub operation_id: [u8; 32],
168 pub component: ComponentInstanceId,
169}
170
171#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
178pub struct RootComponentChildCommitRequest {
179 pub operation_id: [u8; 32],
180 pub component: ComponentInstanceId,
181}
182
183#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
190pub struct RootComponentChildDirectoryPreparationRequest {
191 pub operation_id: [u8; 32],
192 pub component: ComponentInstanceId,
193}
194
195#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
202pub struct RootComponentChildRuntimeActivationRequest {
203 pub operation_id: [u8; 32],
204 pub component: ComponentInstanceId,
205}
206
207#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
214pub struct RootComponentChildMembershipActivationRequest {
215 pub operation_id: [u8; 32],
216 pub component: ComponentInstanceId,
217}
218
219#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
226pub struct RootComponentCreationRequest {
227 pub operation_id: [u8; 32],
228}
229
230#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
237pub struct RootComponentInstallRequest {
238 pub operation_id: [u8; 32],
239}
240
241#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
248pub struct RootComponentCommitRequest {
249 pub operation_id: [u8; 32],
250}
251
252#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
259pub struct RootComponentDirectoryPreparationRequest {
260 pub operation_id: [u8; 32],
261}
262
263#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
270pub struct RootComponentRuntimeActivationRequest {
271 pub operation_id: [u8; 32],
272}
273
274#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
281pub struct RootComponentMembershipActivationRequest {
282 pub operation_id: [u8; 32],
283}
284
285#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
292pub enum ComponentProvisioningOrigin {
293 FleetAdministrator { caller: Principal },
294}
295
296#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
303pub enum RootComponentAllocationPhase {
304 Reserved,
305 CreationIntent,
306 Created,
307 InstallIntent,
308 Installed,
309 Verified,
310 Committed,
311}
312
313#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
320pub enum RootComponentSubtreeRemovalPhase {
321 Fenced,
322}
323
324#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
331pub enum ComponentLifecycleStatus {
332 Prepared,
333 Active,
334 Draining,
335 Removed,
336}
337
338#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
345pub struct ComponentRegistryHead {
346 pub component: ComponentInstanceId,
347 pub revision: u64,
348 pub content_hash: [u8; 32],
349}
350
351#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
358pub struct ComponentRegistryPartitionRequest {
359 pub component: ComponentInstanceId,
360}
361
362#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
369pub struct ComponentRegistryPartitionResponse {
370 pub head: ComponentRegistryHead,
371 pub binding: ComponentBinding,
372 pub provisioning_origin: ComponentProvisioningOrigin,
373 pub release_set: FleetSubnetRootReleaseSet,
374 pub status: ComponentLifecycleStatus,
375 pub reserved_descendants: u32,
376 pub committed_descendants: u32,
377 pub encoded_bytes: u64,
378}
379
380#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
387pub struct ComponentDirectoryProvenance {
388 pub component: ComponentBinding,
389 pub source_fleet_subnet_root: Principal,
390 pub component_registry_revision: u64,
391 pub component_registry_content_hash: [u8; 32],
392 pub synchronized_at_ns: u64,
393}
394
395#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
402pub struct ComponentDirectoryHead {
403 pub provenance: ComponentDirectoryProvenance,
404 pub descendant_count: u32,
405}
406
407#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
414pub struct ComponentDirectoryHeadRequest {
415 pub component: ComponentInstanceId,
416}
417
418#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
425pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
426
427#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
434pub struct ComponentDirectoryPageRequest {
435 pub directory: ComponentDirectoryHead,
436 pub parent_canister_id: Option<Principal>,
437 pub role: Option<CanisterRole>,
438 pub status: Option<ComponentLifecycleStatus>,
439 pub cursor: Option<ComponentDirectoryPageCursor>,
440 pub limit: u16,
441}
442
443#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
450pub struct ComponentDirectoryChildEntry {
451 pub binding: ComponentChildBinding,
452 pub kind: ComponentChildKind,
453 pub installed_artifact_hash: [u8; 32],
454 pub status: ComponentLifecycleStatus,
455}
456
457#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
464pub struct ComponentDirectoryPageResponse {
465 pub directory: ComponentDirectoryHead,
466 pub entries: Vec<ComponentDirectoryChildEntry>,
467 pub next_cursor: Option<ComponentDirectoryPageCursor>,
468}
469
470#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
477pub struct ComponentRuntimeDirectoryAuthority {
478 pub fleet: FleetDirectorySnapshot,
479 pub component: ComponentDirectoryHead,
480}
481
482#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
489pub struct ComponentRuntimeDirectoryPreparationRequest {
490 pub operation_id: [u8; 32],
491 pub authority: ComponentRuntimeDirectoryAuthority,
492}
493
494#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
501pub struct ComponentRuntimeDirectorySynchronizationRequest {
502 pub operation_id: [u8; 32],
503 pub authority: ComponentRuntimeDirectoryAuthority,
504}
505
506#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
513pub enum ComponentRuntimePhase {
514 AwaitingDirectory,
515 DirectoryPrepared,
516 Active,
517}
518
519#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
526pub struct ComponentRuntimeActivationEvidence {
527 pub directory_authority_hash: [u8; 32],
528 pub activated_at_ns: u64,
529}
530
531#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
538pub struct ComponentRuntimeActivationRequest {
539 pub operation_id: [u8; 32],
540 pub directory_authority_hash: [u8; 32],
541}
542
543#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
550pub struct ComponentRuntimeStatusResponse {
551 pub operation_id: [u8; 32],
552 pub binding: ManagedCanisterBinding,
553 pub phase: ComponentRuntimePhase,
554 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
555 pub authority_hash: Option<[u8; 32]>,
556 pub activation: Option<ComponentRuntimeActivationEvidence>,
557}
558
559#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
566pub struct ComponentRuntimeDirectoryConvergenceEvidence {
567 pub operation_id: [u8; 32],
568 pub binding: ManagedCanisterBinding,
569 pub covered_authority: ComponentRuntimeDirectoryAuthority,
570 pub covered_authority_hash: [u8; 32],
571 pub activation: ComponentRuntimeActivationEvidence,
572}
573
574#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
581pub struct RootComponentCreationEvidence {
582 pub wasm_store: Principal,
583 pub payload_hash: [u8; 32],
584 pub payload_size_bytes: u64,
585 pub initial_cycles: Cycles,
586 pub controller: Principal,
587 pub canister: Option<Principal>,
588}
589
590#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
597pub struct RootComponentInstallEvidence {
598 pub raw_module_hash: [u8; 32],
599 pub chunk_hashes: Vec<Vec<u8>>,
600 pub binding: ComponentBinding,
601}
602
603#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
610pub struct RootComponentChildInstallEvidence {
611 pub raw_module_hash: [u8; 32],
612 pub chunk_hashes: Vec<Vec<u8>>,
613 pub binding: ComponentChildBinding,
614}
615
616#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
623pub struct RootComponentAllocationResponse {
624 pub operation_id: [u8; 32],
625 pub allocation_sequence: u64,
626 pub component: ComponentInstanceId,
627 pub component_spec: ComponentSpecId,
628 pub spec_hash: [u8; 32],
629 pub role: CanisterRole,
630 pub provisioning_origin: ComponentProvisioningOrigin,
631 pub release_set: FleetSubnetRootReleaseSet,
632 pub phase: RootComponentAllocationPhase,
633 pub creation: Option<RootComponentCreationEvidence>,
634 pub installation: Option<RootComponentInstallEvidence>,
635}
636
637#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
644pub struct RootComponentChildAllocationResponse {
645 pub operation_id: [u8; 32],
646 pub component: ComponentInstanceId,
647 pub parent_canister_id: Principal,
648 pub parent_role: CanisterRole,
649 pub child_role: CanisterRole,
650 pub child_kind: ComponentChildKind,
651 pub maximum_instances_per_parent: u32,
652 pub maximum_descendants: u32,
653 pub maximum_registry_bytes: u64,
654 pub reserved_against_registry: ComponentRegistryHead,
655 pub release_set: FleetSubnetRootReleaseSet,
656 pub phase: RootComponentAllocationPhase,
657 pub creation: Option<RootComponentCreationEvidence>,
658 pub installation: Option<RootComponentChildInstallEvidence>,
659}
660
661#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
668pub struct RootComponentSubtreeRemovalResponse {
669 pub operation_id: [u8; 32],
670 pub component: ComponentInstanceId,
671 pub target_canister_id: Principal,
672 pub target_parent_canister_id: Principal,
673 pub target_role: CanisterRole,
674 pub target_status: ComponentLifecycleStatus,
675 pub reserved_against_registry: ComponentRegistryHead,
676 pub phase: RootComponentSubtreeRemovalPhase,
677}
678
679#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
686pub struct RootComponentChildCommitResponse {
687 pub allocation: RootComponentChildAllocationResponse,
688 pub registry: ComponentRegistryPartitionResponse,
689 pub directory: ComponentDirectoryHead,
690}
691
692#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
699pub struct RootComponentChildDirectoryPreparationResponse {
700 pub committed: RootComponentChildCommitResponse,
701 pub child: ComponentRuntimeStatusResponse,
702 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
703 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
704}
705
706#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
713pub struct RootComponentChildRuntimeActivationResponse {
714 pub committed: RootComponentChildCommitResponse,
715 pub child: ComponentRuntimeStatusResponse,
716}
717
718#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
725pub struct RootComponentChildMembershipActivationResponse {
726 pub committed: RootComponentChildCommitResponse,
727 pub registry: ComponentRegistryPartitionResponse,
728 pub directory: ComponentDirectoryHead,
729 pub child: ComponentRuntimeStatusResponse,
730}
731
732#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
739pub struct RootComponentCommitResponse {
740 pub allocation: RootComponentAllocationResponse,
741 pub registry: ComponentRegistryPartitionResponse,
742 pub directory: ComponentDirectoryHead,
743}
744
745#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
752pub struct RootComponentDirectoryPreparationResponse {
753 pub committed: RootComponentCommitResponse,
754 pub target: ComponentRuntimeStatusResponse,
755}
756
757#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
764pub struct RootComponentRuntimeActivationResponse {
765 pub committed: RootComponentCommitResponse,
766 pub target: ComponentRuntimeStatusResponse,
767}
768
769#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
776pub struct RootComponentMembershipActivationResponse {
777 pub allocation: RootComponentAllocationResponse,
778 pub registry: ComponentRegistryPartitionResponse,
779 pub directory: ComponentDirectoryHead,
780 pub target: ComponentRuntimeStatusResponse,
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use crate::{
787 dto::root_store::RootStoreBootstrapRequest,
788 ids::{
789 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
790 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
791 },
792 };
793
794 #[test]
795 fn component_registry_contracts_round_trip_through_candid() {
796 let request = RootComponentRegistryPreparationRequest {
797 store_bootstrap: RootStoreBootstrapRequest {
798 manifest_payload_size_bytes: 128,
799 },
800 expected_fleet_registry: FleetRegistryVersion {
801 authority: fleet_registry_authority(),
802 revision: 4,
803 content_hash: [5; 32],
804 },
805 };
806 let response = RootComponentRegistryStatusResponse {
807 fleet_subnet_root: Principal::from_slice(&[6; 29]),
808 prepared_against_registry: request.expected_fleet_registry.clone(),
809 release_set: FleetSubnetRootReleaseSet {
810 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
811 [7; 32],
812 )),
813 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
814 },
815 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
816 next_allocation_sequence: 1,
817 reserved_component_instances: 0,
818 committed_component_instances: 0,
819 managed_descendants: 0,
820 known_created_component_canisters: 0,
821 encoded_bytes: 0,
822 initial_inventory: Some(RootComponentInitialInventoryStatus {
823 fleet_activation_operation_id: [10; 32],
824 component_count: 0,
825 inventory_hash: [11; 32],
826 sealed_at_ns: 12,
827 directories_converged: true,
828 root_runtime_activated: true,
829 }),
830 };
831 let allocation = RootComponentAllocationResponse {
832 operation_id: [10; 32],
833 allocation_sequence: 1,
834 component: ComponentInstanceId::from_generated_bytes([11; 32]),
835 component_spec: "projects".parse().expect("Component Spec ID"),
836 spec_hash: [12; 32],
837 role: CanisterRole::new("project_hub"),
838 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
839 caller: Principal::from_slice(&[13; 29]),
840 },
841 release_set: response.release_set,
842 phase: RootComponentAllocationPhase::Reserved,
843 creation: None,
844 installation: None,
845 };
846 let created = RootComponentAllocationResponse {
847 phase: RootComponentAllocationPhase::Created,
848 creation: Some(RootComponentCreationEvidence {
849 wasm_store: Principal::from_slice(&[14; 29]),
850 payload_hash: [15; 32],
851 payload_size_bytes: 4_096,
852 initial_cycles: Cycles::new(5_000_000_000_000),
853 controller: Principal::from_slice(&[6; 29]),
854 canister: Some(Principal::from_slice(&[16; 29])),
855 }),
856 installation: None,
857 ..allocation.clone()
858 };
859 let request_bytes = candid::encode_one(&request).expect("encode request");
860 let response_bytes = candid::encode_one(&response).expect("encode response");
861 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
862 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
863
864 assert_eq!(
865 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
866 .expect("decode request"),
867 request
868 );
869 assert_eq!(
870 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
871 .expect("decode response"),
872 response
873 );
874 assert_eq!(
875 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
876 .expect("decode allocation"),
877 allocation
878 );
879 assert_eq!(
880 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
881 .expect("decode created allocation"),
882 created
883 );
884 }
885
886 #[test]
887 fn component_commit_response_round_trips_through_candid() {
888 let root = Principal::from_slice(&[6; 29]);
889 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
890 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
891 let release_set = FleetSubnetRootReleaseSet {
892 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
893 [7; 32],
894 )),
895 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
896 };
897 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
898 caller: Principal::from_slice(&[13; 29]),
899 };
900 let binding = ComponentBinding {
901 authority: fleet_registry_authority(),
902 component,
903 component_spec: component_spec.clone(),
904 spec_hash: [12; 32],
905 role: CanisterRole::new("project_hub"),
906 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
907 fleet_subnet_root: root,
908 canister_id: Principal::from_slice(&[16; 29]),
909 };
910 let head = ComponentRegistryHead {
911 component,
912 revision: 1,
913 content_hash: [18; 32],
914 };
915 let committed = RootComponentCommitResponse {
916 allocation: RootComponentAllocationResponse {
917 operation_id: [10; 32],
918 allocation_sequence: 1,
919 component,
920 component_spec,
921 spec_hash: binding.spec_hash,
922 role: binding.role.clone(),
923 provisioning_origin: provisioning_origin.clone(),
924 release_set,
925 phase: RootComponentAllocationPhase::Committed,
926 creation: Some(RootComponentCreationEvidence {
927 wasm_store: Principal::from_slice(&[14; 29]),
928 payload_hash: [15; 32],
929 payload_size_bytes: 4_096,
930 initial_cycles: Cycles::new(5_000_000_000_000),
931 controller: root,
932 canister: Some(binding.canister_id),
933 }),
934 installation: Some(RootComponentInstallEvidence {
935 raw_module_hash: [20; 32],
936 chunk_hashes: vec![vec![21; 32]],
937 binding: binding.clone(),
938 }),
939 },
940 registry: ComponentRegistryPartitionResponse {
941 head: head.clone(),
942 binding: binding.clone(),
943 provisioning_origin,
944 release_set,
945 status: ComponentLifecycleStatus::Prepared,
946 reserved_descendants: 0,
947 committed_descendants: 0,
948 encoded_bytes: 2_048,
949 },
950 directory: ComponentDirectoryHead {
951 provenance: ComponentDirectoryProvenance {
952 component: binding,
953 source_fleet_subnet_root: root,
954 component_registry_revision: head.revision,
955 component_registry_content_hash: head.content_hash,
956 synchronized_at_ns: 19,
957 },
958 descendant_count: 0,
959 },
960 };
961 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
962
963 assert_eq!(
964 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
965 .expect("decode committed allocation"),
966 committed
967 );
968 }
969
970 #[test]
971 fn component_directory_page_contracts_round_trip_through_candid() {
972 let root = Principal::from_slice(&[6; 29]);
973 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
974 let binding = ComponentBinding {
975 authority: fleet_registry_authority(),
976 component,
977 component_spec: "projects".parse().expect("Component Spec ID"),
978 spec_hash: [12; 32],
979 role: CanisterRole::new("project_hub"),
980 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
981 fleet_subnet_root: root,
982 canister_id: Principal::from_slice(&[16; 29]),
983 };
984 let directory = ComponentDirectoryHead {
985 provenance: ComponentDirectoryProvenance {
986 component: binding.clone(),
987 source_fleet_subnet_root: root,
988 component_registry_revision: 3,
989 component_registry_content_hash: [18; 32],
990 synchronized_at_ns: 19,
991 },
992 descendant_count: 1,
993 };
994 let request = ComponentDirectoryPageRequest {
995 directory: directory.clone(),
996 parent_canister_id: Some(binding.canister_id),
997 role: Some(CanisterRole::new("project_instance")),
998 status: Some(ComponentLifecycleStatus::Active),
999 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1000 limit: 50,
1001 };
1002 let response = ComponentDirectoryPageResponse {
1003 directory,
1004 entries: vec![ComponentDirectoryChildEntry {
1005 binding: ComponentChildBinding {
1006 component: binding.clone(),
1007 parent_canister_id: binding.canister_id,
1008 role: CanisterRole::new("project_instance"),
1009 canister_id: Principal::from_slice(&[21; 29]),
1010 },
1011 kind: ComponentChildKind::Instance,
1012 installed_artifact_hash: [22; 32],
1013 status: ComponentLifecycleStatus::Active,
1014 }],
1015 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1016 };
1017 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1018 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1019
1020 assert_eq!(
1021 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1022 .expect("decode Directory page request"),
1023 request
1024 );
1025 assert_eq!(
1026 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1027 .expect("decode Directory page response"),
1028 response
1029 );
1030 }
1031
1032 fn fleet_registry_authority() -> FleetRegistryAuthority {
1033 FleetRegistryAuthority {
1034 binding: FleetCoordinatorBinding {
1035 fleet: crate::ids::FleetBinding {
1036 fleet: FleetKey {
1037 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1038 fleet_id: FleetId::from_generated_bytes([1; 32]),
1039 },
1040 app: AppId::from("toko"),
1041 },
1042 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1043 coordinator: Principal::from_slice(&[3; 29]),
1044 },
1045 epoch: 1,
1046 }
1047 }
1048
1049 #[test]
1050 fn component_creation_request_round_trips_through_candid() {
1051 let request = RootComponentCreationRequest {
1052 operation_id: [10; 32],
1053 };
1054 let bytes = candid::encode_one(request).expect("encode creation request");
1055
1056 assert_eq!(
1057 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1058 .expect("decode creation request"),
1059 request
1060 );
1061 }
1062
1063 #[test]
1064 fn component_subtree_removal_contracts_round_trip_through_candid() {
1065 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1066 let registry = ComponentRegistryHead {
1067 component,
1068 revision: 7,
1069 content_hash: [42; 32],
1070 };
1071 let request = RootComponentSubtreeRemovalRequest {
1072 operation_id: [43; 32],
1073 component,
1074 target_canister_id: Principal::from_slice(&[44; 29]),
1075 expected_registry: registry.clone(),
1076 };
1077 let status_request = RootComponentSubtreeRemovalStatusRequest {
1078 operation_id: request.operation_id,
1079 component,
1080 };
1081 let response = RootComponentSubtreeRemovalResponse {
1082 operation_id: request.operation_id,
1083 component,
1084 target_canister_id: request.target_canister_id,
1085 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1086 target_role: CanisterRole::new("project_instance"),
1087 target_status: ComponentLifecycleStatus::Active,
1088 reserved_against_registry: registry,
1089 phase: RootComponentSubtreeRemovalPhase::Fenced,
1090 };
1091
1092 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1093 let status_bytes =
1094 candid::encode_one(status_request).expect("encode subtree removal status request");
1095 let response_bytes =
1096 candid::encode_one(&response).expect("encode subtree removal response");
1097
1098 assert_eq!(
1099 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1100 .expect("decode subtree removal request"),
1101 request
1102 );
1103 assert_eq!(
1104 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1105 .expect("decode subtree removal status request"),
1106 status_request
1107 );
1108 assert_eq!(
1109 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1110 .expect("decode subtree removal response"),
1111 response
1112 );
1113 }
1114
1115 #[test]
1116 #[expect(
1117 clippy::too_many_lines,
1118 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1119 )]
1120 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1121 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1122 let registry = ComponentRegistryHead {
1123 component,
1124 revision: 2,
1125 content_hash: [12; 32],
1126 };
1127 let request = RootComponentChildAllocationRequest {
1128 operation_id: [13; 32],
1129 component,
1130 expected_registry: registry.clone(),
1131 child_role: CanisterRole::new("project_instance"),
1132 };
1133 let status_request = RootComponentChildAllocationStatusRequest {
1134 operation_id: request.operation_id,
1135 component,
1136 };
1137 let creation_request = RootComponentChildCreationRequest {
1138 operation_id: request.operation_id,
1139 component,
1140 };
1141 let install_request = RootComponentChildInstallRequest {
1142 operation_id: request.operation_id,
1143 component,
1144 };
1145 let commit_request = RootComponentChildCommitRequest {
1146 operation_id: request.operation_id,
1147 component,
1148 };
1149 let directory_request = RootComponentChildDirectoryPreparationRequest {
1150 operation_id: request.operation_id,
1151 component,
1152 };
1153 let activation_request = RootComponentChildRuntimeActivationRequest {
1154 operation_id: request.operation_id,
1155 component,
1156 };
1157 let membership_request = RootComponentChildMembershipActivationRequest {
1158 operation_id: request.operation_id,
1159 component,
1160 };
1161 let root = Principal::from_slice(&[17; 29]);
1162 let parent = Principal::from_slice(&[14; 29]);
1163 let child = Principal::from_slice(&[18; 29]);
1164 let child_binding = ComponentChildBinding {
1165 component: ComponentBinding {
1166 authority: fleet_registry_authority(),
1167 component,
1168 component_spec: "projects".parse().expect("Component Spec"),
1169 spec_hash: [19; 32],
1170 role: CanisterRole::new("project_hub"),
1171 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1172 fleet_subnet_root: root,
1173 canister_id: parent,
1174 },
1175 parent_canister_id: parent,
1176 role: request.child_role.clone(),
1177 canister_id: child,
1178 };
1179 let response = RootComponentChildAllocationResponse {
1180 operation_id: request.operation_id,
1181 component,
1182 parent_canister_id: parent,
1183 parent_role: CanisterRole::new("project_hub"),
1184 child_role: request.child_role.clone(),
1185 child_kind: ComponentChildKind::Instance,
1186 maximum_instances_per_parent: 10_000,
1187 maximum_descendants: 20_000,
1188 maximum_registry_bytes: 16_777_216,
1189 reserved_against_registry: registry,
1190 release_set: FleetSubnetRootReleaseSet {
1191 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1192 [15; 32],
1193 )),
1194 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1195 },
1196 phase: RootComponentAllocationPhase::Verified,
1197 creation: Some(RootComponentCreationEvidence {
1198 wasm_store: Principal::from_slice(&[21; 29]),
1199 payload_hash: [22; 32],
1200 payload_size_bytes: 4_096,
1201 initial_cycles: Cycles::new(5_000_000_000_000),
1202 controller: root,
1203 canister: Some(child),
1204 }),
1205 installation: Some(RootComponentChildInstallEvidence {
1206 raw_module_hash: [23; 32],
1207 chunk_hashes: vec![vec![24; 32]],
1208 binding: child_binding.clone(),
1209 }),
1210 };
1211 let commit_response = RootComponentChildCommitResponse {
1212 allocation: response.clone(),
1213 registry: ComponentRegistryPartitionResponse {
1214 head: ComponentRegistryHead {
1215 component,
1216 revision: 3,
1217 content_hash: [25; 32],
1218 },
1219 binding: child_binding.component.clone(),
1220 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1221 caller: Principal::from_slice(&[26; 29]),
1222 },
1223 release_set: response.release_set,
1224 status: ComponentLifecycleStatus::Active,
1225 reserved_descendants: 0,
1226 committed_descendants: 1,
1227 encoded_bytes: 8_192,
1228 },
1229 directory: ComponentDirectoryHead {
1230 provenance: ComponentDirectoryProvenance {
1231 component: child_binding.component.clone(),
1232 source_fleet_subnet_root: root,
1233 component_registry_revision: 3,
1234 component_registry_content_hash: [25; 32],
1235 synchronized_at_ns: 27,
1236 },
1237 descendant_count: 1,
1238 },
1239 };
1240 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1241 fleet: FleetDirectorySnapshot {
1242 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1243 registry: FleetRegistryVersion {
1244 authority: fleet_registry_authority(),
1245 revision: 4,
1246 content_hash: [28; 32],
1247 },
1248 source_fleet_subnet_root: root,
1249 },
1250 fleet_subnet_roots: vec![
1251 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1252 placement_subnet: commit_response.registry.binding.placement_subnet,
1253 fleet_subnet_root: root,
1254 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1255 },
1256 ],
1257 },
1258 component: commit_response.directory.clone(),
1259 };
1260 let activation = ComponentRuntimeActivationEvidence {
1261 directory_authority_hash: [29; 32],
1262 activated_at_ns: 30,
1263 };
1264 let directory_response = RootComponentChildDirectoryPreparationResponse {
1265 committed: commit_response.clone(),
1266 child: ComponentRuntimeStatusResponse {
1267 operation_id: request.operation_id,
1268 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1269 phase: ComponentRuntimePhase::DirectoryPrepared,
1270 authority: Some(runtime_authority.clone()),
1271 authority_hash: Some([31; 32]),
1272 activation: None,
1273 },
1274 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1275 operation_id: [32; 32],
1276 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1277 covered_authority: runtime_authority.clone(),
1278 covered_authority_hash: [31; 32],
1279 activation,
1280 },
1281 parent: None,
1282 };
1283 let activation_response = RootComponentChildRuntimeActivationResponse {
1284 committed: commit_response.clone(),
1285 child: ComponentRuntimeStatusResponse {
1286 operation_id: request.operation_id,
1287 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1288 phase: ComponentRuntimePhase::Active,
1289 authority: Some(runtime_authority.clone()),
1290 authority_hash: Some([31; 32]),
1291 activation: Some(ComponentRuntimeActivationEvidence {
1292 directory_authority_hash: [31; 32],
1293 activated_at_ns: 33,
1294 }),
1295 },
1296 };
1297 let active_directory = ComponentDirectoryHead {
1298 provenance: ComponentDirectoryProvenance {
1299 component: child_binding.component.clone(),
1300 source_fleet_subnet_root: root,
1301 component_registry_revision: 4,
1302 component_registry_content_hash: [34; 32],
1303 synchronized_at_ns: 35,
1304 },
1305 descendant_count: 1,
1306 };
1307 let active_authority = ComponentRuntimeDirectoryAuthority {
1308 fleet: runtime_authority.fleet,
1309 component: active_directory.clone(),
1310 };
1311 let membership_response = RootComponentChildMembershipActivationResponse {
1312 committed: commit_response.clone(),
1313 registry: ComponentRegistryPartitionResponse {
1314 head: ComponentRegistryHead {
1315 component,
1316 revision: 4,
1317 content_hash: [34; 32],
1318 },
1319 binding: child_binding.component.clone(),
1320 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1321 release_set: commit_response.registry.release_set,
1322 status: ComponentLifecycleStatus::Active,
1323 reserved_descendants: 0,
1324 committed_descendants: 1,
1325 encoded_bytes: 8_256,
1326 },
1327 directory: active_directory,
1328 child: ComponentRuntimeStatusResponse {
1329 operation_id: request.operation_id,
1330 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1331 phase: ComponentRuntimePhase::Active,
1332 authority: Some(active_authority),
1333 authority_hash: Some([36; 32]),
1334 activation: Some(ComponentRuntimeActivationEvidence {
1335 directory_authority_hash: [31; 32],
1336 activated_at_ns: 33,
1337 }),
1338 },
1339 };
1340
1341 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1342 let status_bytes =
1343 candid::encode_one(status_request).expect("encode child reservation status");
1344 let creation_bytes =
1345 candid::encode_one(creation_request).expect("encode child creation request");
1346 let install_bytes =
1347 candid::encode_one(install_request).expect("encode child install request");
1348 let response_bytes = candid::encode_one(&response).expect("encode child response");
1349 let commit_request_bytes =
1350 candid::encode_one(commit_request).expect("encode child commit request");
1351 let directory_request_bytes =
1352 candid::encode_one(directory_request).expect("encode child Directory request");
1353 let activation_request_bytes =
1354 candid::encode_one(activation_request).expect("encode child activation request");
1355 let membership_request_bytes =
1356 candid::encode_one(membership_request).expect("encode child membership request");
1357 let commit_response_bytes =
1358 candid::encode_one(&commit_response).expect("encode child commit response");
1359 let directory_response_bytes =
1360 candid::encode_one(&directory_response).expect("encode child Directory response");
1361 let activation_response_bytes =
1362 candid::encode_one(&activation_response).expect("encode child activation response");
1363 let membership_response_bytes =
1364 candid::encode_one(&membership_response).expect("encode child membership response");
1365
1366 assert_eq!(
1367 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1368 .expect("decode child reservation"),
1369 request
1370 );
1371 assert_eq!(
1372 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1373 .expect("decode child reservation status"),
1374 status_request
1375 );
1376 assert_eq!(
1377 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1378 .expect("decode child creation request"),
1379 creation_request
1380 );
1381 assert_eq!(
1382 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1383 .expect("decode child install request"),
1384 install_request
1385 );
1386 assert_eq!(
1387 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1388 .expect("decode child response"),
1389 response
1390 );
1391 assert_eq!(
1392 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1393 .expect("decode child commit request"),
1394 commit_request
1395 );
1396 assert_eq!(
1397 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1398 &directory_request_bytes
1399 )
1400 .expect("decode child Directory request"),
1401 directory_request
1402 );
1403 assert_eq!(
1404 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1405 .expect("decode child commit response"),
1406 commit_response
1407 );
1408 assert_eq!(
1409 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1410 &directory_response_bytes
1411 )
1412 .expect("decode child Directory response"),
1413 directory_response
1414 );
1415 assert_eq!(
1416 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1417 &activation_request_bytes
1418 )
1419 .expect("decode child activation request"),
1420 activation_request
1421 );
1422 assert_eq!(
1423 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1424 &activation_response_bytes
1425 )
1426 .expect("decode child activation response"),
1427 activation_response
1428 );
1429 assert_eq!(
1430 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1431 &membership_request_bytes
1432 )
1433 .expect("decode child membership request"),
1434 membership_request
1435 );
1436 assert_eq!(
1437 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1438 &membership_response_bytes
1439 )
1440 .expect("decode child membership response"),
1441 membership_response
1442 );
1443 }
1444
1445 #[test]
1446 fn component_install_request_round_trips_through_candid() {
1447 let request = RootComponentInstallRequest {
1448 operation_id: [10; 32],
1449 };
1450 let bytes = candid::encode_one(request).expect("encode install request");
1451
1452 assert_eq!(
1453 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1454 .expect("decode install request"),
1455 request
1456 );
1457 }
1458
1459 #[test]
1460 fn component_commit_request_round_trips_through_candid() {
1461 let request = RootComponentCommitRequest {
1462 operation_id: [10; 32],
1463 };
1464 let bytes = candid::encode_one(request).expect("encode commit request");
1465
1466 assert_eq!(
1467 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1468 .expect("decode commit request"),
1469 request
1470 );
1471 }
1472
1473 #[test]
1474 fn component_runtime_activation_requests_round_trip_through_candid() {
1475 let root_request = RootComponentRuntimeActivationRequest {
1476 operation_id: [22; 32],
1477 };
1478 let target_request = ComponentRuntimeActivationRequest {
1479 operation_id: root_request.operation_id,
1480 directory_authority_hash: [23; 32],
1481 };
1482 let membership_request = RootComponentMembershipActivationRequest {
1483 operation_id: root_request.operation_id,
1484 };
1485 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1486 let target_bytes =
1487 candid::encode_one(target_request).expect("encode target activation request");
1488 let membership_bytes =
1489 candid::encode_one(membership_request).expect("encode membership activation request");
1490
1491 assert_eq!(
1492 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1493 .expect("decode root activation request"),
1494 root_request
1495 );
1496 assert_eq!(
1497 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1498 .expect("decode target activation request"),
1499 target_request
1500 );
1501 assert_eq!(
1502 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1503 .expect("decode membership activation request"),
1504 membership_request
1505 );
1506 }
1507}