Skip to main content

canic_core/dto/
component_registry.rs

1//! Module: dto::component_registry
2//!
3//! Responsibility: carry root-local Component Registry preparation and allocation evidence.
4//! Does not own: admission policy, stable mutation, artifact resolution, or lifecycle effects.
5//! Boundary: callers name intent and Spec while the root allocates identity under verified authority.
6
7use crate::{
8    cdk::types::Cycles,
9    config::schema::ComponentChildKind,
10    dto::{
11        fleet_registry::{FleetDirectorySnapshot, FleetRegistryVersion},
12        root_store::RootStoreBootstrapRequest,
13    },
14    ids::{
15        CanisterRole, ComponentBinding, ComponentChildBinding, ComponentInstanceId,
16        ComponentSpecId, ComponentTopologyDigest, FleetSubnetRootReleaseSet,
17        ManagedCanisterBinding,
18    },
19};
20use candid::{CandidType, Principal};
21use serde::{Deserialize, Serialize};
22
23///
24/// RootComponentRegistryPreparationRequest
25///
26/// Exact authority required before an empty root-local Component Registry may be prepared.
27///
28
29#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub struct RootComponentRegistryPreparationRequest {
31    pub store_bootstrap: RootStoreBootstrapRequest,
32    pub expected_fleet_registry: FleetRegistryVersion,
33}
34
35///
36/// RootComponentInitialInventoryStatus
37///
38/// Durable initial Component inventory sealed for one Fleet Subnet Root activation.
39///
40
41#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct RootComponentInitialInventoryStatus {
43    pub fleet_activation_operation_id: [u8; 32],
44    pub component_count: u32,
45    pub inventory_hash: [u8; 32],
46    pub sealed_at_ns: u64,
47    pub directories_converged: bool,
48    pub root_runtime_activated: bool,
49}
50
51///
52/// RootComponentRegistryStatusResponse
53///
54/// Compact durable Component Registry authority and current allocation counters.
55///
56
57#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
58pub struct RootComponentRegistryStatusResponse {
59    pub fleet_subnet_root: Principal,
60    pub prepared_against_registry: FleetRegistryVersion,
61    pub release_set: FleetSubnetRootReleaseSet,
62    pub component_topology_digest: ComponentTopologyDigest,
63    pub next_allocation_sequence: u64,
64    pub reserved_component_instances: u32,
65    pub committed_component_instances: u32,
66    pub managed_descendants: u32,
67    pub known_created_component_canisters: u32,
68    pub encoded_bytes: u64,
69    pub initial_inventory: Option<RootComponentInitialInventoryStatus>,
70}
71
72///
73/// RootComponentAllocationRequest
74///
75/// Controller command naming one idempotent top-level Component reservation intent.
76///
77
78#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct RootComponentAllocationRequest {
80    pub operation_id: [u8; 32],
81    pub component_spec: ComponentSpecId,
82}
83
84///
85/// RootComponentAllocationStatusRequest
86///
87/// Read-only lookup key for one durable top-level Component allocation operation.
88///
89
90#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
91pub struct RootComponentAllocationStatusRequest {
92    pub operation_id: [u8; 32],
93}
94
95///
96/// RootComponentChildAllocationRequest
97///
98/// Parent command naming one idempotent direct-child reservation intent.
99///
100
101#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
102pub struct RootComponentChildAllocationRequest {
103    pub operation_id: [u8; 32],
104    pub component: ComponentInstanceId,
105    pub expected_registry: ComponentRegistryHead,
106    pub child_role: CanisterRole,
107}
108
109///
110/// RootComponentChildAllocationStatusRequest
111///
112/// Parent lookup key for one durable direct-child reservation.
113///
114
115#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct RootComponentChildAllocationStatusRequest {
117    pub operation_id: [u8; 32],
118    pub component: ComponentInstanceId,
119}
120
121///
122/// RootComponentChildCreationRequest
123///
124/// Parent command continuing one already reserved direct-child operation.
125///
126
127#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentChildCreationRequest {
129    pub operation_id: [u8; 32],
130    pub component: ComponentInstanceId,
131}
132
133///
134/// RootComponentChildInstallRequest
135///
136/// Parent command installing and verifying one already created direct-child operation.
137///
138
139#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct RootComponentChildInstallRequest {
141    pub operation_id: [u8; 32],
142    pub component: ComponentInstanceId,
143}
144
145///
146/// RootComponentChildCommitRequest
147///
148/// Parent command committing one already verified direct-child operation.
149///
150
151#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152pub struct RootComponentChildCommitRequest {
153    pub operation_id: [u8; 32],
154    pub component: ComponentInstanceId,
155}
156
157///
158/// RootComponentChildDirectoryPreparationRequest
159///
160/// Parent command distributing one committed child's Directory and converging its affected members.
161///
162
163#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164pub struct RootComponentChildDirectoryPreparationRequest {
165    pub operation_id: [u8; 32],
166    pub component: ComponentInstanceId,
167}
168
169///
170/// RootComponentCreationRequest
171///
172/// Controller command continuing one already reserved top-level Component operation.
173///
174
175#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub struct RootComponentCreationRequest {
177    pub operation_id: [u8; 32],
178}
179
180///
181/// RootComponentInstallRequest
182///
183/// Controller command continuing one already created top-level Component operation.
184///
185
186#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187pub struct RootComponentInstallRequest {
188    pub operation_id: [u8; 32],
189}
190
191///
192/// RootComponentCommitRequest
193///
194/// Controller command committing one already verified top-level Component operation.
195///
196
197#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
198pub struct RootComponentCommitRequest {
199    pub operation_id: [u8; 32],
200}
201
202///
203/// RootComponentDirectoryPreparationRequest
204///
205/// Controller command distributing exact Directories to one committed top-level Component.
206///
207
208#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
209pub struct RootComponentDirectoryPreparationRequest {
210    pub operation_id: [u8; 32],
211}
212
213///
214/// RootComponentRuntimeActivationRequest
215///
216/// Controller command activating one Directory-prepared top-level Component runtime.
217///
218
219#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
220pub struct RootComponentRuntimeActivationRequest {
221    pub operation_id: [u8; 32],
222}
223
224///
225/// RootComponentMembershipActivationRequest
226///
227/// Controller command activating one runtime-active Component's Registry membership.
228///
229
230#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231pub struct RootComponentMembershipActivationRequest {
232    pub operation_id: [u8; 32],
233}
234
235///
236/// ComponentProvisioningOrigin
237///
238/// Authenticated causal authority retained with one top-level Component allocation.
239///
240
241#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
242pub enum ComponentProvisioningOrigin {
243    FleetAdministrator { caller: Principal },
244}
245
246///
247/// RootComponentAllocationPhase
248///
249/// Durable root-local progress of one top-level Component allocation operation.
250///
251
252#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
253pub enum RootComponentAllocationPhase {
254    Reserved,
255    CreationIntent,
256    Created,
257    InstallIntent,
258    Installed,
259    Verified,
260    Committed,
261}
262
263///
264/// ComponentLifecycleStatus
265///
266/// Root-owned runtime lifecycle state of one committed Component Registry member.
267///
268
269#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
270pub enum ComponentLifecycleStatus {
271    Prepared,
272    Active,
273    Draining,
274    Removed,
275}
276
277///
278/// ComponentRegistryHead
279///
280/// Exact independently versioned authority of one Component Registry partition.
281///
282
283#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
284pub struct ComponentRegistryHead {
285    pub component: ComponentInstanceId,
286    pub revision: u64,
287    pub content_hash: [u8; 32],
288}
289
290///
291/// ComponentRegistryPartitionRequest
292///
293/// Read-only lookup key for one committed Component Registry partition.
294///
295
296#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct ComponentRegistryPartitionRequest {
298    pub component: ComponentInstanceId,
299}
300
301///
302/// ComponentRegistryPartitionResponse
303///
304/// Protected top-level row and independent head of one Component Registry partition.
305///
306
307#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
308pub struct ComponentRegistryPartitionResponse {
309    pub head: ComponentRegistryHead,
310    pub binding: ComponentBinding,
311    pub provisioning_origin: ComponentProvisioningOrigin,
312    pub release_set: FleetSubnetRootReleaseSet,
313    pub status: ComponentLifecycleStatus,
314    pub reserved_descendants: u32,
315    pub committed_descendants: u32,
316    pub encoded_bytes: u64,
317}
318
319///
320/// ComponentDirectoryProvenance
321///
322/// Exact Component Registry authority from which one Component Directory is derived.
323///
324
325#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
326pub struct ComponentDirectoryProvenance {
327    pub component: ComponentBinding,
328    pub source_fleet_subnet_root: Principal,
329    pub component_registry_revision: u64,
330    pub component_registry_content_hash: [u8; 32],
331    pub synchronized_at_ns: u64,
332}
333
334///
335/// ComponentDirectoryHead
336///
337/// Compact independently versioned discovery projection for one Component tree.
338///
339
340#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
341pub struct ComponentDirectoryHead {
342    pub provenance: ComponentDirectoryProvenance,
343    pub descendant_count: u32,
344}
345
346///
347/// ComponentDirectoryHeadRequest
348///
349/// Read-only lookup key for one committed Component Directory head.
350///
351
352#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
353pub struct ComponentDirectoryHeadRequest {
354    pub component: ComponentInstanceId,
355}
356
357///
358/// ComponentRuntimeDirectoryAuthority
359///
360/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
361///
362
363#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
364pub struct ComponentRuntimeDirectoryAuthority {
365    pub fleet: FleetDirectorySnapshot,
366    pub component: ComponentDirectoryHead,
367}
368
369///
370/// ComponentRuntimeDirectoryPreparationRequest
371///
372/// Root-issued exact Directory preparation command for one managed Component-tree node.
373///
374
375#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRuntimeDirectoryPreparationRequest {
377    pub operation_id: [u8; 32],
378    pub authority: ComponentRuntimeDirectoryAuthority,
379}
380
381///
382/// ComponentRuntimeDirectorySynchronizationRequest
383///
384/// Root-issued replacement of one active managed Component node's current Directory authority.
385///
386
387#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentRuntimeDirectorySynchronizationRequest {
389    pub operation_id: [u8; 32],
390    pub authority: ComponentRuntimeDirectoryAuthority,
391}
392
393///
394/// ComponentRuntimePhase
395///
396/// Target-local progress from installation through Component runtime activation.
397///
398
399#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub enum ComponentRuntimePhase {
401    AwaitingDirectory,
402    DirectoryPrepared,
403    Active,
404}
405
406///
407/// ComponentRuntimeActivationEvidence
408///
409/// Exact retained Directory authority under which one Component runtime became Active.
410///
411
412#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct ComponentRuntimeActivationEvidence {
414    pub directory_authority_hash: [u8; 32],
415    pub activated_at_ns: u64,
416}
417
418///
419/// ComponentRuntimeActivationRequest
420///
421/// Root-issued exact activation command for one Directory-prepared managed Component node.
422///
423
424#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
425pub struct ComponentRuntimeActivationRequest {
426    pub operation_id: [u8; 32],
427    pub directory_authority_hash: [u8; 32],
428}
429
430///
431/// ComponentRuntimeStatusResponse
432///
433/// Independently observable target-local binding and exact retained Directory authority.
434///
435
436#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
437pub struct ComponentRuntimeStatusResponse {
438    pub operation_id: [u8; 32],
439    pub binding: ManagedCanisterBinding,
440    pub phase: ComponentRuntimePhase,
441    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
442    pub authority_hash: Option<[u8; 32]>,
443    pub activation: Option<ComponentRuntimeActivationEvidence>,
444}
445
446///
447/// ComponentRuntimeDirectoryConvergenceEvidence
448///
449/// Stable root evidence that one active member covered at least the required Directory authority.
450///
451
452#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
453pub struct ComponentRuntimeDirectoryConvergenceEvidence {
454    pub operation_id: [u8; 32],
455    pub binding: ManagedCanisterBinding,
456    pub covered_authority: ComponentRuntimeDirectoryAuthority,
457    pub covered_authority_hash: [u8; 32],
458    pub activation: ComponentRuntimeActivationEvidence,
459}
460
461///
462/// RootComponentCreationEvidence
463///
464/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
465///
466
467#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
468pub struct RootComponentCreationEvidence {
469    pub wasm_store: Principal,
470    pub payload_hash: [u8; 32],
471    pub payload_size_bytes: u64,
472    pub initial_cycles: Cycles,
473    pub controller: Principal,
474    pub canister: Option<Principal>,
475}
476
477///
478/// RootComponentInstallEvidence
479///
480/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
481///
482
483#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
484pub struct RootComponentInstallEvidence {
485    pub raw_module_hash: [u8; 32],
486    pub chunk_hashes: Vec<Vec<u8>>,
487    pub binding: ComponentBinding,
488}
489
490///
491/// RootComponentChildInstallEvidence
492///
493/// Exact child module and immutable retained binding frozen before installation.
494///
495
496#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
497pub struct RootComponentChildInstallEvidence {
498    pub raw_module_hash: [u8; 32],
499    pub chunk_hashes: Vec<Vec<u8>>,
500    pub binding: ComponentChildBinding,
501}
502
503///
504/// RootComponentAllocationResponse
505///
506/// Durable identity reservation returned identically for exact operation retry.
507///
508
509#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub struct RootComponentAllocationResponse {
511    pub operation_id: [u8; 32],
512    pub allocation_sequence: u64,
513    pub component: ComponentInstanceId,
514    pub component_spec: ComponentSpecId,
515    pub spec_hash: [u8; 32],
516    pub role: CanisterRole,
517    pub provisioning_origin: ComponentProvisioningOrigin,
518    pub release_set: FleetSubnetRootReleaseSet,
519    pub phase: RootComponentAllocationPhase,
520    pub creation: Option<RootComponentCreationEvidence>,
521    pub installation: Option<RootComponentInstallEvidence>,
522}
523
524///
525/// RootComponentChildAllocationResponse
526///
527/// Durable direct-child lifecycle progress returned identically for exact parent retry.
528///
529
530#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
531pub struct RootComponentChildAllocationResponse {
532    pub operation_id: [u8; 32],
533    pub component: ComponentInstanceId,
534    pub parent_canister_id: Principal,
535    pub parent_role: CanisterRole,
536    pub child_role: CanisterRole,
537    pub child_kind: ComponentChildKind,
538    pub maximum_instances_per_parent: u32,
539    pub maximum_descendants: u32,
540    pub maximum_registry_bytes: u64,
541    pub reserved_against_registry: ComponentRegistryHead,
542    pub release_set: FleetSubnetRootReleaseSet,
543    pub phase: RootComponentAllocationPhase,
544    pub creation: Option<RootComponentCreationEvidence>,
545    pub installation: Option<RootComponentChildInstallEvidence>,
546}
547
548///
549/// RootComponentChildCommitResponse
550///
551/// Exact committed child operation, authoritative Component Registry and next Directory head.
552///
553
554#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
555pub struct RootComponentChildCommitResponse {
556    pub allocation: RootComponentChildAllocationResponse,
557    pub registry: ComponentRegistryPartitionResponse,
558    pub directory: ComponentDirectoryHead,
559}
560
561///
562/// RootComponentChildDirectoryPreparationResponse
563///
564/// Exact child preparation plus stable bounded active-member Directory coverage.
565///
566
567#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
568pub struct RootComponentChildDirectoryPreparationResponse {
569    pub committed: RootComponentChildCommitResponse,
570    pub child: ComponentRuntimeStatusResponse,
571    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
572    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
573}
574
575///
576/// RootComponentCommitResponse
577///
578/// Exact committed allocation, authoritative Registry row and derived Directory head.
579///
580
581#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
582pub struct RootComponentCommitResponse {
583    pub allocation: RootComponentAllocationResponse,
584    pub registry: ComponentRegistryPartitionResponse,
585    pub directory: ComponentDirectoryHead,
586}
587
588///
589/// RootComponentDirectoryPreparationResponse
590///
591/// Exact root authority plus independently observed target-local Directory preparation.
592///
593
594#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
595pub struct RootComponentDirectoryPreparationResponse {
596    pub committed: RootComponentCommitResponse,
597    pub target: ComponentRuntimeStatusResponse,
598}
599
600///
601/// RootComponentRuntimeActivationResponse
602///
603/// Exact root authority plus independently observed target-local runtime activation.
604///
605
606#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
607pub struct RootComponentRuntimeActivationResponse {
608    pub committed: RootComponentCommitResponse,
609    pub target: ComponentRuntimeStatusResponse,
610}
611
612///
613/// RootComponentMembershipActivationResponse
614///
615/// Exact active Registry authority plus independently observed current target Directory.
616///
617
618#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
619pub struct RootComponentMembershipActivationResponse {
620    pub allocation: RootComponentAllocationResponse,
621    pub registry: ComponentRegistryPartitionResponse,
622    pub directory: ComponentDirectoryHead,
623    pub target: ComponentRuntimeStatusResponse,
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629    use crate::{
630        dto::root_store::RootStoreBootstrapRequest,
631        ids::{
632            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
633            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
634        },
635    };
636
637    #[test]
638    fn component_registry_contracts_round_trip_through_candid() {
639        let request = RootComponentRegistryPreparationRequest {
640            store_bootstrap: RootStoreBootstrapRequest {
641                manifest_payload_size_bytes: 128,
642            },
643            expected_fleet_registry: FleetRegistryVersion {
644                authority: fleet_registry_authority(),
645                revision: 4,
646                content_hash: [5; 32],
647            },
648        };
649        let response = RootComponentRegistryStatusResponse {
650            fleet_subnet_root: Principal::from_slice(&[6; 29]),
651            prepared_against_registry: request.expected_fleet_registry.clone(),
652            release_set: FleetSubnetRootReleaseSet {
653                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
654                    [7; 32],
655                )),
656                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
657            },
658            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
659            next_allocation_sequence: 1,
660            reserved_component_instances: 0,
661            committed_component_instances: 0,
662            managed_descendants: 0,
663            known_created_component_canisters: 0,
664            encoded_bytes: 0,
665            initial_inventory: Some(RootComponentInitialInventoryStatus {
666                fleet_activation_operation_id: [10; 32],
667                component_count: 0,
668                inventory_hash: [11; 32],
669                sealed_at_ns: 12,
670                directories_converged: true,
671                root_runtime_activated: true,
672            }),
673        };
674        let allocation = RootComponentAllocationResponse {
675            operation_id: [10; 32],
676            allocation_sequence: 1,
677            component: ComponentInstanceId::from_generated_bytes([11; 32]),
678            component_spec: "projects".parse().expect("Component Spec ID"),
679            spec_hash: [12; 32],
680            role: CanisterRole::new("project_hub"),
681            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
682                caller: Principal::from_slice(&[13; 29]),
683            },
684            release_set: response.release_set,
685            phase: RootComponentAllocationPhase::Reserved,
686            creation: None,
687            installation: None,
688        };
689        let created = RootComponentAllocationResponse {
690            phase: RootComponentAllocationPhase::Created,
691            creation: Some(RootComponentCreationEvidence {
692                wasm_store: Principal::from_slice(&[14; 29]),
693                payload_hash: [15; 32],
694                payload_size_bytes: 4_096,
695                initial_cycles: Cycles::new(5_000_000_000_000),
696                controller: Principal::from_slice(&[6; 29]),
697                canister: Some(Principal::from_slice(&[16; 29])),
698            }),
699            installation: None,
700            ..allocation.clone()
701        };
702        let request_bytes = candid::encode_one(&request).expect("encode request");
703        let response_bytes = candid::encode_one(&response).expect("encode response");
704        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
705        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
706
707        assert_eq!(
708            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
709                .expect("decode request"),
710            request
711        );
712        assert_eq!(
713            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
714                .expect("decode response"),
715            response
716        );
717        assert_eq!(
718            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
719                .expect("decode allocation"),
720            allocation
721        );
722        assert_eq!(
723            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
724                .expect("decode created allocation"),
725            created
726        );
727    }
728
729    #[test]
730    fn component_commit_response_round_trips_through_candid() {
731        let root = Principal::from_slice(&[6; 29]);
732        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
733        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
734        let release_set = FleetSubnetRootReleaseSet {
735            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
736                [7; 32],
737            )),
738            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
739        };
740        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
741            caller: Principal::from_slice(&[13; 29]),
742        };
743        let binding = ComponentBinding {
744            authority: fleet_registry_authority(),
745            component,
746            component_spec: component_spec.clone(),
747            spec_hash: [12; 32],
748            role: CanisterRole::new("project_hub"),
749            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
750            fleet_subnet_root: root,
751            canister_id: Principal::from_slice(&[16; 29]),
752        };
753        let head = ComponentRegistryHead {
754            component,
755            revision: 1,
756            content_hash: [18; 32],
757        };
758        let committed = RootComponentCommitResponse {
759            allocation: RootComponentAllocationResponse {
760                operation_id: [10; 32],
761                allocation_sequence: 1,
762                component,
763                component_spec,
764                spec_hash: binding.spec_hash,
765                role: binding.role.clone(),
766                provisioning_origin: provisioning_origin.clone(),
767                release_set,
768                phase: RootComponentAllocationPhase::Committed,
769                creation: Some(RootComponentCreationEvidence {
770                    wasm_store: Principal::from_slice(&[14; 29]),
771                    payload_hash: [15; 32],
772                    payload_size_bytes: 4_096,
773                    initial_cycles: Cycles::new(5_000_000_000_000),
774                    controller: root,
775                    canister: Some(binding.canister_id),
776                }),
777                installation: Some(RootComponentInstallEvidence {
778                    raw_module_hash: [20; 32],
779                    chunk_hashes: vec![vec![21; 32]],
780                    binding: binding.clone(),
781                }),
782            },
783            registry: ComponentRegistryPartitionResponse {
784                head: head.clone(),
785                binding: binding.clone(),
786                provisioning_origin,
787                release_set,
788                status: ComponentLifecycleStatus::Prepared,
789                reserved_descendants: 0,
790                committed_descendants: 0,
791                encoded_bytes: 2_048,
792            },
793            directory: ComponentDirectoryHead {
794                provenance: ComponentDirectoryProvenance {
795                    component: binding,
796                    source_fleet_subnet_root: root,
797                    component_registry_revision: head.revision,
798                    component_registry_content_hash: head.content_hash,
799                    synchronized_at_ns: 19,
800                },
801                descendant_count: 0,
802            },
803        };
804        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
805
806        assert_eq!(
807            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
808                .expect("decode committed allocation"),
809            committed
810        );
811    }
812
813    fn fleet_registry_authority() -> FleetRegistryAuthority {
814        FleetRegistryAuthority {
815            binding: FleetCoordinatorBinding {
816                fleet: crate::ids::FleetBinding {
817                    fleet: FleetKey {
818                        canonical_network_id: CanonicalNetworkId::public_ic(),
819                        fleet_id: FleetId::from_generated_bytes([1; 32]),
820                    },
821                    app: AppId::from("toko"),
822                },
823                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
824                coordinator: Principal::from_slice(&[3; 29]),
825            },
826            epoch: 1,
827        }
828    }
829
830    #[test]
831    fn component_creation_request_round_trips_through_candid() {
832        let request = RootComponentCreationRequest {
833            operation_id: [10; 32],
834        };
835        let bytes = candid::encode_one(request).expect("encode creation request");
836
837        assert_eq!(
838            candid::decode_one::<RootComponentCreationRequest>(&bytes)
839                .expect("decode creation request"),
840            request
841        );
842    }
843
844    #[test]
845    #[expect(
846        clippy::too_many_lines,
847        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
848    )]
849    fn component_child_lifecycle_contracts_round_trip_through_candid() {
850        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
851        let registry = ComponentRegistryHead {
852            component,
853            revision: 2,
854            content_hash: [12; 32],
855        };
856        let request = RootComponentChildAllocationRequest {
857            operation_id: [13; 32],
858            component,
859            expected_registry: registry.clone(),
860            child_role: CanisterRole::new("project_instance"),
861        };
862        let status_request = RootComponentChildAllocationStatusRequest {
863            operation_id: request.operation_id,
864            component,
865        };
866        let creation_request = RootComponentChildCreationRequest {
867            operation_id: request.operation_id,
868            component,
869        };
870        let install_request = RootComponentChildInstallRequest {
871            operation_id: request.operation_id,
872            component,
873        };
874        let commit_request = RootComponentChildCommitRequest {
875            operation_id: request.operation_id,
876            component,
877        };
878        let directory_request = RootComponentChildDirectoryPreparationRequest {
879            operation_id: request.operation_id,
880            component,
881        };
882        let root = Principal::from_slice(&[17; 29]);
883        let parent = Principal::from_slice(&[14; 29]);
884        let child = Principal::from_slice(&[18; 29]);
885        let child_binding = ComponentChildBinding {
886            component: ComponentBinding {
887                authority: fleet_registry_authority(),
888                component,
889                component_spec: "projects".parse().expect("Component Spec"),
890                spec_hash: [19; 32],
891                role: CanisterRole::new("project_hub"),
892                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
893                fleet_subnet_root: root,
894                canister_id: parent,
895            },
896            parent_canister_id: parent,
897            role: request.child_role.clone(),
898            canister_id: child,
899        };
900        let response = RootComponentChildAllocationResponse {
901            operation_id: request.operation_id,
902            component,
903            parent_canister_id: parent,
904            parent_role: CanisterRole::new("project_hub"),
905            child_role: request.child_role.clone(),
906            child_kind: ComponentChildKind::Instance,
907            maximum_instances_per_parent: 10_000,
908            maximum_descendants: 20_000,
909            maximum_registry_bytes: 16_777_216,
910            reserved_against_registry: registry,
911            release_set: FleetSubnetRootReleaseSet {
912                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
913                    [15; 32],
914                )),
915                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
916            },
917            phase: RootComponentAllocationPhase::Verified,
918            creation: Some(RootComponentCreationEvidence {
919                wasm_store: Principal::from_slice(&[21; 29]),
920                payload_hash: [22; 32],
921                payload_size_bytes: 4_096,
922                initial_cycles: Cycles::new(5_000_000_000_000),
923                controller: root,
924                canister: Some(child),
925            }),
926            installation: Some(RootComponentChildInstallEvidence {
927                raw_module_hash: [23; 32],
928                chunk_hashes: vec![vec![24; 32]],
929                binding: child_binding.clone(),
930            }),
931        };
932        let commit_response = RootComponentChildCommitResponse {
933            allocation: response.clone(),
934            registry: ComponentRegistryPartitionResponse {
935                head: ComponentRegistryHead {
936                    component,
937                    revision: 3,
938                    content_hash: [25; 32],
939                },
940                binding: child_binding.component.clone(),
941                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
942                    caller: Principal::from_slice(&[26; 29]),
943                },
944                release_set: response.release_set,
945                status: ComponentLifecycleStatus::Active,
946                reserved_descendants: 0,
947                committed_descendants: 1,
948                encoded_bytes: 8_192,
949            },
950            directory: ComponentDirectoryHead {
951                provenance: ComponentDirectoryProvenance {
952                    component: child_binding.component.clone(),
953                    source_fleet_subnet_root: root,
954                    component_registry_revision: 3,
955                    component_registry_content_hash: [25; 32],
956                    synchronized_at_ns: 27,
957                },
958                descendant_count: 1,
959            },
960        };
961        let runtime_authority = ComponentRuntimeDirectoryAuthority {
962            fleet: FleetDirectorySnapshot {
963                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
964                    registry: FleetRegistryVersion {
965                        authority: fleet_registry_authority(),
966                        revision: 4,
967                        content_hash: [28; 32],
968                    },
969                    source_fleet_subnet_root: root,
970                },
971                fleet_subnet_roots: vec![
972                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
973                        placement_subnet: commit_response.registry.binding.placement_subnet,
974                        fleet_subnet_root: root,
975                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
976                    },
977                ],
978            },
979            component: commit_response.directory.clone(),
980        };
981        let activation = ComponentRuntimeActivationEvidence {
982            directory_authority_hash: [29; 32],
983            activated_at_ns: 30,
984        };
985        let directory_response = RootComponentChildDirectoryPreparationResponse {
986            committed: commit_response.clone(),
987            child: ComponentRuntimeStatusResponse {
988                operation_id: request.operation_id,
989                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
990                phase: ComponentRuntimePhase::DirectoryPrepared,
991                authority: Some(runtime_authority.clone()),
992                authority_hash: Some([31; 32]),
993                activation: None,
994            },
995            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
996                operation_id: [32; 32],
997                binding: ManagedCanisterBinding::Component(child_binding.component),
998                covered_authority: runtime_authority,
999                covered_authority_hash: [31; 32],
1000                activation,
1001            },
1002            parent: None,
1003        };
1004
1005        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1006        let status_bytes =
1007            candid::encode_one(status_request).expect("encode child reservation status");
1008        let creation_bytes =
1009            candid::encode_one(creation_request).expect("encode child creation request");
1010        let install_bytes =
1011            candid::encode_one(install_request).expect("encode child install request");
1012        let response_bytes = candid::encode_one(&response).expect("encode child response");
1013        let commit_request_bytes =
1014            candid::encode_one(commit_request).expect("encode child commit request");
1015        let directory_request_bytes =
1016            candid::encode_one(directory_request).expect("encode child Directory request");
1017        let commit_response_bytes =
1018            candid::encode_one(&commit_response).expect("encode child commit response");
1019        let directory_response_bytes =
1020            candid::encode_one(&directory_response).expect("encode child Directory response");
1021
1022        assert_eq!(
1023            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1024                .expect("decode child reservation"),
1025            request
1026        );
1027        assert_eq!(
1028            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1029                .expect("decode child reservation status"),
1030            status_request
1031        );
1032        assert_eq!(
1033            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1034                .expect("decode child creation request"),
1035            creation_request
1036        );
1037        assert_eq!(
1038            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1039                .expect("decode child install request"),
1040            install_request
1041        );
1042        assert_eq!(
1043            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1044                .expect("decode child response"),
1045            response
1046        );
1047        assert_eq!(
1048            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1049                .expect("decode child commit request"),
1050            commit_request
1051        );
1052        assert_eq!(
1053            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1054                &directory_request_bytes
1055            )
1056            .expect("decode child Directory request"),
1057            directory_request
1058        );
1059        assert_eq!(
1060            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1061                .expect("decode child commit response"),
1062            commit_response
1063        );
1064        assert_eq!(
1065            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1066                &directory_response_bytes
1067            )
1068            .expect("decode child Directory response"),
1069            directory_response
1070        );
1071    }
1072
1073    #[test]
1074    fn component_install_request_round_trips_through_candid() {
1075        let request = RootComponentInstallRequest {
1076            operation_id: [10; 32],
1077        };
1078        let bytes = candid::encode_one(request).expect("encode install request");
1079
1080        assert_eq!(
1081            candid::decode_one::<RootComponentInstallRequest>(&bytes)
1082                .expect("decode install request"),
1083            request
1084        );
1085    }
1086
1087    #[test]
1088    fn component_commit_request_round_trips_through_candid() {
1089        let request = RootComponentCommitRequest {
1090            operation_id: [10; 32],
1091        };
1092        let bytes = candid::encode_one(request).expect("encode commit request");
1093
1094        assert_eq!(
1095            candid::decode_one::<RootComponentCommitRequest>(&bytes)
1096                .expect("decode commit request"),
1097            request
1098        );
1099    }
1100
1101    #[test]
1102    fn component_runtime_activation_requests_round_trip_through_candid() {
1103        let root_request = RootComponentRuntimeActivationRequest {
1104            operation_id: [22; 32],
1105        };
1106        let target_request = ComponentRuntimeActivationRequest {
1107            operation_id: root_request.operation_id,
1108            directory_authority_hash: [23; 32],
1109        };
1110        let membership_request = RootComponentMembershipActivationRequest {
1111            operation_id: root_request.operation_id,
1112        };
1113        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1114        let target_bytes =
1115            candid::encode_one(target_request).expect("encode target activation request");
1116        let membership_bytes =
1117            candid::encode_one(membership_request).expect("encode membership activation request");
1118
1119        assert_eq!(
1120            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1121                .expect("decode root activation request"),
1122            root_request
1123        );
1124        assert_eq!(
1125            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1126                .expect("decode target activation request"),
1127            target_request
1128        );
1129        assert_eq!(
1130            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1131                .expect("decode membership activation request"),
1132            membership_request
1133        );
1134    }
1135}