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