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, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentChildCreationRequest {
129 pub operation_id: [u8; 32],
130 pub component: ComponentInstanceId,
131}
132
133#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct RootComponentChildInstallRequest {
141 pub operation_id: [u8; 32],
142 pub component: ComponentInstanceId,
143}
144
145#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152pub struct RootComponentChildCommitRequest {
153 pub operation_id: [u8; 32],
154 pub component: ComponentInstanceId,
155}
156
157#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164pub struct RootComponentChildDirectoryPreparationRequest {
165 pub operation_id: [u8; 32],
166 pub component: ComponentInstanceId,
167}
168
169#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub struct RootComponentChildRuntimeActivationRequest {
177 pub operation_id: [u8; 32],
178 pub component: ComponentInstanceId,
179}
180
181#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct RootComponentChildMembershipActivationRequest {
189 pub operation_id: [u8; 32],
190 pub component: ComponentInstanceId,
191}
192
193#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct RootComponentCreationRequest {
201 pub operation_id: [u8; 32],
202}
203
204#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
211pub struct RootComponentInstallRequest {
212 pub operation_id: [u8; 32],
213}
214
215#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
222pub struct RootComponentCommitRequest {
223 pub operation_id: [u8; 32],
224}
225
226#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
233pub struct RootComponentDirectoryPreparationRequest {
234 pub operation_id: [u8; 32],
235}
236
237#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
244pub struct RootComponentRuntimeActivationRequest {
245 pub operation_id: [u8; 32],
246}
247
248#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
255pub struct RootComponentMembershipActivationRequest {
256 pub operation_id: [u8; 32],
257}
258
259#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
266pub enum ComponentProvisioningOrigin {
267 FleetAdministrator { caller: Principal },
268}
269
270#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
277pub enum RootComponentAllocationPhase {
278 Reserved,
279 CreationIntent,
280 Created,
281 InstallIntent,
282 Installed,
283 Verified,
284 Committed,
285}
286
287#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub enum ComponentLifecycleStatus {
295 Prepared,
296 Active,
297 Draining,
298 Removed,
299}
300
301#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
308pub struct ComponentRegistryHead {
309 pub component: ComponentInstanceId,
310 pub revision: u64,
311 pub content_hash: [u8; 32],
312}
313
314#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct ComponentRegistryPartitionRequest {
322 pub component: ComponentInstanceId,
323}
324
325#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
332pub struct ComponentRegistryPartitionResponse {
333 pub head: ComponentRegistryHead,
334 pub binding: ComponentBinding,
335 pub provisioning_origin: ComponentProvisioningOrigin,
336 pub release_set: FleetSubnetRootReleaseSet,
337 pub status: ComponentLifecycleStatus,
338 pub reserved_descendants: u32,
339 pub committed_descendants: u32,
340 pub encoded_bytes: u64,
341}
342
343#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
350pub struct ComponentDirectoryProvenance {
351 pub component: ComponentBinding,
352 pub source_fleet_subnet_root: Principal,
353 pub component_registry_revision: u64,
354 pub component_registry_content_hash: [u8; 32],
355 pub synchronized_at_ns: u64,
356}
357
358#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct ComponentDirectoryHead {
366 pub provenance: ComponentDirectoryProvenance,
367 pub descendant_count: u32,
368}
369
370#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
377pub struct ComponentDirectoryHeadRequest {
378 pub component: ComponentInstanceId,
379}
380
381#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
389
390#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
397pub struct ComponentDirectoryPageRequest {
398 pub directory: ComponentDirectoryHead,
399 pub parent_canister_id: Option<Principal>,
400 pub role: Option<CanisterRole>,
401 pub status: Option<ComponentLifecycleStatus>,
402 pub cursor: Option<ComponentDirectoryPageCursor>,
403 pub limit: u16,
404}
405
406#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct ComponentDirectoryChildEntry {
414 pub binding: ComponentChildBinding,
415 pub kind: ComponentChildKind,
416 pub installed_artifact_hash: [u8; 32],
417 pub status: ComponentLifecycleStatus,
418}
419
420#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
427pub struct ComponentDirectoryPageResponse {
428 pub directory: ComponentDirectoryHead,
429 pub entries: Vec<ComponentDirectoryChildEntry>,
430 pub next_cursor: Option<ComponentDirectoryPageCursor>,
431}
432
433#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
440pub struct ComponentRuntimeDirectoryAuthority {
441 pub fleet: FleetDirectorySnapshot,
442 pub component: ComponentDirectoryHead,
443}
444
445#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
452pub struct ComponentRuntimeDirectoryPreparationRequest {
453 pub operation_id: [u8; 32],
454 pub authority: ComponentRuntimeDirectoryAuthority,
455}
456
457#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
464pub struct ComponentRuntimeDirectorySynchronizationRequest {
465 pub operation_id: [u8; 32],
466 pub authority: ComponentRuntimeDirectoryAuthority,
467}
468
469#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
476pub enum ComponentRuntimePhase {
477 AwaitingDirectory,
478 DirectoryPrepared,
479 Active,
480}
481
482#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
489pub struct ComponentRuntimeActivationEvidence {
490 pub directory_authority_hash: [u8; 32],
491 pub activated_at_ns: u64,
492}
493
494#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
501pub struct ComponentRuntimeActivationRequest {
502 pub operation_id: [u8; 32],
503 pub directory_authority_hash: [u8; 32],
504}
505
506#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
513pub struct ComponentRuntimeStatusResponse {
514 pub operation_id: [u8; 32],
515 pub binding: ManagedCanisterBinding,
516 pub phase: ComponentRuntimePhase,
517 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
518 pub authority_hash: Option<[u8; 32]>,
519 pub activation: Option<ComponentRuntimeActivationEvidence>,
520}
521
522#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
529pub struct ComponentRuntimeDirectoryConvergenceEvidence {
530 pub operation_id: [u8; 32],
531 pub binding: ManagedCanisterBinding,
532 pub covered_authority: ComponentRuntimeDirectoryAuthority,
533 pub covered_authority_hash: [u8; 32],
534 pub activation: ComponentRuntimeActivationEvidence,
535}
536
537#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
544pub struct RootComponentCreationEvidence {
545 pub wasm_store: Principal,
546 pub payload_hash: [u8; 32],
547 pub payload_size_bytes: u64,
548 pub initial_cycles: Cycles,
549 pub controller: Principal,
550 pub canister: Option<Principal>,
551}
552
553#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
560pub struct RootComponentInstallEvidence {
561 pub raw_module_hash: [u8; 32],
562 pub chunk_hashes: Vec<Vec<u8>>,
563 pub binding: ComponentBinding,
564}
565
566#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
573pub struct RootComponentChildInstallEvidence {
574 pub raw_module_hash: [u8; 32],
575 pub chunk_hashes: Vec<Vec<u8>>,
576 pub binding: ComponentChildBinding,
577}
578
579#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
586pub struct RootComponentAllocationResponse {
587 pub operation_id: [u8; 32],
588 pub allocation_sequence: u64,
589 pub component: ComponentInstanceId,
590 pub component_spec: ComponentSpecId,
591 pub spec_hash: [u8; 32],
592 pub role: CanisterRole,
593 pub provisioning_origin: ComponentProvisioningOrigin,
594 pub release_set: FleetSubnetRootReleaseSet,
595 pub phase: RootComponentAllocationPhase,
596 pub creation: Option<RootComponentCreationEvidence>,
597 pub installation: Option<RootComponentInstallEvidence>,
598}
599
600#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
607pub struct RootComponentChildAllocationResponse {
608 pub operation_id: [u8; 32],
609 pub component: ComponentInstanceId,
610 pub parent_canister_id: Principal,
611 pub parent_role: CanisterRole,
612 pub child_role: CanisterRole,
613 pub child_kind: ComponentChildKind,
614 pub maximum_instances_per_parent: u32,
615 pub maximum_descendants: u32,
616 pub maximum_registry_bytes: u64,
617 pub reserved_against_registry: ComponentRegistryHead,
618 pub release_set: FleetSubnetRootReleaseSet,
619 pub phase: RootComponentAllocationPhase,
620 pub creation: Option<RootComponentCreationEvidence>,
621 pub installation: Option<RootComponentChildInstallEvidence>,
622}
623
624#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
631pub struct RootComponentChildCommitResponse {
632 pub allocation: RootComponentChildAllocationResponse,
633 pub registry: ComponentRegistryPartitionResponse,
634 pub directory: ComponentDirectoryHead,
635}
636
637#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
644pub struct RootComponentChildDirectoryPreparationResponse {
645 pub committed: RootComponentChildCommitResponse,
646 pub child: ComponentRuntimeStatusResponse,
647 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
648 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
649}
650
651#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
658pub struct RootComponentChildRuntimeActivationResponse {
659 pub committed: RootComponentChildCommitResponse,
660 pub child: ComponentRuntimeStatusResponse,
661}
662
663#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
670pub struct RootComponentChildMembershipActivationResponse {
671 pub committed: RootComponentChildCommitResponse,
672 pub registry: ComponentRegistryPartitionResponse,
673 pub directory: ComponentDirectoryHead,
674 pub child: ComponentRuntimeStatusResponse,
675}
676
677#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
684pub struct RootComponentCommitResponse {
685 pub allocation: RootComponentAllocationResponse,
686 pub registry: ComponentRegistryPartitionResponse,
687 pub directory: ComponentDirectoryHead,
688}
689
690#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
697pub struct RootComponentDirectoryPreparationResponse {
698 pub committed: RootComponentCommitResponse,
699 pub target: ComponentRuntimeStatusResponse,
700}
701
702#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
709pub struct RootComponentRuntimeActivationResponse {
710 pub committed: RootComponentCommitResponse,
711 pub target: ComponentRuntimeStatusResponse,
712}
713
714#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
721pub struct RootComponentMembershipActivationResponse {
722 pub allocation: RootComponentAllocationResponse,
723 pub registry: ComponentRegistryPartitionResponse,
724 pub directory: ComponentDirectoryHead,
725 pub target: ComponentRuntimeStatusResponse,
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731 use crate::{
732 dto::root_store::RootStoreBootstrapRequest,
733 ids::{
734 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
735 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
736 },
737 };
738
739 #[test]
740 fn component_registry_contracts_round_trip_through_candid() {
741 let request = RootComponentRegistryPreparationRequest {
742 store_bootstrap: RootStoreBootstrapRequest {
743 manifest_payload_size_bytes: 128,
744 },
745 expected_fleet_registry: FleetRegistryVersion {
746 authority: fleet_registry_authority(),
747 revision: 4,
748 content_hash: [5; 32],
749 },
750 };
751 let response = RootComponentRegistryStatusResponse {
752 fleet_subnet_root: Principal::from_slice(&[6; 29]),
753 prepared_against_registry: request.expected_fleet_registry.clone(),
754 release_set: FleetSubnetRootReleaseSet {
755 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
756 [7; 32],
757 )),
758 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
759 },
760 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
761 next_allocation_sequence: 1,
762 reserved_component_instances: 0,
763 committed_component_instances: 0,
764 managed_descendants: 0,
765 known_created_component_canisters: 0,
766 encoded_bytes: 0,
767 initial_inventory: Some(RootComponentInitialInventoryStatus {
768 fleet_activation_operation_id: [10; 32],
769 component_count: 0,
770 inventory_hash: [11; 32],
771 sealed_at_ns: 12,
772 directories_converged: true,
773 root_runtime_activated: true,
774 }),
775 };
776 let allocation = RootComponentAllocationResponse {
777 operation_id: [10; 32],
778 allocation_sequence: 1,
779 component: ComponentInstanceId::from_generated_bytes([11; 32]),
780 component_spec: "projects".parse().expect("Component Spec ID"),
781 spec_hash: [12; 32],
782 role: CanisterRole::new("project_hub"),
783 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
784 caller: Principal::from_slice(&[13; 29]),
785 },
786 release_set: response.release_set,
787 phase: RootComponentAllocationPhase::Reserved,
788 creation: None,
789 installation: None,
790 };
791 let created = RootComponentAllocationResponse {
792 phase: RootComponentAllocationPhase::Created,
793 creation: Some(RootComponentCreationEvidence {
794 wasm_store: Principal::from_slice(&[14; 29]),
795 payload_hash: [15; 32],
796 payload_size_bytes: 4_096,
797 initial_cycles: Cycles::new(5_000_000_000_000),
798 controller: Principal::from_slice(&[6; 29]),
799 canister: Some(Principal::from_slice(&[16; 29])),
800 }),
801 installation: None,
802 ..allocation.clone()
803 };
804 let request_bytes = candid::encode_one(&request).expect("encode request");
805 let response_bytes = candid::encode_one(&response).expect("encode response");
806 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
807 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
808
809 assert_eq!(
810 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
811 .expect("decode request"),
812 request
813 );
814 assert_eq!(
815 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
816 .expect("decode response"),
817 response
818 );
819 assert_eq!(
820 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
821 .expect("decode allocation"),
822 allocation
823 );
824 assert_eq!(
825 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
826 .expect("decode created allocation"),
827 created
828 );
829 }
830
831 #[test]
832 fn component_commit_response_round_trips_through_candid() {
833 let root = Principal::from_slice(&[6; 29]);
834 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
835 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
836 let release_set = FleetSubnetRootReleaseSet {
837 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
838 [7; 32],
839 )),
840 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
841 };
842 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
843 caller: Principal::from_slice(&[13; 29]),
844 };
845 let binding = ComponentBinding {
846 authority: fleet_registry_authority(),
847 component,
848 component_spec: component_spec.clone(),
849 spec_hash: [12; 32],
850 role: CanisterRole::new("project_hub"),
851 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
852 fleet_subnet_root: root,
853 canister_id: Principal::from_slice(&[16; 29]),
854 };
855 let head = ComponentRegistryHead {
856 component,
857 revision: 1,
858 content_hash: [18; 32],
859 };
860 let committed = RootComponentCommitResponse {
861 allocation: RootComponentAllocationResponse {
862 operation_id: [10; 32],
863 allocation_sequence: 1,
864 component,
865 component_spec,
866 spec_hash: binding.spec_hash,
867 role: binding.role.clone(),
868 provisioning_origin: provisioning_origin.clone(),
869 release_set,
870 phase: RootComponentAllocationPhase::Committed,
871 creation: Some(RootComponentCreationEvidence {
872 wasm_store: Principal::from_slice(&[14; 29]),
873 payload_hash: [15; 32],
874 payload_size_bytes: 4_096,
875 initial_cycles: Cycles::new(5_000_000_000_000),
876 controller: root,
877 canister: Some(binding.canister_id),
878 }),
879 installation: Some(RootComponentInstallEvidence {
880 raw_module_hash: [20; 32],
881 chunk_hashes: vec![vec![21; 32]],
882 binding: binding.clone(),
883 }),
884 },
885 registry: ComponentRegistryPartitionResponse {
886 head: head.clone(),
887 binding: binding.clone(),
888 provisioning_origin,
889 release_set,
890 status: ComponentLifecycleStatus::Prepared,
891 reserved_descendants: 0,
892 committed_descendants: 0,
893 encoded_bytes: 2_048,
894 },
895 directory: ComponentDirectoryHead {
896 provenance: ComponentDirectoryProvenance {
897 component: binding,
898 source_fleet_subnet_root: root,
899 component_registry_revision: head.revision,
900 component_registry_content_hash: head.content_hash,
901 synchronized_at_ns: 19,
902 },
903 descendant_count: 0,
904 },
905 };
906 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
907
908 assert_eq!(
909 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
910 .expect("decode committed allocation"),
911 committed
912 );
913 }
914
915 #[test]
916 fn component_directory_page_contracts_round_trip_through_candid() {
917 let root = Principal::from_slice(&[6; 29]);
918 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
919 let binding = ComponentBinding {
920 authority: fleet_registry_authority(),
921 component,
922 component_spec: "projects".parse().expect("Component Spec ID"),
923 spec_hash: [12; 32],
924 role: CanisterRole::new("project_hub"),
925 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
926 fleet_subnet_root: root,
927 canister_id: Principal::from_slice(&[16; 29]),
928 };
929 let directory = ComponentDirectoryHead {
930 provenance: ComponentDirectoryProvenance {
931 component: binding.clone(),
932 source_fleet_subnet_root: root,
933 component_registry_revision: 3,
934 component_registry_content_hash: [18; 32],
935 synchronized_at_ns: 19,
936 },
937 descendant_count: 1,
938 };
939 let request = ComponentDirectoryPageRequest {
940 directory: directory.clone(),
941 parent_canister_id: Some(binding.canister_id),
942 role: Some(CanisterRole::new("project_instance")),
943 status: Some(ComponentLifecycleStatus::Active),
944 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
945 limit: 50,
946 };
947 let response = ComponentDirectoryPageResponse {
948 directory,
949 entries: vec![ComponentDirectoryChildEntry {
950 binding: ComponentChildBinding {
951 component: binding.clone(),
952 parent_canister_id: binding.canister_id,
953 role: CanisterRole::new("project_instance"),
954 canister_id: Principal::from_slice(&[21; 29]),
955 },
956 kind: ComponentChildKind::Instance,
957 installed_artifact_hash: [22; 32],
958 status: ComponentLifecycleStatus::Active,
959 }],
960 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
961 };
962 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
963 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
964
965 assert_eq!(
966 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
967 .expect("decode Directory page request"),
968 request
969 );
970 assert_eq!(
971 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
972 .expect("decode Directory page response"),
973 response
974 );
975 }
976
977 fn fleet_registry_authority() -> FleetRegistryAuthority {
978 FleetRegistryAuthority {
979 binding: FleetCoordinatorBinding {
980 fleet: crate::ids::FleetBinding {
981 fleet: FleetKey {
982 canonical_network_id: CanonicalNetworkId::public_ic(),
983 fleet_id: FleetId::from_generated_bytes([1; 32]),
984 },
985 app: AppId::from("toko"),
986 },
987 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
988 coordinator: Principal::from_slice(&[3; 29]),
989 },
990 epoch: 1,
991 }
992 }
993
994 #[test]
995 fn component_creation_request_round_trips_through_candid() {
996 let request = RootComponentCreationRequest {
997 operation_id: [10; 32],
998 };
999 let bytes = candid::encode_one(request).expect("encode creation request");
1000
1001 assert_eq!(
1002 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1003 .expect("decode creation request"),
1004 request
1005 );
1006 }
1007
1008 #[test]
1009 #[expect(
1010 clippy::too_many_lines,
1011 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1012 )]
1013 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1014 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1015 let registry = ComponentRegistryHead {
1016 component,
1017 revision: 2,
1018 content_hash: [12; 32],
1019 };
1020 let request = RootComponentChildAllocationRequest {
1021 operation_id: [13; 32],
1022 component,
1023 expected_registry: registry.clone(),
1024 child_role: CanisterRole::new("project_instance"),
1025 };
1026 let status_request = RootComponentChildAllocationStatusRequest {
1027 operation_id: request.operation_id,
1028 component,
1029 };
1030 let creation_request = RootComponentChildCreationRequest {
1031 operation_id: request.operation_id,
1032 component,
1033 };
1034 let install_request = RootComponentChildInstallRequest {
1035 operation_id: request.operation_id,
1036 component,
1037 };
1038 let commit_request = RootComponentChildCommitRequest {
1039 operation_id: request.operation_id,
1040 component,
1041 };
1042 let directory_request = RootComponentChildDirectoryPreparationRequest {
1043 operation_id: request.operation_id,
1044 component,
1045 };
1046 let activation_request = RootComponentChildRuntimeActivationRequest {
1047 operation_id: request.operation_id,
1048 component,
1049 };
1050 let membership_request = RootComponentChildMembershipActivationRequest {
1051 operation_id: request.operation_id,
1052 component,
1053 };
1054 let root = Principal::from_slice(&[17; 29]);
1055 let parent = Principal::from_slice(&[14; 29]);
1056 let child = Principal::from_slice(&[18; 29]);
1057 let child_binding = ComponentChildBinding {
1058 component: ComponentBinding {
1059 authority: fleet_registry_authority(),
1060 component,
1061 component_spec: "projects".parse().expect("Component Spec"),
1062 spec_hash: [19; 32],
1063 role: CanisterRole::new("project_hub"),
1064 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1065 fleet_subnet_root: root,
1066 canister_id: parent,
1067 },
1068 parent_canister_id: parent,
1069 role: request.child_role.clone(),
1070 canister_id: child,
1071 };
1072 let response = RootComponentChildAllocationResponse {
1073 operation_id: request.operation_id,
1074 component,
1075 parent_canister_id: parent,
1076 parent_role: CanisterRole::new("project_hub"),
1077 child_role: request.child_role.clone(),
1078 child_kind: ComponentChildKind::Instance,
1079 maximum_instances_per_parent: 10_000,
1080 maximum_descendants: 20_000,
1081 maximum_registry_bytes: 16_777_216,
1082 reserved_against_registry: registry,
1083 release_set: FleetSubnetRootReleaseSet {
1084 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1085 [15; 32],
1086 )),
1087 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1088 },
1089 phase: RootComponentAllocationPhase::Verified,
1090 creation: Some(RootComponentCreationEvidence {
1091 wasm_store: Principal::from_slice(&[21; 29]),
1092 payload_hash: [22; 32],
1093 payload_size_bytes: 4_096,
1094 initial_cycles: Cycles::new(5_000_000_000_000),
1095 controller: root,
1096 canister: Some(child),
1097 }),
1098 installation: Some(RootComponentChildInstallEvidence {
1099 raw_module_hash: [23; 32],
1100 chunk_hashes: vec![vec![24; 32]],
1101 binding: child_binding.clone(),
1102 }),
1103 };
1104 let commit_response = RootComponentChildCommitResponse {
1105 allocation: response.clone(),
1106 registry: ComponentRegistryPartitionResponse {
1107 head: ComponentRegistryHead {
1108 component,
1109 revision: 3,
1110 content_hash: [25; 32],
1111 },
1112 binding: child_binding.component.clone(),
1113 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1114 caller: Principal::from_slice(&[26; 29]),
1115 },
1116 release_set: response.release_set,
1117 status: ComponentLifecycleStatus::Active,
1118 reserved_descendants: 0,
1119 committed_descendants: 1,
1120 encoded_bytes: 8_192,
1121 },
1122 directory: ComponentDirectoryHead {
1123 provenance: ComponentDirectoryProvenance {
1124 component: child_binding.component.clone(),
1125 source_fleet_subnet_root: root,
1126 component_registry_revision: 3,
1127 component_registry_content_hash: [25; 32],
1128 synchronized_at_ns: 27,
1129 },
1130 descendant_count: 1,
1131 },
1132 };
1133 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1134 fleet: FleetDirectorySnapshot {
1135 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1136 registry: FleetRegistryVersion {
1137 authority: fleet_registry_authority(),
1138 revision: 4,
1139 content_hash: [28; 32],
1140 },
1141 source_fleet_subnet_root: root,
1142 },
1143 fleet_subnet_roots: vec![
1144 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1145 placement_subnet: commit_response.registry.binding.placement_subnet,
1146 fleet_subnet_root: root,
1147 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1148 },
1149 ],
1150 },
1151 component: commit_response.directory.clone(),
1152 };
1153 let activation = ComponentRuntimeActivationEvidence {
1154 directory_authority_hash: [29; 32],
1155 activated_at_ns: 30,
1156 };
1157 let directory_response = RootComponentChildDirectoryPreparationResponse {
1158 committed: commit_response.clone(),
1159 child: ComponentRuntimeStatusResponse {
1160 operation_id: request.operation_id,
1161 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1162 phase: ComponentRuntimePhase::DirectoryPrepared,
1163 authority: Some(runtime_authority.clone()),
1164 authority_hash: Some([31; 32]),
1165 activation: None,
1166 },
1167 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1168 operation_id: [32; 32],
1169 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1170 covered_authority: runtime_authority.clone(),
1171 covered_authority_hash: [31; 32],
1172 activation,
1173 },
1174 parent: None,
1175 };
1176 let activation_response = RootComponentChildRuntimeActivationResponse {
1177 committed: commit_response.clone(),
1178 child: ComponentRuntimeStatusResponse {
1179 operation_id: request.operation_id,
1180 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1181 phase: ComponentRuntimePhase::Active,
1182 authority: Some(runtime_authority.clone()),
1183 authority_hash: Some([31; 32]),
1184 activation: Some(ComponentRuntimeActivationEvidence {
1185 directory_authority_hash: [31; 32],
1186 activated_at_ns: 33,
1187 }),
1188 },
1189 };
1190 let active_directory = ComponentDirectoryHead {
1191 provenance: ComponentDirectoryProvenance {
1192 component: child_binding.component.clone(),
1193 source_fleet_subnet_root: root,
1194 component_registry_revision: 4,
1195 component_registry_content_hash: [34; 32],
1196 synchronized_at_ns: 35,
1197 },
1198 descendant_count: 1,
1199 };
1200 let active_authority = ComponentRuntimeDirectoryAuthority {
1201 fleet: runtime_authority.fleet,
1202 component: active_directory.clone(),
1203 };
1204 let membership_response = RootComponentChildMembershipActivationResponse {
1205 committed: commit_response.clone(),
1206 registry: ComponentRegistryPartitionResponse {
1207 head: ComponentRegistryHead {
1208 component,
1209 revision: 4,
1210 content_hash: [34; 32],
1211 },
1212 binding: child_binding.component.clone(),
1213 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1214 release_set: commit_response.registry.release_set,
1215 status: ComponentLifecycleStatus::Active,
1216 reserved_descendants: 0,
1217 committed_descendants: 1,
1218 encoded_bytes: 8_256,
1219 },
1220 directory: active_directory,
1221 child: ComponentRuntimeStatusResponse {
1222 operation_id: request.operation_id,
1223 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1224 phase: ComponentRuntimePhase::Active,
1225 authority: Some(active_authority),
1226 authority_hash: Some([36; 32]),
1227 activation: Some(ComponentRuntimeActivationEvidence {
1228 directory_authority_hash: [31; 32],
1229 activated_at_ns: 33,
1230 }),
1231 },
1232 };
1233
1234 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1235 let status_bytes =
1236 candid::encode_one(status_request).expect("encode child reservation status");
1237 let creation_bytes =
1238 candid::encode_one(creation_request).expect("encode child creation request");
1239 let install_bytes =
1240 candid::encode_one(install_request).expect("encode child install request");
1241 let response_bytes = candid::encode_one(&response).expect("encode child response");
1242 let commit_request_bytes =
1243 candid::encode_one(commit_request).expect("encode child commit request");
1244 let directory_request_bytes =
1245 candid::encode_one(directory_request).expect("encode child Directory request");
1246 let activation_request_bytes =
1247 candid::encode_one(activation_request).expect("encode child activation request");
1248 let membership_request_bytes =
1249 candid::encode_one(membership_request).expect("encode child membership request");
1250 let commit_response_bytes =
1251 candid::encode_one(&commit_response).expect("encode child commit response");
1252 let directory_response_bytes =
1253 candid::encode_one(&directory_response).expect("encode child Directory response");
1254 let activation_response_bytes =
1255 candid::encode_one(&activation_response).expect("encode child activation response");
1256 let membership_response_bytes =
1257 candid::encode_one(&membership_response).expect("encode child membership response");
1258
1259 assert_eq!(
1260 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1261 .expect("decode child reservation"),
1262 request
1263 );
1264 assert_eq!(
1265 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1266 .expect("decode child reservation status"),
1267 status_request
1268 );
1269 assert_eq!(
1270 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1271 .expect("decode child creation request"),
1272 creation_request
1273 );
1274 assert_eq!(
1275 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1276 .expect("decode child install request"),
1277 install_request
1278 );
1279 assert_eq!(
1280 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1281 .expect("decode child response"),
1282 response
1283 );
1284 assert_eq!(
1285 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1286 .expect("decode child commit request"),
1287 commit_request
1288 );
1289 assert_eq!(
1290 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1291 &directory_request_bytes
1292 )
1293 .expect("decode child Directory request"),
1294 directory_request
1295 );
1296 assert_eq!(
1297 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1298 .expect("decode child commit response"),
1299 commit_response
1300 );
1301 assert_eq!(
1302 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1303 &directory_response_bytes
1304 )
1305 .expect("decode child Directory response"),
1306 directory_response
1307 );
1308 assert_eq!(
1309 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1310 &activation_request_bytes
1311 )
1312 .expect("decode child activation request"),
1313 activation_request
1314 );
1315 assert_eq!(
1316 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1317 &activation_response_bytes
1318 )
1319 .expect("decode child activation response"),
1320 activation_response
1321 );
1322 assert_eq!(
1323 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1324 &membership_request_bytes
1325 )
1326 .expect("decode child membership request"),
1327 membership_request
1328 );
1329 assert_eq!(
1330 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1331 &membership_response_bytes
1332 )
1333 .expect("decode child membership response"),
1334 membership_response
1335 );
1336 }
1337
1338 #[test]
1339 fn component_install_request_round_trips_through_candid() {
1340 let request = RootComponentInstallRequest {
1341 operation_id: [10; 32],
1342 };
1343 let bytes = candid::encode_one(request).expect("encode install request");
1344
1345 assert_eq!(
1346 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1347 .expect("decode install request"),
1348 request
1349 );
1350 }
1351
1352 #[test]
1353 fn component_commit_request_round_trips_through_candid() {
1354 let request = RootComponentCommitRequest {
1355 operation_id: [10; 32],
1356 };
1357 let bytes = candid::encode_one(request).expect("encode commit request");
1358
1359 assert_eq!(
1360 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1361 .expect("decode commit request"),
1362 request
1363 );
1364 }
1365
1366 #[test]
1367 fn component_runtime_activation_requests_round_trip_through_candid() {
1368 let root_request = RootComponentRuntimeActivationRequest {
1369 operation_id: [22; 32],
1370 };
1371 let target_request = ComponentRuntimeActivationRequest {
1372 operation_id: root_request.operation_id,
1373 directory_authority_hash: [23; 32],
1374 };
1375 let membership_request = RootComponentMembershipActivationRequest {
1376 operation_id: root_request.operation_id,
1377 };
1378 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1379 let target_bytes =
1380 candid::encode_one(target_request).expect("encode target activation request");
1381 let membership_bytes =
1382 candid::encode_one(membership_request).expect("encode membership activation request");
1383
1384 assert_eq!(
1385 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1386 .expect("decode root activation request"),
1387 root_request
1388 );
1389 assert_eq!(
1390 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1391 .expect("decode target activation request"),
1392 target_request
1393 );
1394 assert_eq!(
1395 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1396 .expect("decode membership activation request"),
1397 membership_request
1398 );
1399 }
1400}