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/// RootComponentSubtreeRemovalRequest
123///
124/// Controller command durably fencing one registered child subtree.
125///
126
127#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentSubtreeRemovalRequest {
129    pub operation_id: [u8; 32],
130    pub component: ComponentInstanceId,
131    pub target_canister_id: Principal,
132    pub expected_registry: ComponentRegistryHead,
133}
134
135///
136/// RootComponentSubtreeRemovalAdvanceRequest
137///
138/// Controller command advancing bounded traversal from one observed durable step.
139///
140
141#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
142pub struct RootComponentSubtreeRemovalAdvanceRequest {
143    pub operation_id: [u8; 32],
144    pub component: ComponentInstanceId,
145    pub expected_traversal_steps: u32,
146}
147
148///
149/// RootComponentSubtreeRemovalStatusRequest
150///
151/// Controller lookup key for one durable child-subtree removal operation.
152///
153
154#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentSubtreeRemovalStatusRequest {
156    pub operation_id: [u8; 32],
157    pub component: ComponentInstanceId,
158}
159
160///
161/// RootComponentChildCreationRequest
162///
163/// Parent command continuing one already reserved direct-child operation.
164///
165
166#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
167pub struct RootComponentChildCreationRequest {
168    pub operation_id: [u8; 32],
169    pub component: ComponentInstanceId,
170}
171
172///
173/// RootComponentChildInstallRequest
174///
175/// Parent command installing and verifying one already created direct-child operation.
176///
177
178#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
179pub struct RootComponentChildInstallRequest {
180    pub operation_id: [u8; 32],
181    pub component: ComponentInstanceId,
182}
183
184///
185/// RootComponentChildCommitRequest
186///
187/// Parent command committing one already verified direct-child operation.
188///
189
190#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
191pub struct RootComponentChildCommitRequest {
192    pub operation_id: [u8; 32],
193    pub component: ComponentInstanceId,
194}
195
196///
197/// RootComponentChildDirectoryPreparationRequest
198///
199/// Parent command distributing one committed child's Directory and converging its affected members.
200///
201
202#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
203pub struct RootComponentChildDirectoryPreparationRequest {
204    pub operation_id: [u8; 32],
205    pub component: ComponentInstanceId,
206}
207
208///
209/// RootComponentChildRuntimeActivationRequest
210///
211/// Parent command activating one Directory-prepared direct-child runtime.
212///
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct RootComponentChildRuntimeActivationRequest {
216    pub operation_id: [u8; 32],
217    pub component: ComponentInstanceId,
218}
219
220///
221/// RootComponentChildMembershipActivationRequest
222///
223/// Parent command activating one runtime-active direct child's Registry membership.
224///
225
226#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
227pub struct RootComponentChildMembershipActivationRequest {
228    pub operation_id: [u8; 32],
229    pub component: ComponentInstanceId,
230}
231
232///
233/// RootComponentCreationRequest
234///
235/// Controller command continuing one already reserved top-level Component operation.
236///
237
238#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
239pub struct RootComponentCreationRequest {
240    pub operation_id: [u8; 32],
241}
242
243///
244/// RootComponentInstallRequest
245///
246/// Controller command continuing one already created top-level Component operation.
247///
248
249#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
250pub struct RootComponentInstallRequest {
251    pub operation_id: [u8; 32],
252}
253
254///
255/// RootComponentCommitRequest
256///
257/// Controller command committing one already verified top-level Component operation.
258///
259
260#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
261pub struct RootComponentCommitRequest {
262    pub operation_id: [u8; 32],
263}
264
265///
266/// RootComponentDirectoryPreparationRequest
267///
268/// Controller command distributing exact Directories to one committed top-level Component.
269///
270
271#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
272pub struct RootComponentDirectoryPreparationRequest {
273    pub operation_id: [u8; 32],
274}
275
276///
277/// RootComponentRuntimeActivationRequest
278///
279/// Controller command activating one Directory-prepared top-level Component runtime.
280///
281
282#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
283pub struct RootComponentRuntimeActivationRequest {
284    pub operation_id: [u8; 32],
285}
286
287///
288/// RootComponentMembershipActivationRequest
289///
290/// Controller command activating one runtime-active Component's Registry membership.
291///
292
293#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub struct RootComponentMembershipActivationRequest {
295    pub operation_id: [u8; 32],
296}
297
298///
299/// ComponentProvisioningOrigin
300///
301/// Authenticated causal authority retained with one top-level Component allocation.
302///
303
304#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
305pub enum ComponentProvisioningOrigin {
306    FleetAdministrator { caller: Principal },
307}
308
309///
310/// RootComponentAllocationPhase
311///
312/// Durable root-local progress of one top-level Component allocation operation.
313///
314
315#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
316pub enum RootComponentAllocationPhase {
317    Reserved,
318    CreationIntent,
319    Created,
320    InstallIntent,
321    Installed,
322    Verified,
323    Committed,
324}
325
326///
327/// RootComponentSubtreeRemovalPhase
328///
329/// Durable root-local progress of one child-subtree removal operation.
330///
331
332#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
333pub enum RootComponentSubtreeRemovalPhase {
334    Fenced,
335    Traversing(RootComponentSubtreeRemovalNode),
336    LeafSelected(RootComponentSubtreeRemovalNode),
337}
338
339///
340/// RootComponentSubtreeRemovalNode
341///
342/// Exact registered child selected as a traversal cursor or removable leaf.
343///
344
345#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
346pub struct RootComponentSubtreeRemovalNode {
347    pub canister_id: Principal,
348    pub parent_canister_id: Principal,
349    pub role: CanisterRole,
350    pub kind: ComponentChildKind,
351    pub installed_artifact_hash: [u8; 32],
352    pub status: ComponentLifecycleStatus,
353}
354
355///
356/// ComponentLifecycleStatus
357///
358/// Root-owned runtime lifecycle state of one committed Component Registry member.
359///
360
361#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
362pub enum ComponentLifecycleStatus {
363    Prepared,
364    Active,
365    Draining,
366    Removed,
367}
368
369///
370/// ComponentRegistryHead
371///
372/// Exact independently versioned authority of one Component Registry partition.
373///
374
375#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub struct ComponentRegistryHead {
377    pub component: ComponentInstanceId,
378    pub revision: u64,
379    pub content_hash: [u8; 32],
380}
381
382///
383/// ComponentRegistryPartitionRequest
384///
385/// Read-only lookup key for one committed Component Registry partition.
386///
387
388#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
389pub struct ComponentRegistryPartitionRequest {
390    pub component: ComponentInstanceId,
391}
392
393///
394/// ComponentRegistryPartitionResponse
395///
396/// Protected top-level row and independent head of one Component Registry partition.
397///
398
399#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub struct ComponentRegistryPartitionResponse {
401    pub head: ComponentRegistryHead,
402    pub binding: ComponentBinding,
403    pub provisioning_origin: ComponentProvisioningOrigin,
404    pub release_set: FleetSubnetRootReleaseSet,
405    pub status: ComponentLifecycleStatus,
406    pub reserved_descendants: u32,
407    pub committed_descendants: u32,
408    pub encoded_bytes: u64,
409}
410
411///
412/// ComponentDirectoryProvenance
413///
414/// Exact Component Registry authority from which one Component Directory is derived.
415///
416
417#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
418pub struct ComponentDirectoryProvenance {
419    pub component: ComponentBinding,
420    pub source_fleet_subnet_root: Principal,
421    pub component_registry_revision: u64,
422    pub component_registry_content_hash: [u8; 32],
423    pub synchronized_at_ns: u64,
424}
425
426///
427/// ComponentDirectoryHead
428///
429/// Compact independently versioned discovery projection for one Component tree.
430///
431
432#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
433pub struct ComponentDirectoryHead {
434    pub provenance: ComponentDirectoryProvenance,
435    pub descendant_count: u32,
436}
437
438///
439/// ComponentDirectoryHeadRequest
440///
441/// Read-only lookup key for one committed Component Directory head.
442///
443
444#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
445pub struct ComponentDirectoryHeadRequest {
446    pub component: ComponentInstanceId,
447}
448
449///
450/// ComponentDirectoryPageCursor
451///
452/// Opaque revision- and filter-bound continuation for one bounded Directory page.
453///
454
455#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
456pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
457
458///
459/// ComponentDirectoryPageRequest
460///
461/// Bounded member query against one exact current Component Directory authority.
462///
463
464#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
465pub struct ComponentDirectoryPageRequest {
466    pub directory: ComponentDirectoryHead,
467    pub parent_canister_id: Option<Principal>,
468    pub role: Option<CanisterRole>,
469    pub status: Option<ComponentLifecycleStatus>,
470    pub cursor: Option<ComponentDirectoryPageCursor>,
471    pub limit: u16,
472}
473
474///
475/// ComponentDirectoryChildEntry
476///
477/// One authoritative normalized child projected with its complete protected binding.
478///
479
480#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
481pub struct ComponentDirectoryChildEntry {
482    pub binding: ComponentChildBinding,
483    pub kind: ComponentChildKind,
484    pub installed_artifact_hash: [u8; 32],
485    pub status: ComponentLifecycleStatus,
486}
487
488///
489/// ComponentDirectoryPageResponse
490///
491/// One bounded caller-scoped page under the exact requested Directory head.
492///
493
494#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
495pub struct ComponentDirectoryPageResponse {
496    pub directory: ComponentDirectoryHead,
497    pub entries: Vec<ComponentDirectoryChildEntry>,
498    pub next_cursor: Option<ComponentDirectoryPageCursor>,
499}
500
501///
502/// ComponentRuntimeDirectoryAuthority
503///
504/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
505///
506
507#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
508pub struct ComponentRuntimeDirectoryAuthority {
509    pub fleet: FleetDirectorySnapshot,
510    pub component: ComponentDirectoryHead,
511}
512
513///
514/// ComponentRuntimeDirectoryPreparationRequest
515///
516/// Root-issued exact Directory preparation command for one managed Component-tree node.
517///
518
519#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
520pub struct ComponentRuntimeDirectoryPreparationRequest {
521    pub operation_id: [u8; 32],
522    pub authority: ComponentRuntimeDirectoryAuthority,
523}
524
525///
526/// ComponentRuntimeDirectorySynchronizationRequest
527///
528/// Root-issued replacement of one active managed Component node's current Directory authority.
529///
530
531#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
532pub struct ComponentRuntimeDirectorySynchronizationRequest {
533    pub operation_id: [u8; 32],
534    pub authority: ComponentRuntimeDirectoryAuthority,
535}
536
537///
538/// ComponentRuntimePhase
539///
540/// Target-local progress from installation through Component runtime activation.
541///
542
543#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
544pub enum ComponentRuntimePhase {
545    AwaitingDirectory,
546    DirectoryPrepared,
547    Active,
548}
549
550///
551/// ComponentRuntimeActivationEvidence
552///
553/// Exact retained Directory authority under which one Component runtime became Active.
554///
555
556#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
557pub struct ComponentRuntimeActivationEvidence {
558    pub directory_authority_hash: [u8; 32],
559    pub activated_at_ns: u64,
560}
561
562///
563/// ComponentRuntimeActivationRequest
564///
565/// Root-issued exact activation command for one Directory-prepared managed Component node.
566///
567
568#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
569pub struct ComponentRuntimeActivationRequest {
570    pub operation_id: [u8; 32],
571    pub directory_authority_hash: [u8; 32],
572}
573
574///
575/// ComponentRuntimeStatusResponse
576///
577/// Independently observable target-local binding and exact retained Directory authority.
578///
579
580#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
581pub struct ComponentRuntimeStatusResponse {
582    pub operation_id: [u8; 32],
583    pub binding: ManagedCanisterBinding,
584    pub phase: ComponentRuntimePhase,
585    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
586    pub authority_hash: Option<[u8; 32]>,
587    pub activation: Option<ComponentRuntimeActivationEvidence>,
588}
589
590///
591/// ComponentRuntimeDirectoryConvergenceEvidence
592///
593/// Stable root evidence that one active member covered at least the required Directory authority.
594///
595
596#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
597pub struct ComponentRuntimeDirectoryConvergenceEvidence {
598    pub operation_id: [u8; 32],
599    pub binding: ManagedCanisterBinding,
600    pub covered_authority: ComponentRuntimeDirectoryAuthority,
601    pub covered_authority_hash: [u8; 32],
602    pub activation: ComponentRuntimeActivationEvidence,
603}
604
605///
606/// RootComponentCreationEvidence
607///
608/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
609///
610
611#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
612pub struct RootComponentCreationEvidence {
613    pub wasm_store: Principal,
614    pub payload_hash: [u8; 32],
615    pub payload_size_bytes: u64,
616    pub initial_cycles: Cycles,
617    pub controller: Principal,
618    pub canister: Option<Principal>,
619}
620
621///
622/// RootComponentInstallEvidence
623///
624/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
625///
626
627#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
628pub struct RootComponentInstallEvidence {
629    pub raw_module_hash: [u8; 32],
630    pub chunk_hashes: Vec<Vec<u8>>,
631    pub binding: ComponentBinding,
632}
633
634///
635/// RootComponentChildInstallEvidence
636///
637/// Exact child module and immutable retained binding frozen before installation.
638///
639
640#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
641pub struct RootComponentChildInstallEvidence {
642    pub raw_module_hash: [u8; 32],
643    pub chunk_hashes: Vec<Vec<u8>>,
644    pub binding: ComponentChildBinding,
645}
646
647///
648/// RootComponentAllocationResponse
649///
650/// Durable identity reservation returned identically for exact operation retry.
651///
652
653#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
654pub struct RootComponentAllocationResponse {
655    pub operation_id: [u8; 32],
656    pub allocation_sequence: u64,
657    pub component: ComponentInstanceId,
658    pub component_spec: ComponentSpecId,
659    pub spec_hash: [u8; 32],
660    pub role: CanisterRole,
661    pub provisioning_origin: ComponentProvisioningOrigin,
662    pub release_set: FleetSubnetRootReleaseSet,
663    pub phase: RootComponentAllocationPhase,
664    pub creation: Option<RootComponentCreationEvidence>,
665    pub installation: Option<RootComponentInstallEvidence>,
666}
667
668///
669/// RootComponentChildAllocationResponse
670///
671/// Durable direct-child lifecycle progress returned identically for exact parent retry.
672///
673
674#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
675pub struct RootComponentChildAllocationResponse {
676    pub operation_id: [u8; 32],
677    pub component: ComponentInstanceId,
678    pub parent_canister_id: Principal,
679    pub parent_role: CanisterRole,
680    pub child_role: CanisterRole,
681    pub child_kind: ComponentChildKind,
682    pub maximum_instances_per_parent: u32,
683    pub maximum_descendants: u32,
684    pub maximum_registry_bytes: u64,
685    pub reserved_against_registry: ComponentRegistryHead,
686    pub release_set: FleetSubnetRootReleaseSet,
687    pub phase: RootComponentAllocationPhase,
688    pub creation: Option<RootComponentCreationEvidence>,
689    pub installation: Option<RootComponentChildInstallEvidence>,
690}
691
692///
693/// RootComponentSubtreeRemovalResponse
694///
695/// Exact durable subtree fence returned identically for operation retry.
696///
697
698#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
699pub struct RootComponentSubtreeRemovalResponse {
700    pub operation_id: [u8; 32],
701    pub component: ComponentInstanceId,
702    pub target_canister_id: Principal,
703    pub target_parent_canister_id: Principal,
704    pub target_role: CanisterRole,
705    pub target_status: ComponentLifecycleStatus,
706    pub reserved_against_registry: ComponentRegistryHead,
707    pub traversal_steps: u32,
708    pub phase: RootComponentSubtreeRemovalPhase,
709}
710
711///
712/// RootComponentChildCommitResponse
713///
714/// Exact committed child operation, authoritative Component Registry and next Directory head.
715///
716
717#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
718pub struct RootComponentChildCommitResponse {
719    pub allocation: RootComponentChildAllocationResponse,
720    pub registry: ComponentRegistryPartitionResponse,
721    pub directory: ComponentDirectoryHead,
722}
723
724///
725/// RootComponentChildDirectoryPreparationResponse
726///
727/// Exact child preparation plus stable bounded active-member Directory coverage.
728///
729
730#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
731pub struct RootComponentChildDirectoryPreparationResponse {
732    pub committed: RootComponentChildCommitResponse,
733    pub child: ComponentRuntimeStatusResponse,
734    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
735    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
736}
737
738///
739/// RootComponentChildRuntimeActivationResponse
740///
741/// Exact child commitment plus independently observed Directory-bound runtime activation.
742///
743
744#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
745pub struct RootComponentChildRuntimeActivationResponse {
746    pub committed: RootComponentChildCommitResponse,
747    pub child: ComponentRuntimeStatusResponse,
748}
749
750///
751/// RootComponentChildMembershipActivationResponse
752///
753/// Original child commitment plus active Registry, Directory and target convergence evidence.
754///
755
756#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
757pub struct RootComponentChildMembershipActivationResponse {
758    pub committed: RootComponentChildCommitResponse,
759    pub registry: ComponentRegistryPartitionResponse,
760    pub directory: ComponentDirectoryHead,
761    pub child: ComponentRuntimeStatusResponse,
762}
763
764///
765/// RootComponentCommitResponse
766///
767/// Exact committed allocation, authoritative Registry row and derived Directory head.
768///
769
770#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
771pub struct RootComponentCommitResponse {
772    pub allocation: RootComponentAllocationResponse,
773    pub registry: ComponentRegistryPartitionResponse,
774    pub directory: ComponentDirectoryHead,
775}
776
777///
778/// RootComponentDirectoryPreparationResponse
779///
780/// Exact root authority plus independently observed target-local Directory preparation.
781///
782
783#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
784pub struct RootComponentDirectoryPreparationResponse {
785    pub committed: RootComponentCommitResponse,
786    pub target: ComponentRuntimeStatusResponse,
787}
788
789///
790/// RootComponentRuntimeActivationResponse
791///
792/// Exact root authority plus independently observed target-local runtime activation.
793///
794
795#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796pub struct RootComponentRuntimeActivationResponse {
797    pub committed: RootComponentCommitResponse,
798    pub target: ComponentRuntimeStatusResponse,
799}
800
801///
802/// RootComponentMembershipActivationResponse
803///
804/// Exact active Registry authority plus independently observed current target Directory.
805///
806
807#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
808pub struct RootComponentMembershipActivationResponse {
809    pub allocation: RootComponentAllocationResponse,
810    pub registry: ComponentRegistryPartitionResponse,
811    pub directory: ComponentDirectoryHead,
812    pub target: ComponentRuntimeStatusResponse,
813}
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818    use crate::{
819        dto::root_store::RootStoreBootstrapRequest,
820        ids::{
821            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
822            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
823        },
824    };
825
826    #[test]
827    fn component_registry_contracts_round_trip_through_candid() {
828        let request = RootComponentRegistryPreparationRequest {
829            store_bootstrap: RootStoreBootstrapRequest {
830                manifest_payload_size_bytes: 128,
831            },
832            expected_fleet_registry: FleetRegistryVersion {
833                authority: fleet_registry_authority(),
834                revision: 4,
835                content_hash: [5; 32],
836            },
837        };
838        let response = RootComponentRegistryStatusResponse {
839            fleet_subnet_root: Principal::from_slice(&[6; 29]),
840            prepared_against_registry: request.expected_fleet_registry.clone(),
841            release_set: FleetSubnetRootReleaseSet {
842                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
843                    [7; 32],
844                )),
845                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
846            },
847            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
848            next_allocation_sequence: 1,
849            reserved_component_instances: 0,
850            committed_component_instances: 0,
851            managed_descendants: 0,
852            known_created_component_canisters: 0,
853            encoded_bytes: 0,
854            initial_inventory: Some(RootComponentInitialInventoryStatus {
855                fleet_activation_operation_id: [10; 32],
856                component_count: 0,
857                inventory_hash: [11; 32],
858                sealed_at_ns: 12,
859                directories_converged: true,
860                root_runtime_activated: true,
861            }),
862        };
863        let allocation = RootComponentAllocationResponse {
864            operation_id: [10; 32],
865            allocation_sequence: 1,
866            component: ComponentInstanceId::from_generated_bytes([11; 32]),
867            component_spec: "projects".parse().expect("Component Spec ID"),
868            spec_hash: [12; 32],
869            role: CanisterRole::new("project_hub"),
870            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
871                caller: Principal::from_slice(&[13; 29]),
872            },
873            release_set: response.release_set,
874            phase: RootComponentAllocationPhase::Reserved,
875            creation: None,
876            installation: None,
877        };
878        let created = RootComponentAllocationResponse {
879            phase: RootComponentAllocationPhase::Created,
880            creation: Some(RootComponentCreationEvidence {
881                wasm_store: Principal::from_slice(&[14; 29]),
882                payload_hash: [15; 32],
883                payload_size_bytes: 4_096,
884                initial_cycles: Cycles::new(5_000_000_000_000),
885                controller: Principal::from_slice(&[6; 29]),
886                canister: Some(Principal::from_slice(&[16; 29])),
887            }),
888            installation: None,
889            ..allocation.clone()
890        };
891        let request_bytes = candid::encode_one(&request).expect("encode request");
892        let response_bytes = candid::encode_one(&response).expect("encode response");
893        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
894        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
895
896        assert_eq!(
897            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
898                .expect("decode request"),
899            request
900        );
901        assert_eq!(
902            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
903                .expect("decode response"),
904            response
905        );
906        assert_eq!(
907            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
908                .expect("decode allocation"),
909            allocation
910        );
911        assert_eq!(
912            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
913                .expect("decode created allocation"),
914            created
915        );
916    }
917
918    #[test]
919    fn component_commit_response_round_trips_through_candid() {
920        let root = Principal::from_slice(&[6; 29]);
921        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
922        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
923        let release_set = FleetSubnetRootReleaseSet {
924            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
925                [7; 32],
926            )),
927            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
928        };
929        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
930            caller: Principal::from_slice(&[13; 29]),
931        };
932        let binding = ComponentBinding {
933            authority: fleet_registry_authority(),
934            component,
935            component_spec: component_spec.clone(),
936            spec_hash: [12; 32],
937            role: CanisterRole::new("project_hub"),
938            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
939            fleet_subnet_root: root,
940            canister_id: Principal::from_slice(&[16; 29]),
941        };
942        let head = ComponentRegistryHead {
943            component,
944            revision: 1,
945            content_hash: [18; 32],
946        };
947        let committed = RootComponentCommitResponse {
948            allocation: RootComponentAllocationResponse {
949                operation_id: [10; 32],
950                allocation_sequence: 1,
951                component,
952                component_spec,
953                spec_hash: binding.spec_hash,
954                role: binding.role.clone(),
955                provisioning_origin: provisioning_origin.clone(),
956                release_set,
957                phase: RootComponentAllocationPhase::Committed,
958                creation: Some(RootComponentCreationEvidence {
959                    wasm_store: Principal::from_slice(&[14; 29]),
960                    payload_hash: [15; 32],
961                    payload_size_bytes: 4_096,
962                    initial_cycles: Cycles::new(5_000_000_000_000),
963                    controller: root,
964                    canister: Some(binding.canister_id),
965                }),
966                installation: Some(RootComponentInstallEvidence {
967                    raw_module_hash: [20; 32],
968                    chunk_hashes: vec![vec![21; 32]],
969                    binding: binding.clone(),
970                }),
971            },
972            registry: ComponentRegistryPartitionResponse {
973                head: head.clone(),
974                binding: binding.clone(),
975                provisioning_origin,
976                release_set,
977                status: ComponentLifecycleStatus::Prepared,
978                reserved_descendants: 0,
979                committed_descendants: 0,
980                encoded_bytes: 2_048,
981            },
982            directory: ComponentDirectoryHead {
983                provenance: ComponentDirectoryProvenance {
984                    component: binding,
985                    source_fleet_subnet_root: root,
986                    component_registry_revision: head.revision,
987                    component_registry_content_hash: head.content_hash,
988                    synchronized_at_ns: 19,
989                },
990                descendant_count: 0,
991            },
992        };
993        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
994
995        assert_eq!(
996            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
997                .expect("decode committed allocation"),
998            committed
999        );
1000    }
1001
1002    #[test]
1003    fn component_directory_page_contracts_round_trip_through_candid() {
1004        let root = Principal::from_slice(&[6; 29]);
1005        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1006        let binding = ComponentBinding {
1007            authority: fleet_registry_authority(),
1008            component,
1009            component_spec: "projects".parse().expect("Component Spec ID"),
1010            spec_hash: [12; 32],
1011            role: CanisterRole::new("project_hub"),
1012            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1013            fleet_subnet_root: root,
1014            canister_id: Principal::from_slice(&[16; 29]),
1015        };
1016        let directory = ComponentDirectoryHead {
1017            provenance: ComponentDirectoryProvenance {
1018                component: binding.clone(),
1019                source_fleet_subnet_root: root,
1020                component_registry_revision: 3,
1021                component_registry_content_hash: [18; 32],
1022                synchronized_at_ns: 19,
1023            },
1024            descendant_count: 1,
1025        };
1026        let request = ComponentDirectoryPageRequest {
1027            directory: directory.clone(),
1028            parent_canister_id: Some(binding.canister_id),
1029            role: Some(CanisterRole::new("project_instance")),
1030            status: Some(ComponentLifecycleStatus::Active),
1031            cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1032            limit: 50,
1033        };
1034        let response = ComponentDirectoryPageResponse {
1035            directory,
1036            entries: vec![ComponentDirectoryChildEntry {
1037                binding: ComponentChildBinding {
1038                    component: binding.clone(),
1039                    parent_canister_id: binding.canister_id,
1040                    role: CanisterRole::new("project_instance"),
1041                    canister_id: Principal::from_slice(&[21; 29]),
1042                },
1043                kind: ComponentChildKind::Instance,
1044                installed_artifact_hash: [22; 32],
1045                status: ComponentLifecycleStatus::Active,
1046            }],
1047            next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1048        };
1049        let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1050        let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1051
1052        assert_eq!(
1053            candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1054                .expect("decode Directory page request"),
1055            request
1056        );
1057        assert_eq!(
1058            candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1059                .expect("decode Directory page response"),
1060            response
1061        );
1062    }
1063
1064    fn fleet_registry_authority() -> FleetRegistryAuthority {
1065        FleetRegistryAuthority {
1066            binding: FleetCoordinatorBinding {
1067                fleet: crate::ids::FleetBinding {
1068                    fleet: FleetKey {
1069                        canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1070                        fleet_id: FleetId::from_generated_bytes([1; 32]),
1071                    },
1072                    app: AppId::from("toko"),
1073                },
1074                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1075                coordinator: Principal::from_slice(&[3; 29]),
1076            },
1077            epoch: 1,
1078        }
1079    }
1080
1081    #[test]
1082    fn component_creation_request_round_trips_through_candid() {
1083        let request = RootComponentCreationRequest {
1084            operation_id: [10; 32],
1085        };
1086        let bytes = candid::encode_one(request).expect("encode creation request");
1087
1088        assert_eq!(
1089            candid::decode_one::<RootComponentCreationRequest>(&bytes)
1090                .expect("decode creation request"),
1091            request
1092        );
1093    }
1094
1095    #[test]
1096    fn component_subtree_removal_contracts_round_trip_through_candid() {
1097        let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1098        let registry = ComponentRegistryHead {
1099            component,
1100            revision: 7,
1101            content_hash: [42; 32],
1102        };
1103        let request = RootComponentSubtreeRemovalRequest {
1104            operation_id: [43; 32],
1105            component,
1106            target_canister_id: Principal::from_slice(&[44; 29]),
1107            expected_registry: registry.clone(),
1108        };
1109        let status_request = RootComponentSubtreeRemovalStatusRequest {
1110            operation_id: request.operation_id,
1111            component,
1112        };
1113        let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1114            operation_id: request.operation_id,
1115            component,
1116            expected_traversal_steps: 1,
1117        };
1118        let response = RootComponentSubtreeRemovalResponse {
1119            operation_id: request.operation_id,
1120            component,
1121            target_canister_id: request.target_canister_id,
1122            target_parent_canister_id: Principal::from_slice(&[45; 29]),
1123            target_role: CanisterRole::new("project_instance"),
1124            target_status: ComponentLifecycleStatus::Active,
1125            reserved_against_registry: registry,
1126            traversal_steps: 2,
1127            phase: RootComponentSubtreeRemovalPhase::LeafSelected(
1128                RootComponentSubtreeRemovalNode {
1129                    canister_id: Principal::from_slice(&[46; 29]),
1130                    parent_canister_id: request.target_canister_id,
1131                    role: CanisterRole::new("project_ledger"),
1132                    kind: ComponentChildKind::Singleton,
1133                    installed_artifact_hash: [47; 32],
1134                    status: ComponentLifecycleStatus::Active,
1135                },
1136            ),
1137        };
1138
1139        let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1140        let advance_bytes =
1141            candid::encode_one(advance_request).expect("encode subtree removal advance request");
1142        let status_bytes =
1143            candid::encode_one(status_request).expect("encode subtree removal status request");
1144        let response_bytes =
1145            candid::encode_one(&response).expect("encode subtree removal response");
1146
1147        assert_eq!(
1148            candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1149                .expect("decode subtree removal request"),
1150            request
1151        );
1152        assert_eq!(
1153            candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1154                .expect("decode subtree removal advance request"),
1155            advance_request
1156        );
1157        assert_eq!(
1158            candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1159                .expect("decode subtree removal status request"),
1160            status_request
1161        );
1162        assert_eq!(
1163            candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1164                .expect("decode subtree removal response"),
1165            response
1166        );
1167    }
1168
1169    #[test]
1170    #[expect(
1171        clippy::too_many_lines,
1172        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1173    )]
1174    fn component_child_lifecycle_contracts_round_trip_through_candid() {
1175        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1176        let registry = ComponentRegistryHead {
1177            component,
1178            revision: 2,
1179            content_hash: [12; 32],
1180        };
1181        let request = RootComponentChildAllocationRequest {
1182            operation_id: [13; 32],
1183            component,
1184            expected_registry: registry.clone(),
1185            child_role: CanisterRole::new("project_instance"),
1186        };
1187        let status_request = RootComponentChildAllocationStatusRequest {
1188            operation_id: request.operation_id,
1189            component,
1190        };
1191        let creation_request = RootComponentChildCreationRequest {
1192            operation_id: request.operation_id,
1193            component,
1194        };
1195        let install_request = RootComponentChildInstallRequest {
1196            operation_id: request.operation_id,
1197            component,
1198        };
1199        let commit_request = RootComponentChildCommitRequest {
1200            operation_id: request.operation_id,
1201            component,
1202        };
1203        let directory_request = RootComponentChildDirectoryPreparationRequest {
1204            operation_id: request.operation_id,
1205            component,
1206        };
1207        let activation_request = RootComponentChildRuntimeActivationRequest {
1208            operation_id: request.operation_id,
1209            component,
1210        };
1211        let membership_request = RootComponentChildMembershipActivationRequest {
1212            operation_id: request.operation_id,
1213            component,
1214        };
1215        let root = Principal::from_slice(&[17; 29]);
1216        let parent = Principal::from_slice(&[14; 29]);
1217        let child = Principal::from_slice(&[18; 29]);
1218        let child_binding = ComponentChildBinding {
1219            component: ComponentBinding {
1220                authority: fleet_registry_authority(),
1221                component,
1222                component_spec: "projects".parse().expect("Component Spec"),
1223                spec_hash: [19; 32],
1224                role: CanisterRole::new("project_hub"),
1225                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1226                fleet_subnet_root: root,
1227                canister_id: parent,
1228            },
1229            parent_canister_id: parent,
1230            role: request.child_role.clone(),
1231            canister_id: child,
1232        };
1233        let response = RootComponentChildAllocationResponse {
1234            operation_id: request.operation_id,
1235            component,
1236            parent_canister_id: parent,
1237            parent_role: CanisterRole::new("project_hub"),
1238            child_role: request.child_role.clone(),
1239            child_kind: ComponentChildKind::Instance,
1240            maximum_instances_per_parent: 10_000,
1241            maximum_descendants: 20_000,
1242            maximum_registry_bytes: 16_777_216,
1243            reserved_against_registry: registry,
1244            release_set: FleetSubnetRootReleaseSet {
1245                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1246                    [15; 32],
1247                )),
1248                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1249            },
1250            phase: RootComponentAllocationPhase::Verified,
1251            creation: Some(RootComponentCreationEvidence {
1252                wasm_store: Principal::from_slice(&[21; 29]),
1253                payload_hash: [22; 32],
1254                payload_size_bytes: 4_096,
1255                initial_cycles: Cycles::new(5_000_000_000_000),
1256                controller: root,
1257                canister: Some(child),
1258            }),
1259            installation: Some(RootComponentChildInstallEvidence {
1260                raw_module_hash: [23; 32],
1261                chunk_hashes: vec![vec![24; 32]],
1262                binding: child_binding.clone(),
1263            }),
1264        };
1265        let commit_response = RootComponentChildCommitResponse {
1266            allocation: response.clone(),
1267            registry: ComponentRegistryPartitionResponse {
1268                head: ComponentRegistryHead {
1269                    component,
1270                    revision: 3,
1271                    content_hash: [25; 32],
1272                },
1273                binding: child_binding.component.clone(),
1274                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1275                    caller: Principal::from_slice(&[26; 29]),
1276                },
1277                release_set: response.release_set,
1278                status: ComponentLifecycleStatus::Active,
1279                reserved_descendants: 0,
1280                committed_descendants: 1,
1281                encoded_bytes: 8_192,
1282            },
1283            directory: ComponentDirectoryHead {
1284                provenance: ComponentDirectoryProvenance {
1285                    component: child_binding.component.clone(),
1286                    source_fleet_subnet_root: root,
1287                    component_registry_revision: 3,
1288                    component_registry_content_hash: [25; 32],
1289                    synchronized_at_ns: 27,
1290                },
1291                descendant_count: 1,
1292            },
1293        };
1294        let runtime_authority = ComponentRuntimeDirectoryAuthority {
1295            fleet: FleetDirectorySnapshot {
1296                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1297                    registry: FleetRegistryVersion {
1298                        authority: fleet_registry_authority(),
1299                        revision: 4,
1300                        content_hash: [28; 32],
1301                    },
1302                    source_fleet_subnet_root: root,
1303                },
1304                fleet_subnet_roots: vec![
1305                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1306                        placement_subnet: commit_response.registry.binding.placement_subnet,
1307                        fleet_subnet_root: root,
1308                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1309                    },
1310                ],
1311            },
1312            component: commit_response.directory.clone(),
1313        };
1314        let activation = ComponentRuntimeActivationEvidence {
1315            directory_authority_hash: [29; 32],
1316            activated_at_ns: 30,
1317        };
1318        let directory_response = RootComponentChildDirectoryPreparationResponse {
1319            committed: commit_response.clone(),
1320            child: ComponentRuntimeStatusResponse {
1321                operation_id: request.operation_id,
1322                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1323                phase: ComponentRuntimePhase::DirectoryPrepared,
1324                authority: Some(runtime_authority.clone()),
1325                authority_hash: Some([31; 32]),
1326                activation: None,
1327            },
1328            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1329                operation_id: [32; 32],
1330                binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1331                covered_authority: runtime_authority.clone(),
1332                covered_authority_hash: [31; 32],
1333                activation,
1334            },
1335            parent: None,
1336        };
1337        let activation_response = RootComponentChildRuntimeActivationResponse {
1338            committed: commit_response.clone(),
1339            child: ComponentRuntimeStatusResponse {
1340                operation_id: request.operation_id,
1341                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1342                phase: ComponentRuntimePhase::Active,
1343                authority: Some(runtime_authority.clone()),
1344                authority_hash: Some([31; 32]),
1345                activation: Some(ComponentRuntimeActivationEvidence {
1346                    directory_authority_hash: [31; 32],
1347                    activated_at_ns: 33,
1348                }),
1349            },
1350        };
1351        let active_directory = ComponentDirectoryHead {
1352            provenance: ComponentDirectoryProvenance {
1353                component: child_binding.component.clone(),
1354                source_fleet_subnet_root: root,
1355                component_registry_revision: 4,
1356                component_registry_content_hash: [34; 32],
1357                synchronized_at_ns: 35,
1358            },
1359            descendant_count: 1,
1360        };
1361        let active_authority = ComponentRuntimeDirectoryAuthority {
1362            fleet: runtime_authority.fleet,
1363            component: active_directory.clone(),
1364        };
1365        let membership_response = RootComponentChildMembershipActivationResponse {
1366            committed: commit_response.clone(),
1367            registry: ComponentRegistryPartitionResponse {
1368                head: ComponentRegistryHead {
1369                    component,
1370                    revision: 4,
1371                    content_hash: [34; 32],
1372                },
1373                binding: child_binding.component.clone(),
1374                provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1375                release_set: commit_response.registry.release_set,
1376                status: ComponentLifecycleStatus::Active,
1377                reserved_descendants: 0,
1378                committed_descendants: 1,
1379                encoded_bytes: 8_256,
1380            },
1381            directory: active_directory,
1382            child: ComponentRuntimeStatusResponse {
1383                operation_id: request.operation_id,
1384                binding: ManagedCanisterBinding::ComponentChild(child_binding),
1385                phase: ComponentRuntimePhase::Active,
1386                authority: Some(active_authority),
1387                authority_hash: Some([36; 32]),
1388                activation: Some(ComponentRuntimeActivationEvidence {
1389                    directory_authority_hash: [31; 32],
1390                    activated_at_ns: 33,
1391                }),
1392            },
1393        };
1394
1395        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1396        let status_bytes =
1397            candid::encode_one(status_request).expect("encode child reservation status");
1398        let creation_bytes =
1399            candid::encode_one(creation_request).expect("encode child creation request");
1400        let install_bytes =
1401            candid::encode_one(install_request).expect("encode child install request");
1402        let response_bytes = candid::encode_one(&response).expect("encode child response");
1403        let commit_request_bytes =
1404            candid::encode_one(commit_request).expect("encode child commit request");
1405        let directory_request_bytes =
1406            candid::encode_one(directory_request).expect("encode child Directory request");
1407        let activation_request_bytes =
1408            candid::encode_one(activation_request).expect("encode child activation request");
1409        let membership_request_bytes =
1410            candid::encode_one(membership_request).expect("encode child membership request");
1411        let commit_response_bytes =
1412            candid::encode_one(&commit_response).expect("encode child commit response");
1413        let directory_response_bytes =
1414            candid::encode_one(&directory_response).expect("encode child Directory response");
1415        let activation_response_bytes =
1416            candid::encode_one(&activation_response).expect("encode child activation response");
1417        let membership_response_bytes =
1418            candid::encode_one(&membership_response).expect("encode child membership response");
1419
1420        assert_eq!(
1421            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1422                .expect("decode child reservation"),
1423            request
1424        );
1425        assert_eq!(
1426            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1427                .expect("decode child reservation status"),
1428            status_request
1429        );
1430        assert_eq!(
1431            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1432                .expect("decode child creation request"),
1433            creation_request
1434        );
1435        assert_eq!(
1436            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1437                .expect("decode child install request"),
1438            install_request
1439        );
1440        assert_eq!(
1441            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1442                .expect("decode child response"),
1443            response
1444        );
1445        assert_eq!(
1446            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1447                .expect("decode child commit request"),
1448            commit_request
1449        );
1450        assert_eq!(
1451            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1452                &directory_request_bytes
1453            )
1454            .expect("decode child Directory request"),
1455            directory_request
1456        );
1457        assert_eq!(
1458            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1459                .expect("decode child commit response"),
1460            commit_response
1461        );
1462        assert_eq!(
1463            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1464                &directory_response_bytes
1465            )
1466            .expect("decode child Directory response"),
1467            directory_response
1468        );
1469        assert_eq!(
1470            candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1471                &activation_request_bytes
1472            )
1473            .expect("decode child activation request"),
1474            activation_request
1475        );
1476        assert_eq!(
1477            candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1478                &activation_response_bytes
1479            )
1480            .expect("decode child activation response"),
1481            activation_response
1482        );
1483        assert_eq!(
1484            candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1485                &membership_request_bytes
1486            )
1487            .expect("decode child membership request"),
1488            membership_request
1489        );
1490        assert_eq!(
1491            candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1492                &membership_response_bytes
1493            )
1494            .expect("decode child membership response"),
1495            membership_response
1496        );
1497    }
1498
1499    #[test]
1500    fn component_install_request_round_trips_through_candid() {
1501        let request = RootComponentInstallRequest {
1502            operation_id: [10; 32],
1503        };
1504        let bytes = candid::encode_one(request).expect("encode install request");
1505
1506        assert_eq!(
1507            candid::decode_one::<RootComponentInstallRequest>(&bytes)
1508                .expect("decode install request"),
1509            request
1510        );
1511    }
1512
1513    #[test]
1514    fn component_commit_request_round_trips_through_candid() {
1515        let request = RootComponentCommitRequest {
1516            operation_id: [10; 32],
1517        };
1518        let bytes = candid::encode_one(request).expect("encode commit request");
1519
1520        assert_eq!(
1521            candid::decode_one::<RootComponentCommitRequest>(&bytes)
1522                .expect("decode commit request"),
1523            request
1524        );
1525    }
1526
1527    #[test]
1528    fn component_runtime_activation_requests_round_trip_through_candid() {
1529        let root_request = RootComponentRuntimeActivationRequest {
1530            operation_id: [22; 32],
1531        };
1532        let target_request = ComponentRuntimeActivationRequest {
1533            operation_id: root_request.operation_id,
1534            directory_authority_hash: [23; 32],
1535        };
1536        let membership_request = RootComponentMembershipActivationRequest {
1537            operation_id: root_request.operation_id,
1538        };
1539        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1540        let target_bytes =
1541            candid::encode_one(target_request).expect("encode target activation request");
1542        let membership_bytes =
1543            candid::encode_one(membership_request).expect("encode membership activation request");
1544
1545        assert_eq!(
1546            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1547                .expect("decode root activation request"),
1548            root_request
1549        );
1550        assert_eq!(
1551            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1552                .expect("decode target activation request"),
1553            target_request
1554        );
1555        assert_eq!(
1556            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1557                .expect("decode membership activation request"),
1558            membership_request
1559        );
1560    }
1561}