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