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