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    dto::{
10        fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
11        root_store::RootStoreBootstrapRequest,
12    },
13    ids::{
14        CanisterRole, ComponentBinding, ComponentInstanceId, ComponentSpecId,
15        ComponentTopologyDigest, FleetSubnetRootReleaseSet, ManagedCanisterBinding,
16    },
17};
18use candid::{CandidType, Principal};
19use serde::{Deserialize, Serialize};
20
21///
22/// RootComponentRegistryPreparationRequest
23///
24/// Exact authority required before an empty root-local Component Registry may be prepared.
25///
26
27#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
28pub struct RootComponentRegistryPreparationRequest {
29    pub store_bootstrap: RootStoreBootstrapRequest,
30    pub expected_fleet_registry: FleetRegistryVersion,
31}
32
33///
34/// RootComponentInitialInventoryStatus
35///
36/// Durable initial Component inventory sealed for one Fleet Subnet Root activation.
37///
38
39#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
40pub struct RootComponentInitialInventoryStatus {
41    pub fleet_activation_operation_id: [u8; 32],
42    pub component_count: u32,
43    pub inventory_hash: [u8; 32],
44    pub sealed_at_ns: u64,
45    pub directories_converged: bool,
46    pub root_runtime_activated: bool,
47}
48
49///
50/// RootComponentRegistryStatusResponse
51///
52/// Compact durable Component Registry authority and current allocation counters.
53///
54
55#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
56pub struct RootComponentRegistryStatusResponse {
57    pub fleet_subnet_root: Principal,
58    pub prepared_against_registry: FleetRegistryVersion,
59    pub release_set: FleetSubnetRootReleaseSet,
60    pub component_topology_digest: ComponentTopologyDigest,
61    pub next_allocation_sequence: u64,
62    pub reserved_component_instances: u32,
63    pub committed_component_instances: u32,
64    pub managed_descendants: u32,
65    pub encoded_bytes: u64,
66    pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
67}
68
69///
70/// RootComponentAllocationRequest
71///
72/// Controller command naming one idempotent top-level Component reservation intent.
73///
74
75#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
76pub struct RootComponentAllocationRequest {
77    pub operation_id: [u8; 32],
78    pub component_spec: ComponentSpecId,
79}
80
81///
82/// RootComponentAllocationStatusRequest
83///
84/// Read-only lookup key for one durable top-level Component allocation operation.
85///
86
87#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
88pub struct RootComponentAllocationStatusRequest {
89    pub operation_id: [u8; 32],
90}
91
92///
93/// RootComponentCreationRequest
94///
95/// Controller command continuing one already reserved top-level Component operation.
96///
97
98#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
99pub struct RootComponentCreationRequest {
100    pub operation_id: [u8; 32],
101}
102
103///
104/// RootComponentInstallRequest
105///
106/// Controller command continuing one already created top-level Component operation.
107///
108
109#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
110pub struct RootComponentInstallRequest {
111    pub operation_id: [u8; 32],
112}
113
114///
115/// RootComponentCommitRequest
116///
117/// Controller command committing one already verified top-level Component operation.
118///
119
120#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
121pub struct RootComponentCommitRequest {
122    pub operation_id: [u8; 32],
123}
124
125///
126/// RootComponentDirectoryPreparationRequest
127///
128/// Controller command distributing exact Directories to one committed top-level Component.
129///
130
131#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
132pub struct RootComponentDirectoryPreparationRequest {
133    pub operation_id: [u8; 32],
134}
135
136///
137/// RootComponentRuntimeActivationRequest
138///
139/// Controller command activating one Directory-prepared top-level Component runtime.
140///
141
142#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
143pub struct RootComponentRuntimeActivationRequest {
144    pub operation_id: [u8; 32],
145}
146
147///
148/// RootComponentMembershipActivationRequest
149///
150/// Controller command activating one runtime-active Component's Registry membership.
151///
152
153#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
154pub struct RootComponentMembershipActivationRequest {
155    pub operation_id: [u8; 32],
156}
157
158///
159/// ComponentProvisioningOrigin
160///
161/// Authenticated causal authority retained with one top-level Component allocation.
162///
163
164#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
165pub enum ComponentProvisioningOrigin {
166    FleetAdministrator { caller: Principal },
167}
168
169///
170/// RootComponentAllocationPhase
171///
172/// Durable root-local progress of one top-level Component allocation operation.
173///
174
175#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub enum RootComponentAllocationPhase {
177    Reserved,
178    CreationIntent,
179    Created,
180    InstallIntent,
181    Installed,
182    Verified,
183    Committed,
184}
185
186///
187/// ComponentLifecycleStatus
188///
189/// Root-owned runtime lifecycle state of one committed Component Registry member.
190///
191
192#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
193pub enum ComponentLifecycleStatus {
194    Prepared,
195    Active,
196    Draining,
197    Removed,
198}
199
200///
201/// ComponentRegistryHead
202///
203/// Exact independently versioned authority of one Component Registry partition.
204///
205
206#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
207pub struct ComponentRegistryHead {
208    pub component: ComponentInstanceId,
209    pub revision: u64,
210    pub content_hash: [u8; 32],
211}
212
213///
214/// ComponentRegistryPartitionRequest
215///
216/// Read-only lookup key for one committed Component Registry partition.
217///
218
219#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
220pub struct ComponentRegistryPartitionRequest {
221    pub component: ComponentInstanceId,
222}
223
224///
225/// ComponentRegistryPartitionResponse
226///
227/// Protected top-level row and independent head of one Component Registry partition.
228///
229
230#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
231pub struct ComponentRegistryPartitionResponse {
232    pub head: ComponentRegistryHead,
233    pub binding: ComponentBinding,
234    pub provisioning_origin: ComponentProvisioningOrigin,
235    pub release_set: FleetSubnetRootReleaseSet,
236    pub status: ComponentLifecycleStatus,
237    pub encoded_bytes: u64,
238}
239
240///
241/// ComponentDirectoryProvenance
242///
243/// Exact Component Registry authority from which one Component Directory is derived.
244///
245
246#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
247pub struct ComponentDirectoryProvenance {
248    pub component: ComponentBinding,
249    pub source_fleet_subnet_root: Principal,
250    pub component_registry_revision: u64,
251    pub component_registry_content_hash: [u8; 32],
252    pub synchronized_at_ns: u64,
253}
254
255///
256/// ComponentDirectoryHead
257///
258/// Compact independently versioned discovery projection for one Component tree.
259///
260
261#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
262pub struct ComponentDirectoryHead {
263    pub provenance: ComponentDirectoryProvenance,
264    pub descendant_count: u32,
265}
266
267///
268/// ComponentDirectoryHeadRequest
269///
270/// Read-only lookup key for one committed Component Directory head.
271///
272
273#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
274pub struct ComponentDirectoryHeadRequest {
275    pub component: ComponentInstanceId,
276}
277
278///
279/// ComponentRuntimeDirectoryAuthority
280///
281/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
282///
283
284#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
285pub struct ComponentRuntimeDirectoryAuthority {
286    pub fleet: FleetDirectorySnapshot,
287    pub component: ComponentDirectoryHead,
288}
289
290///
291/// ComponentRuntimeDirectoryPreparationRequest
292///
293/// Root-issued exact Directory preparation command for one managed Component-tree node.
294///
295
296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct ComponentRuntimeDirectoryPreparationRequest {
298    pub operation_id: [u8; 32],
299    pub authority: ComponentRuntimeDirectoryAuthority,
300}
301
302///
303/// ComponentRuntimeDirectorySynchronizationRequest
304///
305/// Root-issued replacement of one active managed Component node's current Directory authority.
306///
307
308#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
309pub struct ComponentRuntimeDirectorySynchronizationRequest {
310    pub operation_id: [u8; 32],
311    pub authority: ComponentRuntimeDirectoryAuthority,
312}
313
314///
315/// ComponentRuntimePhase
316///
317/// Target-local progress from installation through Component runtime activation.
318///
319
320#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub enum ComponentRuntimePhase {
322    AwaitingDirectory,
323    DirectoryPrepared,
324    Active,
325}
326
327///
328/// ComponentRuntimeActivationEvidence
329///
330/// Exact retained Directory authority under which one Component runtime became Active.
331///
332
333#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
334pub struct ComponentRuntimeActivationEvidence {
335    pub directory_authority_hash: [u8; 32],
336    pub activated_at_ns: u64,
337}
338
339///
340/// ComponentRuntimeActivationRequest
341///
342/// Root-issued exact activation command for one Directory-prepared managed Component node.
343///
344
345#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
346pub struct ComponentRuntimeActivationRequest {
347    pub operation_id: [u8; 32],
348    pub directory_authority_hash: [u8; 32],
349}
350
351///
352/// ComponentRuntimeStatusResponse
353///
354/// Independently observable target-local binding and exact retained Directory authority.
355///
356
357#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
358pub struct ComponentRuntimeStatusResponse {
359    pub operation_id: [u8; 32],
360    pub binding: ManagedCanisterBinding,
361    pub phase: ComponentRuntimePhase,
362    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
363    pub authority_hash: Option<[u8; 32]>,
364    pub activation: Option<ComponentRuntimeActivationEvidence>,
365}
366
367///
368/// RootComponentCreationEvidence
369///
370/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
371///
372
373#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
374pub struct RootComponentCreationEvidence {
375    pub wasm_store: Principal,
376    pub payload_hash: [u8; 32],
377    pub payload_size_bytes: u64,
378    pub initial_cycles: Cycles,
379    pub controller: Principal,
380    pub canister: Option<Principal>,
381}
382
383///
384/// RootComponentInstallEvidence
385///
386/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
387///
388
389#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
390pub struct RootComponentInstallEvidence {
391    pub raw_module_hash: [u8; 32],
392    pub chunk_hashes: Vec<Vec<u8>>,
393    pub binding: ComponentBinding,
394}
395
396///
397/// RootComponentAllocationResponse
398///
399/// Durable identity reservation returned identically for exact operation retry.
400///
401
402#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
403pub struct RootComponentAllocationResponse {
404    pub operation_id: [u8; 32],
405    pub allocation_sequence: u64,
406    pub component: ComponentInstanceId,
407    pub component_spec: ComponentSpecId,
408    pub spec_hash: [u8; 32],
409    pub role: CanisterRole,
410    pub provisioning_origin: ComponentProvisioningOrigin,
411    pub release_set: FleetSubnetRootReleaseSet,
412    pub phase: RootComponentAllocationPhase,
413    pub creation: Option<RootComponentCreationEvidence>,
414    pub installation: Option<RootComponentInstallEvidence>,
415}
416
417///
418/// RootComponentCommitResponse
419///
420/// Exact committed allocation, authoritative Registry row and derived Directory head.
421///
422
423#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
424pub struct RootComponentCommitResponse {
425    pub allocation: RootComponentAllocationResponse,
426    pub registry: ComponentRegistryPartitionResponse,
427    pub directory: ComponentDirectoryHead,
428}
429
430///
431/// RootComponentDirectoryPreparationResponse
432///
433/// Exact root authority plus independently observed target-local Directory preparation.
434///
435
436#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
437pub struct RootComponentDirectoryPreparationResponse {
438    pub committed: RootComponentCommitResponse,
439    pub target: ComponentRuntimeStatusResponse,
440}
441
442///
443/// RootComponentRuntimeActivationResponse
444///
445/// Exact root authority plus independently observed target-local runtime activation.
446///
447
448#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
449pub struct RootComponentRuntimeActivationResponse {
450    pub committed: RootComponentCommitResponse,
451    pub target: ComponentRuntimeStatusResponse,
452}
453
454///
455/// RootComponentMembershipActivationResponse
456///
457/// Exact active Registry authority plus independently observed current target Directory.
458///
459
460#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
461pub struct RootComponentMembershipActivationResponse {
462    pub allocation: RootComponentAllocationResponse,
463    pub registry: ComponentRegistryPartitionResponse,
464    pub directory: ComponentDirectoryHead,
465    pub target: ComponentRuntimeStatusResponse,
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::{
472        dto::root_store::RootStoreBootstrapRequest,
473        ids::{
474            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
475            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
476        },
477    };
478
479    #[test]
480    fn component_registry_contracts_round_trip_through_candid() {
481        let request = RootComponentRegistryPreparationRequest {
482            store_bootstrap: RootStoreBootstrapRequest {
483                manifest_payload_size_bytes: 128,
484            },
485            expected_fleet_registry: FleetRegistryVersion {
486                authority: fleet_registry_authority(),
487                revision: 4,
488                content_hash: [5; 32],
489            },
490        };
491        let response = RootComponentRegistryStatusResponse {
492            fleet_subnet_root: Principal::from_slice(&[6; 29]),
493            prepared_against_registry: request.expected_fleet_registry.clone(),
494            release_set: FleetSubnetRootReleaseSet {
495                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
496                    [7; 32],
497                )),
498                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
499            },
500            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
501            next_allocation_sequence: 1,
502            reserved_component_instances: 0,
503            committed_component_instances: 0,
504            managed_descendants: 0,
505            encoded_bytes: 0,
506            initial_inventory: Some(RootComponentInitialInventoryStatus {
507                fleet_activation_operation_id: [10; 32],
508                component_count: 0,
509                inventory_hash: [11; 32],
510                sealed_at_ns: 12,
511                directories_converged: true,
512                root_runtime_activated: true,
513            }),
514        };
515        let allocation = RootComponentAllocationResponse {
516            operation_id: [10; 32],
517            allocation_sequence: 1,
518            component: ComponentInstanceId::from_generated_bytes([11; 32]),
519            component_spec: "projects".parse().expect("Component Spec ID"),
520            spec_hash: [12; 32],
521            role: CanisterRole::new("project_hub"),
522            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
523                caller: Principal::from_slice(&[13; 29]),
524            },
525            release_set: response.release_set,
526            phase: RootComponentAllocationPhase::Reserved,
527            creation: None,
528            installation: None,
529        };
530        let created = RootComponentAllocationResponse {
531            phase: RootComponentAllocationPhase::Created,
532            creation: Some(RootComponentCreationEvidence {
533                wasm_store: Principal::from_slice(&[14; 29]),
534                payload_hash: [15; 32],
535                payload_size_bytes: 4_096,
536                initial_cycles: Cycles::new(5_000_000_000_000),
537                controller: Principal::from_slice(&[6; 29]),
538                canister: Some(Principal::from_slice(&[16; 29])),
539            }),
540            installation: None,
541            ..allocation.clone()
542        };
543        let request_bytes = candid::encode_one(&request).expect("encode request");
544        let response_bytes = candid::encode_one(&response).expect("encode response");
545        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
546        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
547
548        assert_eq!(
549            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
550                .expect("decode request"),
551            request
552        );
553        assert_eq!(
554            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
555                .expect("decode response"),
556            response
557        );
558        assert_eq!(
559            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
560                .expect("decode allocation"),
561            allocation
562        );
563        assert_eq!(
564            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
565                .expect("decode created allocation"),
566            created
567        );
568    }
569
570    #[test]
571    fn component_commit_response_round_trips_through_candid() {
572        let root = Principal::from_slice(&[6; 29]);
573        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
574        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
575        let release_set = FleetSubnetRootReleaseSet {
576            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
577                [7; 32],
578            )),
579            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
580        };
581        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
582            caller: Principal::from_slice(&[13; 29]),
583        };
584        let binding = ComponentBinding {
585            authority: fleet_registry_authority(),
586            component,
587            component_spec: component_spec.clone(),
588            spec_hash: [12; 32],
589            role: CanisterRole::new("project_hub"),
590            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
591            fleet_subnet_root: root,
592            canister_id: Principal::from_slice(&[16; 29]),
593        };
594        let head = ComponentRegistryHead {
595            component,
596            revision: 1,
597            content_hash: [18; 32],
598        };
599        let committed = RootComponentCommitResponse {
600            allocation: RootComponentAllocationResponse {
601                operation_id: [10; 32],
602                allocation_sequence: 1,
603                component,
604                component_spec,
605                spec_hash: binding.spec_hash,
606                role: binding.role.clone(),
607                provisioning_origin: provisioning_origin.clone(),
608                release_set,
609                phase: RootComponentAllocationPhase::Committed,
610                creation: Some(RootComponentCreationEvidence {
611                    wasm_store: Principal::from_slice(&[14; 29]),
612                    payload_hash: [15; 32],
613                    payload_size_bytes: 4_096,
614                    initial_cycles: Cycles::new(5_000_000_000_000),
615                    controller: root,
616                    canister: Some(binding.canister_id),
617                }),
618                installation: Some(RootComponentInstallEvidence {
619                    raw_module_hash: [20; 32],
620                    chunk_hashes: vec![vec![21; 32]],
621                    binding: binding.clone(),
622                }),
623            },
624            registry: ComponentRegistryPartitionResponse {
625                head: head.clone(),
626                binding: binding.clone(),
627                provisioning_origin,
628                release_set,
629                status: ComponentLifecycleStatus::Prepared,
630                encoded_bytes: 2_048,
631            },
632            directory: ComponentDirectoryHead {
633                provenance: ComponentDirectoryProvenance {
634                    component: binding,
635                    source_fleet_subnet_root: root,
636                    component_registry_revision: head.revision,
637                    component_registry_content_hash: head.content_hash,
638                    synchronized_at_ns: 19,
639                },
640                descendant_count: 0,
641            },
642        };
643        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
644
645        assert_eq!(
646            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
647                .expect("decode committed allocation"),
648            committed
649        );
650    }
651
652    fn fleet_registry_authority() -> FleetRegistryAuthority {
653        FleetRegistryAuthority {
654            binding: FleetCoordinatorBinding {
655                fleet: crate::ids::FleetBinding {
656                    fleet: FleetKey {
657                        canonical_network_id: CanonicalNetworkId::public_ic(),
658                        fleet_id: FleetId::from_generated_bytes([1; 32]),
659                    },
660                    app: AppId::from("toko"),
661                },
662                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
663                coordinator: Principal::from_slice(&[3; 29]),
664            },
665            epoch: 1,
666        }
667    }
668
669    #[test]
670    fn component_creation_request_round_trips_through_candid() {
671        let request = RootComponentCreationRequest {
672            operation_id: [10; 32],
673        };
674        let bytes = candid::encode_one(request).expect("encode creation request");
675
676        assert_eq!(
677            candid::decode_one::<RootComponentCreationRequest>(&bytes)
678                .expect("decode creation request"),
679            request
680        );
681    }
682
683    #[test]
684    fn component_install_request_round_trips_through_candid() {
685        let request = RootComponentInstallRequest {
686            operation_id: [10; 32],
687        };
688        let bytes = candid::encode_one(request).expect("encode install request");
689
690        assert_eq!(
691            candid::decode_one::<RootComponentInstallRequest>(&bytes)
692                .expect("decode install request"),
693            request
694        );
695    }
696
697    #[test]
698    fn component_commit_request_round_trips_through_candid() {
699        let request = RootComponentCommitRequest {
700            operation_id: [10; 32],
701        };
702        let bytes = candid::encode_one(request).expect("encode commit request");
703
704        assert_eq!(
705            candid::decode_one::<RootComponentCommitRequest>(&bytes)
706                .expect("decode commit request"),
707            request
708        );
709    }
710
711    #[test]
712    fn component_runtime_activation_requests_round_trip_through_candid() {
713        let root_request = RootComponentRuntimeActivationRequest {
714            operation_id: [22; 32],
715        };
716        let target_request = ComponentRuntimeActivationRequest {
717            operation_id: root_request.operation_id,
718            directory_authority_hash: [23; 32],
719        };
720        let membership_request = RootComponentMembershipActivationRequest {
721            operation_id: root_request.operation_id,
722        };
723        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
724        let target_bytes =
725            candid::encode_one(target_request).expect("encode target activation request");
726        let membership_bytes =
727            candid::encode_one(membership_request).expect("encode membership activation request");
728
729        assert_eq!(
730            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
731                .expect("decode root activation request"),
732            root_request
733        );
734        assert_eq!(
735            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
736                .expect("decode target activation request"),
737            target_request
738        );
739        assert_eq!(
740            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
741                .expect("decode membership activation request"),
742            membership_request
743        );
744    }
745}