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 RootComponentSubtreeRemovalAdvanceRequest {
143 pub operation_id: [u8; 32],
144 pub component: ComponentInstanceId,
145 pub expected_traversal_steps: u32,
146}
147
148#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentSubtreeRemovalStatusRequest {
156 pub operation_id: [u8; 32],
157 pub component: ComponentInstanceId,
158}
159
160#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
167pub struct RootComponentChildCreationRequest {
168 pub operation_id: [u8; 32],
169 pub component: ComponentInstanceId,
170}
171
172#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
179pub struct RootComponentChildInstallRequest {
180 pub operation_id: [u8; 32],
181 pub component: ComponentInstanceId,
182}
183
184#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
191pub struct RootComponentChildCommitRequest {
192 pub operation_id: [u8; 32],
193 pub component: ComponentInstanceId,
194}
195
196#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct RootComponentChildDirectoryPreparationRequest {
204 pub operation_id: [u8; 32],
205 pub component: ComponentInstanceId,
206}
207
208#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct RootComponentChildRuntimeActivationRequest {
216 pub operation_id: [u8; 32],
217 pub component: ComponentInstanceId,
218}
219
220#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
227pub struct RootComponentChildMembershipActivationRequest {
228 pub operation_id: [u8; 32],
229 pub component: ComponentInstanceId,
230}
231
232#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
239pub struct RootComponentCreationRequest {
240 pub operation_id: [u8; 32],
241}
242
243#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
250pub struct RootComponentInstallRequest {
251 pub operation_id: [u8; 32],
252}
253
254#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
261pub struct RootComponentCommitRequest {
262 pub operation_id: [u8; 32],
263}
264
265#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
272pub struct RootComponentDirectoryPreparationRequest {
273 pub operation_id: [u8; 32],
274}
275
276#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
283pub struct RootComponentRuntimeActivationRequest {
284 pub operation_id: [u8; 32],
285}
286
287#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub struct RootComponentMembershipActivationRequest {
295 pub operation_id: [u8; 32],
296}
297
298#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
305pub enum ComponentProvisioningOrigin {
306 FleetAdministrator { caller: Principal },
307}
308
309#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
316pub enum RootComponentAllocationPhase {
317 Reserved,
318 CreationIntent,
319 Created,
320 InstallIntent,
321 Installed,
322 Verified,
323 Committed,
324}
325
326#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
333pub enum RootComponentSubtreeRemovalPhase {
334 Fenced,
335 Traversing(RootComponentSubtreeRemovalNode),
336 LeafSelected(RootComponentSubtreeRemovalNode),
337}
338
339#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
346pub struct RootComponentSubtreeRemovalNode {
347 pub canister_id: Principal,
348 pub parent_canister_id: Principal,
349 pub role: CanisterRole,
350 pub kind: ComponentChildKind,
351 pub installed_artifact_hash: [u8; 32],
352 pub status: ComponentLifecycleStatus,
353}
354
355#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
362pub enum ComponentLifecycleStatus {
363 Prepared,
364 Active,
365 Draining,
366 Removed,
367}
368
369#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRegistryHead {
377 pub component: ComponentInstanceId,
378 pub revision: u64,
379 pub content_hash: [u8; 32],
380}
381
382#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
389pub struct ComponentRegistryPartitionRequest {
390 pub component: ComponentInstanceId,
391}
392
393#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub struct ComponentRegistryPartitionResponse {
401 pub head: ComponentRegistryHead,
402 pub binding: ComponentBinding,
403 pub provisioning_origin: ComponentProvisioningOrigin,
404 pub release_set: FleetSubnetRootReleaseSet,
405 pub status: ComponentLifecycleStatus,
406 pub reserved_descendants: u32,
407 pub committed_descendants: u32,
408 pub encoded_bytes: u64,
409}
410
411#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
418pub struct ComponentDirectoryProvenance {
419 pub component: ComponentBinding,
420 pub source_fleet_subnet_root: Principal,
421 pub component_registry_revision: u64,
422 pub component_registry_content_hash: [u8; 32],
423 pub synchronized_at_ns: u64,
424}
425
426#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
433pub struct ComponentDirectoryHead {
434 pub provenance: ComponentDirectoryProvenance,
435 pub descendant_count: u32,
436}
437
438#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
445pub struct ComponentDirectoryHeadRequest {
446 pub component: ComponentInstanceId,
447}
448
449#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
456pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
457
458#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
465pub struct ComponentDirectoryPageRequest {
466 pub directory: ComponentDirectoryHead,
467 pub parent_canister_id: Option<Principal>,
468 pub role: Option<CanisterRole>,
469 pub status: Option<ComponentLifecycleStatus>,
470 pub cursor: Option<ComponentDirectoryPageCursor>,
471 pub limit: u16,
472}
473
474#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
481pub struct ComponentDirectoryChildEntry {
482 pub binding: ComponentChildBinding,
483 pub kind: ComponentChildKind,
484 pub installed_artifact_hash: [u8; 32],
485 pub status: ComponentLifecycleStatus,
486}
487
488#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
495pub struct ComponentDirectoryPageResponse {
496 pub directory: ComponentDirectoryHead,
497 pub entries: Vec<ComponentDirectoryChildEntry>,
498 pub next_cursor: Option<ComponentDirectoryPageCursor>,
499}
500
501#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
508pub struct ComponentRuntimeDirectoryAuthority {
509 pub fleet: FleetDirectorySnapshot,
510 pub component: ComponentDirectoryHead,
511}
512
513#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
520pub struct ComponentRuntimeDirectoryPreparationRequest {
521 pub operation_id: [u8; 32],
522 pub authority: ComponentRuntimeDirectoryAuthority,
523}
524
525#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
532pub struct ComponentRuntimeDirectorySynchronizationRequest {
533 pub operation_id: [u8; 32],
534 pub authority: ComponentRuntimeDirectoryAuthority,
535}
536
537#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
544pub enum ComponentRuntimePhase {
545 AwaitingDirectory,
546 DirectoryPrepared,
547 Active,
548}
549
550#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
557pub struct ComponentRuntimeActivationEvidence {
558 pub directory_authority_hash: [u8; 32],
559 pub activated_at_ns: u64,
560}
561
562#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
569pub struct ComponentRuntimeActivationRequest {
570 pub operation_id: [u8; 32],
571 pub directory_authority_hash: [u8; 32],
572}
573
574#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
581pub struct ComponentRuntimeStatusResponse {
582 pub operation_id: [u8; 32],
583 pub binding: ManagedCanisterBinding,
584 pub phase: ComponentRuntimePhase,
585 pub authority: Option<ComponentRuntimeDirectoryAuthority>,
586 pub authority_hash: Option<[u8; 32]>,
587 pub activation: Option<ComponentRuntimeActivationEvidence>,
588}
589
590#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
597pub struct ComponentRuntimeDirectoryConvergenceEvidence {
598 pub operation_id: [u8; 32],
599 pub binding: ManagedCanisterBinding,
600 pub covered_authority: ComponentRuntimeDirectoryAuthority,
601 pub covered_authority_hash: [u8; 32],
602 pub activation: ComponentRuntimeActivationEvidence,
603}
604
605#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
612pub struct RootComponentCreationEvidence {
613 pub wasm_store: Principal,
614 pub payload_hash: [u8; 32],
615 pub payload_size_bytes: u64,
616 pub initial_cycles: Cycles,
617 pub controller: Principal,
618 pub canister: Option<Principal>,
619}
620
621#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
628pub struct RootComponentInstallEvidence {
629 pub raw_module_hash: [u8; 32],
630 pub chunk_hashes: Vec<Vec<u8>>,
631 pub binding: ComponentBinding,
632}
633
634#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
641pub struct RootComponentChildInstallEvidence {
642 pub raw_module_hash: [u8; 32],
643 pub chunk_hashes: Vec<Vec<u8>>,
644 pub binding: ComponentChildBinding,
645}
646
647#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
654pub struct RootComponentAllocationResponse {
655 pub operation_id: [u8; 32],
656 pub allocation_sequence: u64,
657 pub component: ComponentInstanceId,
658 pub component_spec: ComponentSpecId,
659 pub spec_hash: [u8; 32],
660 pub role: CanisterRole,
661 pub provisioning_origin: ComponentProvisioningOrigin,
662 pub release_set: FleetSubnetRootReleaseSet,
663 pub phase: RootComponentAllocationPhase,
664 pub creation: Option<RootComponentCreationEvidence>,
665 pub installation: Option<RootComponentInstallEvidence>,
666}
667
668#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
675pub struct RootComponentChildAllocationResponse {
676 pub operation_id: [u8; 32],
677 pub component: ComponentInstanceId,
678 pub parent_canister_id: Principal,
679 pub parent_role: CanisterRole,
680 pub child_role: CanisterRole,
681 pub child_kind: ComponentChildKind,
682 pub maximum_instances_per_parent: u32,
683 pub maximum_descendants: u32,
684 pub maximum_registry_bytes: u64,
685 pub reserved_against_registry: ComponentRegistryHead,
686 pub release_set: FleetSubnetRootReleaseSet,
687 pub phase: RootComponentAllocationPhase,
688 pub creation: Option<RootComponentCreationEvidence>,
689 pub installation: Option<RootComponentChildInstallEvidence>,
690}
691
692#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
699pub struct RootComponentSubtreeRemovalResponse {
700 pub operation_id: [u8; 32],
701 pub component: ComponentInstanceId,
702 pub target_canister_id: Principal,
703 pub target_parent_canister_id: Principal,
704 pub target_role: CanisterRole,
705 pub target_status: ComponentLifecycleStatus,
706 pub reserved_against_registry: ComponentRegistryHead,
707 pub traversal_steps: u32,
708 pub phase: RootComponentSubtreeRemovalPhase,
709}
710
711#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
718pub struct RootComponentChildCommitResponse {
719 pub allocation: RootComponentChildAllocationResponse,
720 pub registry: ComponentRegistryPartitionResponse,
721 pub directory: ComponentDirectoryHead,
722}
723
724#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
731pub struct RootComponentChildDirectoryPreparationResponse {
732 pub committed: RootComponentChildCommitResponse,
733 pub child: ComponentRuntimeStatusResponse,
734 pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
735 pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
736}
737
738#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
745pub struct RootComponentChildRuntimeActivationResponse {
746 pub committed: RootComponentChildCommitResponse,
747 pub child: ComponentRuntimeStatusResponse,
748}
749
750#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
757pub struct RootComponentChildMembershipActivationResponse {
758 pub committed: RootComponentChildCommitResponse,
759 pub registry: ComponentRegistryPartitionResponse,
760 pub directory: ComponentDirectoryHead,
761 pub child: ComponentRuntimeStatusResponse,
762}
763
764#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
771pub struct RootComponentCommitResponse {
772 pub allocation: RootComponentAllocationResponse,
773 pub registry: ComponentRegistryPartitionResponse,
774 pub directory: ComponentDirectoryHead,
775}
776
777#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
784pub struct RootComponentDirectoryPreparationResponse {
785 pub committed: RootComponentCommitResponse,
786 pub target: ComponentRuntimeStatusResponse,
787}
788
789#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796pub struct RootComponentRuntimeActivationResponse {
797 pub committed: RootComponentCommitResponse,
798 pub target: ComponentRuntimeStatusResponse,
799}
800
801#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
808pub struct RootComponentMembershipActivationResponse {
809 pub allocation: RootComponentAllocationResponse,
810 pub registry: ComponentRegistryPartitionResponse,
811 pub directory: ComponentDirectoryHead,
812 pub target: ComponentRuntimeStatusResponse,
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818 use crate::{
819 dto::root_store::RootStoreBootstrapRequest,
820 ids::{
821 AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
822 FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
823 },
824 };
825
826 #[test]
827 fn component_registry_contracts_round_trip_through_candid() {
828 let request = RootComponentRegistryPreparationRequest {
829 store_bootstrap: RootStoreBootstrapRequest {
830 manifest_payload_size_bytes: 128,
831 },
832 expected_fleet_registry: FleetRegistryVersion {
833 authority: fleet_registry_authority(),
834 revision: 4,
835 content_hash: [5; 32],
836 },
837 };
838 let response = RootComponentRegistryStatusResponse {
839 fleet_subnet_root: Principal::from_slice(&[6; 29]),
840 prepared_against_registry: request.expected_fleet_registry.clone(),
841 release_set: FleetSubnetRootReleaseSet {
842 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
843 [7; 32],
844 )),
845 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
846 },
847 component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
848 next_allocation_sequence: 1,
849 reserved_component_instances: 0,
850 committed_component_instances: 0,
851 managed_descendants: 0,
852 known_created_component_canisters: 0,
853 encoded_bytes: 0,
854 initial_inventory: Some(RootComponentInitialInventoryStatus {
855 fleet_activation_operation_id: [10; 32],
856 component_count: 0,
857 inventory_hash: [11; 32],
858 sealed_at_ns: 12,
859 directories_converged: true,
860 root_runtime_activated: true,
861 }),
862 };
863 let allocation = RootComponentAllocationResponse {
864 operation_id: [10; 32],
865 allocation_sequence: 1,
866 component: ComponentInstanceId::from_generated_bytes([11; 32]),
867 component_spec: "projects".parse().expect("Component Spec ID"),
868 spec_hash: [12; 32],
869 role: CanisterRole::new("project_hub"),
870 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
871 caller: Principal::from_slice(&[13; 29]),
872 },
873 release_set: response.release_set,
874 phase: RootComponentAllocationPhase::Reserved,
875 creation: None,
876 installation: None,
877 };
878 let created = RootComponentAllocationResponse {
879 phase: RootComponentAllocationPhase::Created,
880 creation: Some(RootComponentCreationEvidence {
881 wasm_store: Principal::from_slice(&[14; 29]),
882 payload_hash: [15; 32],
883 payload_size_bytes: 4_096,
884 initial_cycles: Cycles::new(5_000_000_000_000),
885 controller: Principal::from_slice(&[6; 29]),
886 canister: Some(Principal::from_slice(&[16; 29])),
887 }),
888 installation: None,
889 ..allocation.clone()
890 };
891 let request_bytes = candid::encode_one(&request).expect("encode request");
892 let response_bytes = candid::encode_one(&response).expect("encode response");
893 let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
894 let created_bytes = candid::encode_one(&created).expect("encode created allocation");
895
896 assert_eq!(
897 candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
898 .expect("decode request"),
899 request
900 );
901 assert_eq!(
902 candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
903 .expect("decode response"),
904 response
905 );
906 assert_eq!(
907 candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
908 .expect("decode allocation"),
909 allocation
910 );
911 assert_eq!(
912 candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
913 .expect("decode created allocation"),
914 created
915 );
916 }
917
918 #[test]
919 fn component_commit_response_round_trips_through_candid() {
920 let root = Principal::from_slice(&[6; 29]);
921 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
922 let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
923 let release_set = FleetSubnetRootReleaseSet {
924 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
925 [7; 32],
926 )),
927 manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
928 };
929 let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
930 caller: Principal::from_slice(&[13; 29]),
931 };
932 let binding = ComponentBinding {
933 authority: fleet_registry_authority(),
934 component,
935 component_spec: component_spec.clone(),
936 spec_hash: [12; 32],
937 role: CanisterRole::new("project_hub"),
938 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
939 fleet_subnet_root: root,
940 canister_id: Principal::from_slice(&[16; 29]),
941 };
942 let head = ComponentRegistryHead {
943 component,
944 revision: 1,
945 content_hash: [18; 32],
946 };
947 let committed = RootComponentCommitResponse {
948 allocation: RootComponentAllocationResponse {
949 operation_id: [10; 32],
950 allocation_sequence: 1,
951 component,
952 component_spec,
953 spec_hash: binding.spec_hash,
954 role: binding.role.clone(),
955 provisioning_origin: provisioning_origin.clone(),
956 release_set,
957 phase: RootComponentAllocationPhase::Committed,
958 creation: Some(RootComponentCreationEvidence {
959 wasm_store: Principal::from_slice(&[14; 29]),
960 payload_hash: [15; 32],
961 payload_size_bytes: 4_096,
962 initial_cycles: Cycles::new(5_000_000_000_000),
963 controller: root,
964 canister: Some(binding.canister_id),
965 }),
966 installation: Some(RootComponentInstallEvidence {
967 raw_module_hash: [20; 32],
968 chunk_hashes: vec![vec![21; 32]],
969 binding: binding.clone(),
970 }),
971 },
972 registry: ComponentRegistryPartitionResponse {
973 head: head.clone(),
974 binding: binding.clone(),
975 provisioning_origin,
976 release_set,
977 status: ComponentLifecycleStatus::Prepared,
978 reserved_descendants: 0,
979 committed_descendants: 0,
980 encoded_bytes: 2_048,
981 },
982 directory: ComponentDirectoryHead {
983 provenance: ComponentDirectoryProvenance {
984 component: binding,
985 source_fleet_subnet_root: root,
986 component_registry_revision: head.revision,
987 component_registry_content_hash: head.content_hash,
988 synchronized_at_ns: 19,
989 },
990 descendant_count: 0,
991 },
992 };
993 let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
994
995 assert_eq!(
996 candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
997 .expect("decode committed allocation"),
998 committed
999 );
1000 }
1001
1002 #[test]
1003 fn component_directory_page_contracts_round_trip_through_candid() {
1004 let root = Principal::from_slice(&[6; 29]);
1005 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1006 let binding = ComponentBinding {
1007 authority: fleet_registry_authority(),
1008 component,
1009 component_spec: "projects".parse().expect("Component Spec ID"),
1010 spec_hash: [12; 32],
1011 role: CanisterRole::new("project_hub"),
1012 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1013 fleet_subnet_root: root,
1014 canister_id: Principal::from_slice(&[16; 29]),
1015 };
1016 let directory = ComponentDirectoryHead {
1017 provenance: ComponentDirectoryProvenance {
1018 component: binding.clone(),
1019 source_fleet_subnet_root: root,
1020 component_registry_revision: 3,
1021 component_registry_content_hash: [18; 32],
1022 synchronized_at_ns: 19,
1023 },
1024 descendant_count: 1,
1025 };
1026 let request = ComponentDirectoryPageRequest {
1027 directory: directory.clone(),
1028 parent_canister_id: Some(binding.canister_id),
1029 role: Some(CanisterRole::new("project_instance")),
1030 status: Some(ComponentLifecycleStatus::Active),
1031 cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1032 limit: 50,
1033 };
1034 let response = ComponentDirectoryPageResponse {
1035 directory,
1036 entries: vec![ComponentDirectoryChildEntry {
1037 binding: ComponentChildBinding {
1038 component: binding.clone(),
1039 parent_canister_id: binding.canister_id,
1040 role: CanisterRole::new("project_instance"),
1041 canister_id: Principal::from_slice(&[21; 29]),
1042 },
1043 kind: ComponentChildKind::Instance,
1044 installed_artifact_hash: [22; 32],
1045 status: ComponentLifecycleStatus::Active,
1046 }],
1047 next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1048 };
1049 let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1050 let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1051
1052 assert_eq!(
1053 candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1054 .expect("decode Directory page request"),
1055 request
1056 );
1057 assert_eq!(
1058 candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1059 .expect("decode Directory page response"),
1060 response
1061 );
1062 }
1063
1064 fn fleet_registry_authority() -> FleetRegistryAuthority {
1065 FleetRegistryAuthority {
1066 binding: FleetCoordinatorBinding {
1067 fleet: crate::ids::FleetBinding {
1068 fleet: FleetKey {
1069 canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1070 fleet_id: FleetId::from_generated_bytes([1; 32]),
1071 },
1072 app: AppId::from("toko"),
1073 },
1074 coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1075 coordinator: Principal::from_slice(&[3; 29]),
1076 },
1077 epoch: 1,
1078 }
1079 }
1080
1081 #[test]
1082 fn component_creation_request_round_trips_through_candid() {
1083 let request = RootComponentCreationRequest {
1084 operation_id: [10; 32],
1085 };
1086 let bytes = candid::encode_one(request).expect("encode creation request");
1087
1088 assert_eq!(
1089 candid::decode_one::<RootComponentCreationRequest>(&bytes)
1090 .expect("decode creation request"),
1091 request
1092 );
1093 }
1094
1095 #[test]
1096 fn component_subtree_removal_contracts_round_trip_through_candid() {
1097 let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1098 let registry = ComponentRegistryHead {
1099 component,
1100 revision: 7,
1101 content_hash: [42; 32],
1102 };
1103 let request = RootComponentSubtreeRemovalRequest {
1104 operation_id: [43; 32],
1105 component,
1106 target_canister_id: Principal::from_slice(&[44; 29]),
1107 expected_registry: registry.clone(),
1108 };
1109 let status_request = RootComponentSubtreeRemovalStatusRequest {
1110 operation_id: request.operation_id,
1111 component,
1112 };
1113 let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1114 operation_id: request.operation_id,
1115 component,
1116 expected_traversal_steps: 1,
1117 };
1118 let response = RootComponentSubtreeRemovalResponse {
1119 operation_id: request.operation_id,
1120 component,
1121 target_canister_id: request.target_canister_id,
1122 target_parent_canister_id: Principal::from_slice(&[45; 29]),
1123 target_role: CanisterRole::new("project_instance"),
1124 target_status: ComponentLifecycleStatus::Active,
1125 reserved_against_registry: registry,
1126 traversal_steps: 2,
1127 phase: RootComponentSubtreeRemovalPhase::LeafSelected(
1128 RootComponentSubtreeRemovalNode {
1129 canister_id: Principal::from_slice(&[46; 29]),
1130 parent_canister_id: request.target_canister_id,
1131 role: CanisterRole::new("project_ledger"),
1132 kind: ComponentChildKind::Singleton,
1133 installed_artifact_hash: [47; 32],
1134 status: ComponentLifecycleStatus::Active,
1135 },
1136 ),
1137 };
1138
1139 let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1140 let advance_bytes =
1141 candid::encode_one(advance_request).expect("encode subtree removal advance request");
1142 let status_bytes =
1143 candid::encode_one(status_request).expect("encode subtree removal status request");
1144 let response_bytes =
1145 candid::encode_one(&response).expect("encode subtree removal response");
1146
1147 assert_eq!(
1148 candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1149 .expect("decode subtree removal request"),
1150 request
1151 );
1152 assert_eq!(
1153 candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1154 .expect("decode subtree removal advance request"),
1155 advance_request
1156 );
1157 assert_eq!(
1158 candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1159 .expect("decode subtree removal status request"),
1160 status_request
1161 );
1162 assert_eq!(
1163 candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1164 .expect("decode subtree removal response"),
1165 response
1166 );
1167 }
1168
1169 #[test]
1170 #[expect(
1171 clippy::too_many_lines,
1172 reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1173 )]
1174 fn component_child_lifecycle_contracts_round_trip_through_candid() {
1175 let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1176 let registry = ComponentRegistryHead {
1177 component,
1178 revision: 2,
1179 content_hash: [12; 32],
1180 };
1181 let request = RootComponentChildAllocationRequest {
1182 operation_id: [13; 32],
1183 component,
1184 expected_registry: registry.clone(),
1185 child_role: CanisterRole::new("project_instance"),
1186 };
1187 let status_request = RootComponentChildAllocationStatusRequest {
1188 operation_id: request.operation_id,
1189 component,
1190 };
1191 let creation_request = RootComponentChildCreationRequest {
1192 operation_id: request.operation_id,
1193 component,
1194 };
1195 let install_request = RootComponentChildInstallRequest {
1196 operation_id: request.operation_id,
1197 component,
1198 };
1199 let commit_request = RootComponentChildCommitRequest {
1200 operation_id: request.operation_id,
1201 component,
1202 };
1203 let directory_request = RootComponentChildDirectoryPreparationRequest {
1204 operation_id: request.operation_id,
1205 component,
1206 };
1207 let activation_request = RootComponentChildRuntimeActivationRequest {
1208 operation_id: request.operation_id,
1209 component,
1210 };
1211 let membership_request = RootComponentChildMembershipActivationRequest {
1212 operation_id: request.operation_id,
1213 component,
1214 };
1215 let root = Principal::from_slice(&[17; 29]);
1216 let parent = Principal::from_slice(&[14; 29]);
1217 let child = Principal::from_slice(&[18; 29]);
1218 let child_binding = ComponentChildBinding {
1219 component: ComponentBinding {
1220 authority: fleet_registry_authority(),
1221 component,
1222 component_spec: "projects".parse().expect("Component Spec"),
1223 spec_hash: [19; 32],
1224 role: CanisterRole::new("project_hub"),
1225 placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1226 fleet_subnet_root: root,
1227 canister_id: parent,
1228 },
1229 parent_canister_id: parent,
1230 role: request.child_role.clone(),
1231 canister_id: child,
1232 };
1233 let response = RootComponentChildAllocationResponse {
1234 operation_id: request.operation_id,
1235 component,
1236 parent_canister_id: parent,
1237 parent_role: CanisterRole::new("project_hub"),
1238 child_role: request.child_role.clone(),
1239 child_kind: ComponentChildKind::Instance,
1240 maximum_instances_per_parent: 10_000,
1241 maximum_descendants: 20_000,
1242 maximum_registry_bytes: 16_777_216,
1243 reserved_against_registry: registry,
1244 release_set: FleetSubnetRootReleaseSet {
1245 release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1246 [15; 32],
1247 )),
1248 manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1249 },
1250 phase: RootComponentAllocationPhase::Verified,
1251 creation: Some(RootComponentCreationEvidence {
1252 wasm_store: Principal::from_slice(&[21; 29]),
1253 payload_hash: [22; 32],
1254 payload_size_bytes: 4_096,
1255 initial_cycles: Cycles::new(5_000_000_000_000),
1256 controller: root,
1257 canister: Some(child),
1258 }),
1259 installation: Some(RootComponentChildInstallEvidence {
1260 raw_module_hash: [23; 32],
1261 chunk_hashes: vec![vec![24; 32]],
1262 binding: child_binding.clone(),
1263 }),
1264 };
1265 let commit_response = RootComponentChildCommitResponse {
1266 allocation: response.clone(),
1267 registry: ComponentRegistryPartitionResponse {
1268 head: ComponentRegistryHead {
1269 component,
1270 revision: 3,
1271 content_hash: [25; 32],
1272 },
1273 binding: child_binding.component.clone(),
1274 provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1275 caller: Principal::from_slice(&[26; 29]),
1276 },
1277 release_set: response.release_set,
1278 status: ComponentLifecycleStatus::Active,
1279 reserved_descendants: 0,
1280 committed_descendants: 1,
1281 encoded_bytes: 8_192,
1282 },
1283 directory: ComponentDirectoryHead {
1284 provenance: ComponentDirectoryProvenance {
1285 component: child_binding.component.clone(),
1286 source_fleet_subnet_root: root,
1287 component_registry_revision: 3,
1288 component_registry_content_hash: [25; 32],
1289 synchronized_at_ns: 27,
1290 },
1291 descendant_count: 1,
1292 },
1293 };
1294 let runtime_authority = ComponentRuntimeDirectoryAuthority {
1295 fleet: FleetDirectorySnapshot {
1296 provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1297 registry: FleetRegistryVersion {
1298 authority: fleet_registry_authority(),
1299 revision: 4,
1300 content_hash: [28; 32],
1301 },
1302 source_fleet_subnet_root: root,
1303 },
1304 fleet_subnet_roots: vec![
1305 crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1306 placement_subnet: commit_response.registry.binding.placement_subnet,
1307 fleet_subnet_root: root,
1308 status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1309 },
1310 ],
1311 },
1312 component: commit_response.directory.clone(),
1313 };
1314 let activation = ComponentRuntimeActivationEvidence {
1315 directory_authority_hash: [29; 32],
1316 activated_at_ns: 30,
1317 };
1318 let directory_response = RootComponentChildDirectoryPreparationResponse {
1319 committed: commit_response.clone(),
1320 child: ComponentRuntimeStatusResponse {
1321 operation_id: request.operation_id,
1322 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1323 phase: ComponentRuntimePhase::DirectoryPrepared,
1324 authority: Some(runtime_authority.clone()),
1325 authority_hash: Some([31; 32]),
1326 activation: None,
1327 },
1328 owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1329 operation_id: [32; 32],
1330 binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1331 covered_authority: runtime_authority.clone(),
1332 covered_authority_hash: [31; 32],
1333 activation,
1334 },
1335 parent: None,
1336 };
1337 let activation_response = RootComponentChildRuntimeActivationResponse {
1338 committed: commit_response.clone(),
1339 child: ComponentRuntimeStatusResponse {
1340 operation_id: request.operation_id,
1341 binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1342 phase: ComponentRuntimePhase::Active,
1343 authority: Some(runtime_authority.clone()),
1344 authority_hash: Some([31; 32]),
1345 activation: Some(ComponentRuntimeActivationEvidence {
1346 directory_authority_hash: [31; 32],
1347 activated_at_ns: 33,
1348 }),
1349 },
1350 };
1351 let active_directory = ComponentDirectoryHead {
1352 provenance: ComponentDirectoryProvenance {
1353 component: child_binding.component.clone(),
1354 source_fleet_subnet_root: root,
1355 component_registry_revision: 4,
1356 component_registry_content_hash: [34; 32],
1357 synchronized_at_ns: 35,
1358 },
1359 descendant_count: 1,
1360 };
1361 let active_authority = ComponentRuntimeDirectoryAuthority {
1362 fleet: runtime_authority.fleet,
1363 component: active_directory.clone(),
1364 };
1365 let membership_response = RootComponentChildMembershipActivationResponse {
1366 committed: commit_response.clone(),
1367 registry: ComponentRegistryPartitionResponse {
1368 head: ComponentRegistryHead {
1369 component,
1370 revision: 4,
1371 content_hash: [34; 32],
1372 },
1373 binding: child_binding.component.clone(),
1374 provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1375 release_set: commit_response.registry.release_set,
1376 status: ComponentLifecycleStatus::Active,
1377 reserved_descendants: 0,
1378 committed_descendants: 1,
1379 encoded_bytes: 8_256,
1380 },
1381 directory: active_directory,
1382 child: ComponentRuntimeStatusResponse {
1383 operation_id: request.operation_id,
1384 binding: ManagedCanisterBinding::ComponentChild(child_binding),
1385 phase: ComponentRuntimePhase::Active,
1386 authority: Some(active_authority),
1387 authority_hash: Some([36; 32]),
1388 activation: Some(ComponentRuntimeActivationEvidence {
1389 directory_authority_hash: [31; 32],
1390 activated_at_ns: 33,
1391 }),
1392 },
1393 };
1394
1395 let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1396 let status_bytes =
1397 candid::encode_one(status_request).expect("encode child reservation status");
1398 let creation_bytes =
1399 candid::encode_one(creation_request).expect("encode child creation request");
1400 let install_bytes =
1401 candid::encode_one(install_request).expect("encode child install request");
1402 let response_bytes = candid::encode_one(&response).expect("encode child response");
1403 let commit_request_bytes =
1404 candid::encode_one(commit_request).expect("encode child commit request");
1405 let directory_request_bytes =
1406 candid::encode_one(directory_request).expect("encode child Directory request");
1407 let activation_request_bytes =
1408 candid::encode_one(activation_request).expect("encode child activation request");
1409 let membership_request_bytes =
1410 candid::encode_one(membership_request).expect("encode child membership request");
1411 let commit_response_bytes =
1412 candid::encode_one(&commit_response).expect("encode child commit response");
1413 let directory_response_bytes =
1414 candid::encode_one(&directory_response).expect("encode child Directory response");
1415 let activation_response_bytes =
1416 candid::encode_one(&activation_response).expect("encode child activation response");
1417 let membership_response_bytes =
1418 candid::encode_one(&membership_response).expect("encode child membership response");
1419
1420 assert_eq!(
1421 candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1422 .expect("decode child reservation"),
1423 request
1424 );
1425 assert_eq!(
1426 candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1427 .expect("decode child reservation status"),
1428 status_request
1429 );
1430 assert_eq!(
1431 candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1432 .expect("decode child creation request"),
1433 creation_request
1434 );
1435 assert_eq!(
1436 candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1437 .expect("decode child install request"),
1438 install_request
1439 );
1440 assert_eq!(
1441 candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1442 .expect("decode child response"),
1443 response
1444 );
1445 assert_eq!(
1446 candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1447 .expect("decode child commit request"),
1448 commit_request
1449 );
1450 assert_eq!(
1451 candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1452 &directory_request_bytes
1453 )
1454 .expect("decode child Directory request"),
1455 directory_request
1456 );
1457 assert_eq!(
1458 candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1459 .expect("decode child commit response"),
1460 commit_response
1461 );
1462 assert_eq!(
1463 candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1464 &directory_response_bytes
1465 )
1466 .expect("decode child Directory response"),
1467 directory_response
1468 );
1469 assert_eq!(
1470 candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1471 &activation_request_bytes
1472 )
1473 .expect("decode child activation request"),
1474 activation_request
1475 );
1476 assert_eq!(
1477 candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1478 &activation_response_bytes
1479 )
1480 .expect("decode child activation response"),
1481 activation_response
1482 );
1483 assert_eq!(
1484 candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1485 &membership_request_bytes
1486 )
1487 .expect("decode child membership request"),
1488 membership_request
1489 );
1490 assert_eq!(
1491 candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1492 &membership_response_bytes
1493 )
1494 .expect("decode child membership response"),
1495 membership_response
1496 );
1497 }
1498
1499 #[test]
1500 fn component_install_request_round_trips_through_candid() {
1501 let request = RootComponentInstallRequest {
1502 operation_id: [10; 32],
1503 };
1504 let bytes = candid::encode_one(request).expect("encode install request");
1505
1506 assert_eq!(
1507 candid::decode_one::<RootComponentInstallRequest>(&bytes)
1508 .expect("decode install request"),
1509 request
1510 );
1511 }
1512
1513 #[test]
1514 fn component_commit_request_round_trips_through_candid() {
1515 let request = RootComponentCommitRequest {
1516 operation_id: [10; 32],
1517 };
1518 let bytes = candid::encode_one(request).expect("encode commit request");
1519
1520 assert_eq!(
1521 candid::decode_one::<RootComponentCommitRequest>(&bytes)
1522 .expect("decode commit request"),
1523 request
1524 );
1525 }
1526
1527 #[test]
1528 fn component_runtime_activation_requests_round_trip_through_candid() {
1529 let root_request = RootComponentRuntimeActivationRequest {
1530 operation_id: [22; 32],
1531 };
1532 let target_request = ComponentRuntimeActivationRequest {
1533 operation_id: root_request.operation_id,
1534 directory_authority_hash: [23; 32],
1535 };
1536 let membership_request = RootComponentMembershipActivationRequest {
1537 operation_id: root_request.operation_id,
1538 };
1539 let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1540 let target_bytes =
1541 candid::encode_one(target_request).expect("encode target activation request");
1542 let membership_bytes =
1543 candid::encode_one(membership_request).expect("encode membership activation request");
1544
1545 assert_eq!(
1546 candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1547 .expect("decode root activation request"),
1548 root_request
1549 );
1550 assert_eq!(
1551 candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1552 .expect("decode target activation request"),
1553 target_request
1554 );
1555 assert_eq!(
1556 candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1557 .expect("decode membership activation request"),
1558 membership_request
1559 );
1560 }
1561}