Skip to main content

canic_core/dto/
component_registry.rs

1//! Module: dto::component_registry
2//!
3//! Responsibility: carry root-local Component Registry preparation and allocation evidence.
4//! Does not own: admission policy, stable mutation, artifact resolution, or lifecycle effects.
5//! Boundary: callers name intent and Spec while the root allocates identity under verified authority.
6
7use 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///
24/// RootComponentRegistryPreparationRequest
25///
26/// Exact authority required before an empty root-local Component Registry may be prepared.
27///
28
29#[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///
36/// RootComponentInitialInventoryStatus
37///
38/// Durable initial Component inventory sealed for one Fleet Subnet Root activation.
39///
40
41#[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///
52/// RootComponentRegistryStatusResponse
53///
54/// Compact durable Component Registry authority and current allocation counters.
55///
56
57#[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///
73/// RootComponentAllocationRequest
74///
75/// Controller command naming one idempotent top-level Component reservation intent.
76///
77
78#[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///
85/// RootComponentAllocationStatusRequest
86///
87/// Read-only lookup key for one durable top-level Component allocation operation.
88///
89
90#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
91pub struct RootComponentAllocationStatusRequest {
92    pub operation_id: [u8; 32],
93}
94
95///
96/// RootComponentChildAllocationRequest
97///
98/// Parent command naming one idempotent direct-child reservation intent.
99///
100
101#[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///
110/// RootComponentChildAllocationStatusRequest
111///
112/// Parent lookup key for one durable direct-child reservation.
113///
114
115#[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///
122/// RootComponentChildCreationRequest
123///
124/// Parent command continuing one already reserved direct-child operation.
125///
126
127#[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///
134/// RootComponentChildInstallRequest
135///
136/// Parent command installing and verifying one already created direct-child operation.
137///
138
139#[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///
146/// RootComponentChildCommitRequest
147///
148/// Parent command committing one already verified direct-child operation.
149///
150
151#[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///
158/// RootComponentChildDirectoryPreparationRequest
159///
160/// Parent command distributing one committed child's Directory and converging its affected members.
161///
162
163#[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///
170/// RootComponentChildRuntimeActivationRequest
171///
172/// Parent command activating one Directory-prepared direct-child runtime.
173///
174
175#[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///
182/// RootComponentChildMembershipActivationRequest
183///
184/// Parent command activating one runtime-active direct child's Registry membership.
185///
186
187#[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///
194/// RootComponentCreationRequest
195///
196/// Controller command continuing one already reserved top-level Component operation.
197///
198
199#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct RootComponentCreationRequest {
201    pub operation_id: [u8; 32],
202}
203
204///
205/// RootComponentInstallRequest
206///
207/// Controller command continuing one already created top-level Component operation.
208///
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
211pub struct RootComponentInstallRequest {
212    pub operation_id: [u8; 32],
213}
214
215///
216/// RootComponentCommitRequest
217///
218/// Controller command committing one already verified top-level Component operation.
219///
220
221#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
222pub struct RootComponentCommitRequest {
223    pub operation_id: [u8; 32],
224}
225
226///
227/// RootComponentDirectoryPreparationRequest
228///
229/// Controller command distributing exact Directories to one committed top-level Component.
230///
231
232#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
233pub struct RootComponentDirectoryPreparationRequest {
234    pub operation_id: [u8; 32],
235}
236
237///
238/// RootComponentRuntimeActivationRequest
239///
240/// Controller command activating one Directory-prepared top-level Component runtime.
241///
242
243#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
244pub struct RootComponentRuntimeActivationRequest {
245    pub operation_id: [u8; 32],
246}
247
248///
249/// RootComponentMembershipActivationRequest
250///
251/// Controller command activating one runtime-active Component's Registry membership.
252///
253
254#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
255pub struct RootComponentMembershipActivationRequest {
256    pub operation_id: [u8; 32],
257}
258
259///
260/// ComponentProvisioningOrigin
261///
262/// Authenticated causal authority retained with one top-level Component allocation.
263///
264
265#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
266pub enum ComponentProvisioningOrigin {
267    FleetAdministrator { caller: Principal },
268}
269
270///
271/// RootComponentAllocationPhase
272///
273/// Durable root-local progress of one top-level Component allocation operation.
274///
275
276#[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///
288/// ComponentLifecycleStatus
289///
290/// Root-owned runtime lifecycle state of one committed Component Registry member.
291///
292
293#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub enum ComponentLifecycleStatus {
295    Prepared,
296    Active,
297    Draining,
298    Removed,
299}
300
301///
302/// ComponentRegistryHead
303///
304/// Exact independently versioned authority of one Component Registry partition.
305///
306
307#[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///
315/// ComponentRegistryPartitionRequest
316///
317/// Read-only lookup key for one committed Component Registry partition.
318///
319
320#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct ComponentRegistryPartitionRequest {
322    pub component: ComponentInstanceId,
323}
324
325///
326/// ComponentRegistryPartitionResponse
327///
328/// Protected top-level row and independent head of one Component Registry partition.
329///
330
331#[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///
344/// ComponentDirectoryProvenance
345///
346/// Exact Component Registry authority from which one Component Directory is derived.
347///
348
349#[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///
359/// ComponentDirectoryHead
360///
361/// Compact independently versioned discovery projection for one Component tree.
362///
363
364#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct ComponentDirectoryHead {
366    pub provenance: ComponentDirectoryProvenance,
367    pub descendant_count: u32,
368}
369
370///
371/// ComponentDirectoryHeadRequest
372///
373/// Read-only lookup key for one committed Component Directory head.
374///
375
376#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
377pub struct ComponentDirectoryHeadRequest {
378    pub component: ComponentInstanceId,
379}
380
381///
382/// ComponentDirectoryPageCursor
383///
384/// Opaque revision- and filter-bound continuation for one bounded Directory page.
385///
386
387#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
389
390///
391/// ComponentDirectoryPageRequest
392///
393/// Bounded member query against one exact current Component Directory authority.
394///
395
396#[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///
407/// ComponentDirectoryChildEntry
408///
409/// One authoritative normalized child projected with its complete protected binding.
410///
411
412#[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///
421/// ComponentDirectoryPageResponse
422///
423/// One bounded caller-scoped page under the exact requested Directory head.
424///
425
426#[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///
434/// ComponentRuntimeDirectoryAuthority
435///
436/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
437///
438
439#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
440pub struct ComponentRuntimeDirectoryAuthority {
441    pub fleet: FleetDirectorySnapshot,
442    pub component: ComponentDirectoryHead,
443}
444
445///
446/// ComponentRuntimeDirectoryPreparationRequest
447///
448/// Root-issued exact Directory preparation command for one managed Component-tree node.
449///
450
451#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
452pub struct ComponentRuntimeDirectoryPreparationRequest {
453    pub operation_id: [u8; 32],
454    pub authority: ComponentRuntimeDirectoryAuthority,
455}
456
457///
458/// ComponentRuntimeDirectorySynchronizationRequest
459///
460/// Root-issued replacement of one active managed Component node's current Directory authority.
461///
462
463#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
464pub struct ComponentRuntimeDirectorySynchronizationRequest {
465    pub operation_id: [u8; 32],
466    pub authority: ComponentRuntimeDirectoryAuthority,
467}
468
469///
470/// ComponentRuntimePhase
471///
472/// Target-local progress from installation through Component runtime activation.
473///
474
475#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
476pub enum ComponentRuntimePhase {
477    AwaitingDirectory,
478    DirectoryPrepared,
479    Active,
480}
481
482///
483/// ComponentRuntimeActivationEvidence
484///
485/// Exact retained Directory authority under which one Component runtime became Active.
486///
487
488#[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///
495/// ComponentRuntimeActivationRequest
496///
497/// Root-issued exact activation command for one Directory-prepared managed Component node.
498///
499
500#[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///
507/// ComponentRuntimeStatusResponse
508///
509/// Independently observable target-local binding and exact retained Directory authority.
510///
511
512#[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///
523/// ComponentRuntimeDirectoryConvergenceEvidence
524///
525/// Stable root evidence that one active member covered at least the required Directory authority.
526///
527
528#[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///
538/// RootComponentCreationEvidence
539///
540/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
541///
542
543#[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///
554/// RootComponentInstallEvidence
555///
556/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
557///
558
559#[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///
567/// RootComponentChildInstallEvidence
568///
569/// Exact child module and immutable retained binding frozen before installation.
570///
571
572#[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///
580/// RootComponentAllocationResponse
581///
582/// Durable identity reservation returned identically for exact operation retry.
583///
584
585#[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///
601/// RootComponentChildAllocationResponse
602///
603/// Durable direct-child lifecycle progress returned identically for exact parent retry.
604///
605
606#[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///
625/// RootComponentChildCommitResponse
626///
627/// Exact committed child operation, authoritative Component Registry and next Directory head.
628///
629
630#[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///
638/// RootComponentChildDirectoryPreparationResponse
639///
640/// Exact child preparation plus stable bounded active-member Directory coverage.
641///
642
643#[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///
652/// RootComponentChildRuntimeActivationResponse
653///
654/// Exact child commitment plus independently observed Directory-bound runtime activation.
655///
656
657#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
658pub struct RootComponentChildRuntimeActivationResponse {
659    pub committed: RootComponentChildCommitResponse,
660    pub child: ComponentRuntimeStatusResponse,
661}
662
663///
664/// RootComponentChildMembershipActivationResponse
665///
666/// Original child commitment plus active Registry, Directory and target convergence evidence.
667///
668
669#[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///
678/// RootComponentCommitResponse
679///
680/// Exact committed allocation, authoritative Registry row and derived Directory head.
681///
682
683#[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///
691/// RootComponentDirectoryPreparationResponse
692///
693/// Exact root authority plus independently observed target-local Directory preparation.
694///
695
696#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
697pub struct RootComponentDirectoryPreparationResponse {
698    pub committed: RootComponentCommitResponse,
699    pub target: ComponentRuntimeStatusResponse,
700}
701
702///
703/// RootComponentRuntimeActivationResponse
704///
705/// Exact root authority plus independently observed target-local runtime activation.
706///
707
708#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
709pub struct RootComponentRuntimeActivationResponse {
710    pub committed: RootComponentCommitResponse,
711    pub target: ComponentRuntimeStatusResponse,
712}
713
714///
715/// RootComponentMembershipActivationResponse
716///
717/// Exact active Registry authority plus independently observed current target Directory.
718///
719
720#[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}