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