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/// RootComponentSubtreeRemovalStopPreparationRequest
150///
151/// Controller command freezing the exact selected leaf and root stop authority.
152///
153
154#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct RootComponentSubtreeRemovalStopPreparationRequest {
156    pub operation_id: [u8; 32],
157    pub component: ComponentInstanceId,
158    pub expected_traversal_steps: u32,
159    pub expected_leaf_canister_id: Principal,
160    pub expected_leaf_parent_canister_id: Principal,
161}
162
163///
164/// RootComponentSubtreeRemovalStopRequest
165///
166/// Controller command reconciling and stopping one exactly prepared leaf.
167///
168
169#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
170pub struct RootComponentSubtreeRemovalStopRequest {
171    pub operation_id: [u8; 32],
172    pub component: ComponentInstanceId,
173    pub expected_traversal_steps: u32,
174    pub expected_leaf_canister_id: Principal,
175    pub expected_leaf_parent_canister_id: Principal,
176}
177
178///
179/// RootComponentSubtreeRemovalDeletePreparationRequest
180///
181/// Controller command freezing exact deletion authority from one stopped receipt.
182///
183
184#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
185pub struct RootComponentSubtreeRemovalDeletePreparationRequest {
186    pub operation_id: [u8; 32],
187    pub component: ComponentInstanceId,
188    pub expected_traversal_steps: u32,
189    pub expected_leaf_canister_id: Principal,
190    pub expected_leaf_parent_canister_id: Principal,
191}
192
193///
194/// RootComponentSubtreeRemovalDeleteRequest
195///
196/// Controller command reconciling and deleting one exactly prepared leaf.
197///
198
199#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct RootComponentSubtreeRemovalDeleteRequest {
201    pub operation_id: [u8; 32],
202    pub component: ComponentInstanceId,
203    pub expected_traversal_steps: u32,
204    pub expected_leaf_canister_id: Principal,
205    pub expected_leaf_parent_canister_id: Principal,
206}
207
208///
209/// RootComponentSubtreeRemovalMembershipRemovalRequest
210///
211/// Controller command removing one independently deleted leaf from Registry membership.
212///
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct RootComponentSubtreeRemovalMembershipRemovalRequest {
216    pub operation_id: [u8; 32],
217    pub component: ComponentInstanceId,
218    pub expected_traversal_steps: u32,
219    pub expected_leaf_canister_id: Principal,
220    pub expected_leaf_parent_canister_id: Principal,
221}
222
223///
224/// RootComponentSubtreeRemovalDirectorySynchronizationRequest
225///
226/// Controller command converging the post-removal Directory on surviving members.
227///
228
229#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct RootComponentSubtreeRemovalDirectorySynchronizationRequest {
231    pub operation_id: [u8; 32],
232    pub component: ComponentInstanceId,
233    pub expected_traversal_steps: u32,
234    pub expected_leaf_canister_id: Principal,
235    pub expected_leaf_parent_canister_id: Principal,
236}
237
238///
239/// RootComponentSubtreeRemovalLeafFinalizationRequest
240///
241/// Controller command archiving one completed leaf and resuming its retained parent.
242///
243
244#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
245pub struct RootComponentSubtreeRemovalLeafFinalizationRequest {
246    pub operation_id: [u8; 32],
247    pub component: ComponentInstanceId,
248    pub expected_traversal_steps: u32,
249    pub expected_leaf_canister_id: Principal,
250    pub expected_leaf_parent_canister_id: Principal,
251}
252
253///
254/// RootComponentSubtreeRemovalStatusRequest
255///
256/// Controller lookup key for one durable child-subtree removal operation.
257///
258
259#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
260pub struct RootComponentSubtreeRemovalStatusRequest {
261    pub operation_id: [u8; 32],
262    pub component: ComponentInstanceId,
263}
264
265///
266/// RootComponentDrainingRequest
267///
268/// Controller command fencing one exact active Component against new mutation.
269///
270
271#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
272pub struct RootComponentDrainingRequest {
273    pub operation_id: [u8; 32],
274    pub component: ComponentInstanceId,
275    pub expected_registry: ComponentRegistryHead,
276}
277
278///
279/// RootComponentDrainingStatusRequest
280///
281/// Read-only lookup key for one durable Component-draining operation.
282///
283
284#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
285pub struct RootComponentDrainingStatusRequest {
286    pub operation_id: [u8; 32],
287    pub component: ComponentInstanceId,
288}
289
290///
291/// RootComponentChildCreationRequest
292///
293/// Parent command continuing one already reserved direct-child operation.
294///
295
296#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct RootComponentChildCreationRequest {
298    pub operation_id: [u8; 32],
299    pub component: ComponentInstanceId,
300}
301
302///
303/// RootComponentChildInstallRequest
304///
305/// Parent command installing and verifying one already created direct-child operation.
306///
307
308#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
309pub struct RootComponentChildInstallRequest {
310    pub operation_id: [u8; 32],
311    pub component: ComponentInstanceId,
312}
313
314///
315/// RootComponentChildCommitRequest
316///
317/// Parent command committing one already verified direct-child operation.
318///
319
320#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct RootComponentChildCommitRequest {
322    pub operation_id: [u8; 32],
323    pub component: ComponentInstanceId,
324}
325
326///
327/// RootComponentChildDirectoryPreparationRequest
328///
329/// Parent command distributing one committed child's Directory and converging its affected members.
330///
331
332#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
333pub struct RootComponentChildDirectoryPreparationRequest {
334    pub operation_id: [u8; 32],
335    pub component: ComponentInstanceId,
336}
337
338///
339/// RootComponentChildRuntimeActivationRequest
340///
341/// Parent command activating one Directory-prepared direct-child runtime.
342///
343
344#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
345pub struct RootComponentChildRuntimeActivationRequest {
346    pub operation_id: [u8; 32],
347    pub component: ComponentInstanceId,
348}
349
350///
351/// RootComponentChildMembershipActivationRequest
352///
353/// Parent command activating one runtime-active direct child's Registry membership.
354///
355
356#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
357pub struct RootComponentChildMembershipActivationRequest {
358    pub operation_id: [u8; 32],
359    pub component: ComponentInstanceId,
360}
361
362///
363/// RootComponentCreationRequest
364///
365/// Controller command continuing one already reserved top-level Component operation.
366///
367
368#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
369pub struct RootComponentCreationRequest {
370    pub operation_id: [u8; 32],
371}
372
373///
374/// RootComponentInstallRequest
375///
376/// Controller command continuing one already created top-level Component operation.
377///
378
379#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
380pub struct RootComponentInstallRequest {
381    pub operation_id: [u8; 32],
382}
383
384///
385/// RootComponentCommitRequest
386///
387/// Controller command committing one already verified top-level Component operation.
388///
389
390#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
391pub struct RootComponentCommitRequest {
392    pub operation_id: [u8; 32],
393}
394
395///
396/// RootComponentDirectoryPreparationRequest
397///
398/// Controller command distributing exact Directories to one committed top-level Component.
399///
400
401#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
402pub struct RootComponentDirectoryPreparationRequest {
403    pub operation_id: [u8; 32],
404}
405
406///
407/// RootComponentRuntimeActivationRequest
408///
409/// Controller command activating one Directory-prepared top-level Component runtime.
410///
411
412#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
413pub struct RootComponentRuntimeActivationRequest {
414    pub operation_id: [u8; 32],
415}
416
417///
418/// RootComponentMembershipActivationRequest
419///
420/// Controller command activating one runtime-active Component's Registry membership.
421///
422
423#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
424pub struct RootComponentMembershipActivationRequest {
425    pub operation_id: [u8; 32],
426}
427
428///
429/// ComponentProvisioningOrigin
430///
431/// Authenticated causal authority retained with one top-level Component allocation.
432///
433
434#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
435pub enum ComponentProvisioningOrigin {
436    FleetAdministrator { caller: Principal },
437}
438
439///
440/// RootComponentAllocationPhase
441///
442/// Durable root-local progress of one top-level Component allocation operation.
443///
444
445#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
446pub enum RootComponentAllocationPhase {
447    Reserved,
448    CreationIntent,
449    Created,
450    InstallIntent,
451    Installed,
452    Verified,
453    Committed,
454}
455
456///
457/// RootComponentSubtreeRemovalPhase
458///
459/// Durable root-local progress of one child-subtree removal operation.
460///
461
462#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
463#[expect(
464    clippy::large_enum_variant,
465    reason = "wire phases retain complete inline receipts for deterministic Candid responses"
466)]
467pub enum RootComponentSubtreeRemovalPhase {
468    Fenced,
469    Traversing(RootComponentSubtreeRemovalNode),
470    LeafSelected(RootComponentSubtreeRemovalNode),
471    StopIntent(RootComponentSubtreeRemovalStopIntent),
472    Stopped(RootComponentSubtreeRemovalStoppedReceipt),
473    DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
474    Deleted(RootComponentSubtreeRemovalDeletedReceipt),
475    MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
476    DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
477    Completed(RootComponentSubtreeRemovalCompletedReceipt),
478}
479
480///
481/// RootComponentSubtreeRemovalNode
482///
483/// Exact registered child selected as a traversal cursor or removable leaf.
484///
485
486#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
487pub struct RootComponentSubtreeRemovalNode {
488    pub canister_id: Principal,
489    pub parent_canister_id: Principal,
490    pub role: CanisterRole,
491    pub kind: ComponentChildKind,
492    pub installed_artifact_hash: [u8; 32],
493    pub status: ComponentLifecycleStatus,
494}
495
496///
497/// RootComponentSubtreeRemovalStopIntent
498///
499/// Exact registered leaf and sole root controller frozen before a stop call.
500///
501
502#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
503pub struct RootComponentSubtreeRemovalStopIntent {
504    pub leaf: RootComponentSubtreeRemovalNode,
505    pub controller: Principal,
506}
507
508///
509/// RootComponentSubtreeRemovalStoppedReceipt
510///
511/// Frozen stop authority plus the independently observed installed module.
512///
513
514#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
515pub struct RootComponentSubtreeRemovalStoppedReceipt {
516    pub stop: RootComponentSubtreeRemovalStopIntent,
517    pub observed_module_hash: [u8; 32],
518}
519
520///
521/// RootComponentSubtreeRemovalDeleteIntent
522///
523/// Exact stopped receipt frozen before the destructive management call.
524///
525
526#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
527pub struct RootComponentSubtreeRemovalDeleteIntent {
528    pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
529}
530
531///
532/// RootComponentSubtreeRemovalDeletedReceipt
533///
534/// Frozen deletion authority committed only after independently observed absence.
535///
536
537#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
538pub struct RootComponentSubtreeRemovalDeletedReceipt {
539    pub deletion: RootComponentSubtreeRemovalDeleteIntent,
540}
541
542///
543/// RootComponentSubtreeRemovalMembershipRemovedReceipt
544///
545/// Exact Registry transition retained after the independently deleted leaf is unregistered.
546///
547
548#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
549pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
550    pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
551    pub removed_from_registry: ComponentRegistryHead,
552    pub previous_descendant_content_hash: [u8; 32],
553    pub previous_committed_descendants: u32,
554    pub registry: ComponentRegistryHead,
555    pub descendant_content_hash: [u8; 32],
556    pub registry_encoded_bytes: u64,
557    pub reserved_descendants: u32,
558    pub committed_descendants: u32,
559    pub directory_synchronized_at_ns: u64,
560    pub directory_authority_hash: [u8; 32],
561    pub parent_role_instances: u32,
562    pub root_managed_descendants: u32,
563    pub root_known_created_component_canisters: u32,
564}
565
566///
567/// RootComponentSubtreeRemovalDirectoryConvergenceEvidence
568///
569/// Compact durable proof that one surviving member covered the required Directory.
570///
571
572#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
573pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
574    pub operation_id: [u8; 32],
575    pub canister_id: Principal,
576    pub activation: ComponentRuntimeActivationEvidence,
577}
578
579///
580/// RootComponentSubtreeRemovalDirectorySynchronizedReceipt
581///
582/// Membership removal plus independently verified surviving-member convergence.
583///
584
585#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
586pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
587    pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
588    pub covered_fleet_registry_revision: u64,
589    pub covered_fleet_registry_content_hash: [u8; 32],
590    pub covered_component_registry: ComponentRegistryHead,
591    pub covered_authority_hash: [u8; 32],
592    pub owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence,
593    pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
594}
595
596///
597/// RootComponentSubtreeRemovalCompletedReceipt
598///
599/// Terminal Registry and Directory authority after the fenced target is finalized.
600///
601
602#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603pub struct RootComponentSubtreeRemovalCompletedReceipt {
604    pub registry: ComponentRegistryHead,
605    pub directory_authority_hash: [u8; 32],
606}
607
608///
609/// ComponentLifecycleStatus
610///
611/// Root-owned runtime lifecycle state of one committed Component Registry member.
612///
613
614#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
615pub enum ComponentLifecycleStatus {
616    Prepared,
617    Active,
618    Draining,
619    Removed,
620}
621
622///
623/// ComponentRegistryHead
624///
625/// Exact independently versioned authority of one Component Registry partition.
626///
627
628#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
629pub struct ComponentRegistryHead {
630    pub component: ComponentInstanceId,
631    pub revision: u64,
632    pub content_hash: [u8; 32],
633}
634
635///
636/// ComponentRegistryPartitionRequest
637///
638/// Read-only lookup key for one committed Component Registry partition.
639///
640
641#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub struct ComponentRegistryPartitionRequest {
643    pub component: ComponentInstanceId,
644}
645
646///
647/// ComponentRegistryPartitionResponse
648///
649/// Protected top-level row and independent head of one Component Registry partition.
650///
651
652#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
653pub struct ComponentRegistryPartitionResponse {
654    pub head: ComponentRegistryHead,
655    pub binding: ComponentBinding,
656    pub provisioning_origin: ComponentProvisioningOrigin,
657    pub release_set: FleetSubnetRootReleaseSet,
658    pub status: ComponentLifecycleStatus,
659    pub reserved_descendants: u32,
660    pub committed_descendants: u32,
661    pub encoded_bytes: u64,
662}
663
664///
665/// ComponentDirectoryProvenance
666///
667/// Exact Component Registry authority from which one Component Directory is derived.
668///
669
670#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
671pub struct ComponentDirectoryProvenance {
672    pub component: ComponentBinding,
673    pub source_fleet_subnet_root: Principal,
674    pub component_registry_revision: u64,
675    pub component_registry_content_hash: [u8; 32],
676    pub synchronized_at_ns: u64,
677}
678
679///
680/// ComponentDirectoryHead
681///
682/// Compact independently versioned discovery projection for one Component tree.
683///
684
685#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
686pub struct ComponentDirectoryHead {
687    pub provenance: ComponentDirectoryProvenance,
688    pub descendant_count: u32,
689}
690
691///
692/// ComponentDirectoryHeadRequest
693///
694/// Read-only lookup key for one committed Component Directory head.
695///
696
697#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
698pub struct ComponentDirectoryHeadRequest {
699    pub component: ComponentInstanceId,
700}
701
702///
703/// ComponentDirectoryPageCursor
704///
705/// Opaque revision- and filter-bound continuation for one bounded Directory page.
706///
707
708#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
709pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
710
711///
712/// ComponentDirectoryPageRequest
713///
714/// Bounded member query against one exact current Component Directory authority.
715///
716
717#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
718pub struct ComponentDirectoryPageRequest {
719    pub directory: ComponentDirectoryHead,
720    pub parent_canister_id: Option<Principal>,
721    pub role: Option<CanisterRole>,
722    pub status: Option<ComponentLifecycleStatus>,
723    pub cursor: Option<ComponentDirectoryPageCursor>,
724    pub limit: u16,
725}
726
727///
728/// ComponentDirectoryChildEntry
729///
730/// One authoritative normalized child projected with its complete protected binding.
731///
732
733#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
734pub struct ComponentDirectoryChildEntry {
735    pub binding: ComponentChildBinding,
736    pub kind: ComponentChildKind,
737    pub installed_artifact_hash: [u8; 32],
738    pub status: ComponentLifecycleStatus,
739}
740
741///
742/// ComponentDirectoryPageResponse
743///
744/// One bounded caller-scoped page under the exact requested Directory head.
745///
746
747#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
748pub struct ComponentDirectoryPageResponse {
749    pub directory: ComponentDirectoryHead,
750    pub entries: Vec<ComponentDirectoryChildEntry>,
751    pub next_cursor: Option<ComponentDirectoryPageCursor>,
752}
753
754///
755/// ComponentRuntimeDirectoryAuthority
756///
757/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
758///
759
760#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
761pub struct ComponentRuntimeDirectoryAuthority {
762    pub fleet: FleetDirectorySnapshot,
763    pub component: ComponentDirectoryHead,
764}
765
766///
767/// ComponentRuntimeDirectoryPreparationRequest
768///
769/// Root-issued exact Directory preparation command for one managed Component-tree node.
770///
771
772#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
773pub struct ComponentRuntimeDirectoryPreparationRequest {
774    pub operation_id: [u8; 32],
775    pub authority: ComponentRuntimeDirectoryAuthority,
776}
777
778///
779/// ComponentRuntimeDirectorySynchronizationRequest
780///
781/// Root-issued replacement of one active managed Component node's current Directory authority.
782///
783
784#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
785pub struct ComponentRuntimeDirectorySynchronizationRequest {
786    pub operation_id: [u8; 32],
787    pub authority: ComponentRuntimeDirectoryAuthority,
788}
789
790///
791/// ComponentRuntimePhase
792///
793/// Target-local progress from installation through Component runtime activation.
794///
795
796#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
797pub enum ComponentRuntimePhase {
798    AwaitingDirectory,
799    DirectoryPrepared,
800    Active,
801}
802
803///
804/// ComponentRuntimeActivationEvidence
805///
806/// Exact retained Directory authority under which one Component runtime became Active.
807///
808
809#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
810pub struct ComponentRuntimeActivationEvidence {
811    pub directory_authority_hash: [u8; 32],
812    pub activated_at_ns: u64,
813}
814
815///
816/// ComponentRuntimeActivationRequest
817///
818/// Root-issued exact activation command for one Directory-prepared managed Component node.
819///
820
821#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
822pub struct ComponentRuntimeActivationRequest {
823    pub operation_id: [u8; 32],
824    pub directory_authority_hash: [u8; 32],
825}
826
827///
828/// ComponentRuntimeStatusResponse
829///
830/// Independently observable target-local binding and exact retained Directory authority.
831///
832
833#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
834pub struct ComponentRuntimeStatusResponse {
835    pub operation_id: [u8; 32],
836    pub binding: ManagedCanisterBinding,
837    pub phase: ComponentRuntimePhase,
838    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
839    pub authority_hash: Option<[u8; 32]>,
840    pub activation: Option<ComponentRuntimeActivationEvidence>,
841}
842
843///
844/// ComponentRuntimeDirectoryConvergenceEvidence
845///
846/// Stable root evidence that one active member covered at least the required Directory authority.
847///
848
849#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
850pub struct ComponentRuntimeDirectoryConvergenceEvidence {
851    pub operation_id: [u8; 32],
852    pub binding: ManagedCanisterBinding,
853    pub covered_authority: ComponentRuntimeDirectoryAuthority,
854    pub covered_authority_hash: [u8; 32],
855    pub activation: ComponentRuntimeActivationEvidence,
856}
857
858///
859/// RootComponentCreationEvidence
860///
861/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
862///
863
864#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
865pub struct RootComponentCreationEvidence {
866    pub wasm_store: Principal,
867    pub payload_hash: [u8; 32],
868    pub payload_size_bytes: u64,
869    pub initial_cycles: Cycles,
870    pub controller: Principal,
871    pub canister: Option<Principal>,
872}
873
874///
875/// RootComponentInstallEvidence
876///
877/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
878///
879
880#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
881pub struct RootComponentInstallEvidence {
882    pub raw_module_hash: [u8; 32],
883    pub chunk_hashes: Vec<Vec<u8>>,
884    pub binding: ComponentBinding,
885}
886
887///
888/// RootComponentChildInstallEvidence
889///
890/// Exact child module and immutable retained binding frozen before installation.
891///
892
893#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
894pub struct RootComponentChildInstallEvidence {
895    pub raw_module_hash: [u8; 32],
896    pub chunk_hashes: Vec<Vec<u8>>,
897    pub binding: ComponentChildBinding,
898}
899
900///
901/// RootComponentAllocationResponse
902///
903/// Durable identity reservation returned identically for exact operation retry.
904///
905
906#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
907pub struct RootComponentAllocationResponse {
908    pub operation_id: [u8; 32],
909    pub allocation_sequence: u64,
910    pub component: ComponentInstanceId,
911    pub component_spec: ComponentSpecId,
912    pub spec_hash: [u8; 32],
913    pub role: CanisterRole,
914    pub provisioning_origin: ComponentProvisioningOrigin,
915    pub release_set: FleetSubnetRootReleaseSet,
916    pub phase: RootComponentAllocationPhase,
917    pub creation: Option<RootComponentCreationEvidence>,
918    pub installation: Option<RootComponentInstallEvidence>,
919}
920
921///
922/// RootComponentChildAllocationResponse
923///
924/// Durable direct-child lifecycle progress returned identically for exact parent retry.
925///
926
927#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
928pub struct RootComponentChildAllocationResponse {
929    pub operation_id: [u8; 32],
930    pub component: ComponentInstanceId,
931    pub parent_canister_id: Principal,
932    pub parent_role: CanisterRole,
933    pub child_role: CanisterRole,
934    pub child_kind: ComponentChildKind,
935    pub maximum_instances_per_parent: u32,
936    pub maximum_descendants: u32,
937    pub maximum_registry_bytes: u64,
938    pub reserved_against_registry: ComponentRegistryHead,
939    pub release_set: FleetSubnetRootReleaseSet,
940    pub phase: RootComponentAllocationPhase,
941    pub creation: Option<RootComponentCreationEvidence>,
942    pub installation: Option<RootComponentChildInstallEvidence>,
943}
944
945///
946/// RootComponentSubtreeRemovalResponse
947///
948/// Current durable snapshot of one monotonic subtree-removal operation.
949///
950
951#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
952pub struct RootComponentSubtreeRemovalResponse {
953    pub operation_id: [u8; 32],
954    pub component: ComponentInstanceId,
955    pub target_canister_id: Principal,
956    pub target_parent_canister_id: Principal,
957    pub target_role: CanisterRole,
958    pub target_status: ComponentLifecycleStatus,
959    pub reserved_against_registry: ComponentRegistryHead,
960    pub maximum_completed_leaves: u32,
961    pub completed_leaves: u32,
962    pub traversal_steps: u32,
963    pub phase: RootComponentSubtreeRemovalPhase,
964}
965
966///
967/// RootComponentDrainingResponse
968///
969/// Exact Registry and Directory authority produced by the durable draining fence.
970///
971
972#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
973pub struct RootComponentDrainingResponse {
974    pub operation_id: [u8; 32],
975    pub component: ComponentInstanceId,
976    pub previous_registry: ComponentRegistryHead,
977    pub registry: ComponentRegistryHead,
978    pub descendant_count: u32,
979    pub descendant_content_hash: [u8; 32],
980    pub directory_authority_hash: [u8; 32],
981    pub started_at_ns: u64,
982}
983
984///
985/// RootComponentChildCommitResponse
986///
987/// Exact committed child operation, authoritative Component Registry and next Directory head.
988///
989
990#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
991pub struct RootComponentChildCommitResponse {
992    pub allocation: RootComponentChildAllocationResponse,
993    pub registry: ComponentRegistryPartitionResponse,
994    pub directory: ComponentDirectoryHead,
995}
996
997///
998/// RootComponentChildDirectoryPreparationResponse
999///
1000/// Exact child preparation plus stable bounded active-member Directory coverage.
1001///
1002
1003#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1004pub struct RootComponentChildDirectoryPreparationResponse {
1005    pub committed: RootComponentChildCommitResponse,
1006    pub child: ComponentRuntimeStatusResponse,
1007    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1008    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1009}
1010
1011///
1012/// RootComponentChildRuntimeActivationResponse
1013///
1014/// Exact child commitment plus independently observed Directory-bound runtime activation.
1015///
1016
1017#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1018pub struct RootComponentChildRuntimeActivationResponse {
1019    pub committed: RootComponentChildCommitResponse,
1020    pub child: ComponentRuntimeStatusResponse,
1021}
1022
1023///
1024/// RootComponentChildMembershipActivationResponse
1025///
1026/// Original child commitment plus active Registry, Directory and target convergence evidence.
1027///
1028
1029#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030pub struct RootComponentChildMembershipActivationResponse {
1031    pub committed: RootComponentChildCommitResponse,
1032    pub registry: ComponentRegistryPartitionResponse,
1033    pub directory: ComponentDirectoryHead,
1034    pub child: ComponentRuntimeStatusResponse,
1035}
1036
1037///
1038/// RootComponentCommitResponse
1039///
1040/// Exact committed allocation, authoritative Registry row and derived Directory head.
1041///
1042
1043#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1044pub struct RootComponentCommitResponse {
1045    pub allocation: RootComponentAllocationResponse,
1046    pub registry: ComponentRegistryPartitionResponse,
1047    pub directory: ComponentDirectoryHead,
1048}
1049
1050///
1051/// RootComponentDirectoryPreparationResponse
1052///
1053/// Exact root authority plus independently observed target-local Directory preparation.
1054///
1055
1056#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1057pub struct RootComponentDirectoryPreparationResponse {
1058    pub committed: RootComponentCommitResponse,
1059    pub target: ComponentRuntimeStatusResponse,
1060}
1061
1062///
1063/// RootComponentRuntimeActivationResponse
1064///
1065/// Exact root authority plus independently observed target-local runtime activation.
1066///
1067
1068#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1069pub struct RootComponentRuntimeActivationResponse {
1070    pub committed: RootComponentCommitResponse,
1071    pub target: ComponentRuntimeStatusResponse,
1072}
1073
1074///
1075/// RootComponentMembershipActivationResponse
1076///
1077/// Exact active Registry authority plus independently observed current target Directory.
1078///
1079
1080#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1081pub struct RootComponentMembershipActivationResponse {
1082    pub allocation: RootComponentAllocationResponse,
1083    pub registry: ComponentRegistryPartitionResponse,
1084    pub directory: ComponentDirectoryHead,
1085    pub target: ComponentRuntimeStatusResponse,
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090    use super::*;
1091    use crate::{
1092        dto::root_store::RootStoreBootstrapRequest,
1093        ids::{
1094            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1095            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1096        },
1097    };
1098
1099    #[test]
1100    fn component_registry_contracts_round_trip_through_candid() {
1101        let request = RootComponentRegistryPreparationRequest {
1102            store_bootstrap: RootStoreBootstrapRequest {
1103                manifest_payload_size_bytes: 128,
1104            },
1105            expected_fleet_registry: FleetRegistryVersion {
1106                authority: fleet_registry_authority(),
1107                revision: 4,
1108                content_hash: [5; 32],
1109            },
1110        };
1111        let response = RootComponentRegistryStatusResponse {
1112            fleet_subnet_root: Principal::from_slice(&[6; 29]),
1113            prepared_against_registry: request.expected_fleet_registry.clone(),
1114            release_set: FleetSubnetRootReleaseSet {
1115                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1116                    [7; 32],
1117                )),
1118                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1119            },
1120            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1121            next_allocation_sequence: 1,
1122            reserved_component_instances: 0,
1123            committed_component_instances: 0,
1124            managed_descendants: 0,
1125            known_created_component_canisters: 0,
1126            encoded_bytes: 0,
1127            initial_inventory: Some(RootComponentInitialInventoryStatus {
1128                fleet_activation_operation_id: [10; 32],
1129                component_count: 0,
1130                inventory_hash: [11; 32],
1131                sealed_at_ns: 12,
1132                directories_converged: true,
1133                root_runtime_activated: true,
1134            }),
1135        };
1136        let allocation = RootComponentAllocationResponse {
1137            operation_id: [10; 32],
1138            allocation_sequence: 1,
1139            component: ComponentInstanceId::from_generated_bytes([11; 32]),
1140            component_spec: "projects".parse().expect("Component Spec ID"),
1141            spec_hash: [12; 32],
1142            role: CanisterRole::new("project_hub"),
1143            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1144                caller: Principal::from_slice(&[13; 29]),
1145            },
1146            release_set: response.release_set,
1147            phase: RootComponentAllocationPhase::Reserved,
1148            creation: None,
1149            installation: None,
1150        };
1151        let created = RootComponentAllocationResponse {
1152            phase: RootComponentAllocationPhase::Created,
1153            creation: Some(RootComponentCreationEvidence {
1154                wasm_store: Principal::from_slice(&[14; 29]),
1155                payload_hash: [15; 32],
1156                payload_size_bytes: 4_096,
1157                initial_cycles: Cycles::new(5_000_000_000_000),
1158                controller: Principal::from_slice(&[6; 29]),
1159                canister: Some(Principal::from_slice(&[16; 29])),
1160            }),
1161            installation: None,
1162            ..allocation.clone()
1163        };
1164        let request_bytes = candid::encode_one(&request).expect("encode request");
1165        let response_bytes = candid::encode_one(&response).expect("encode response");
1166        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1167        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1168
1169        assert_eq!(
1170            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1171                .expect("decode request"),
1172            request
1173        );
1174        assert_eq!(
1175            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1176                .expect("decode response"),
1177            response
1178        );
1179        assert_eq!(
1180            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1181                .expect("decode allocation"),
1182            allocation
1183        );
1184        assert_eq!(
1185            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1186                .expect("decode created allocation"),
1187            created
1188        );
1189    }
1190
1191    #[test]
1192    fn component_commit_response_round_trips_through_candid() {
1193        let root = Principal::from_slice(&[6; 29]);
1194        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1195        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1196        let release_set = FleetSubnetRootReleaseSet {
1197            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1198                [7; 32],
1199            )),
1200            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1201        };
1202        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1203            caller: Principal::from_slice(&[13; 29]),
1204        };
1205        let binding = ComponentBinding {
1206            authority: fleet_registry_authority(),
1207            component,
1208            component_spec: component_spec.clone(),
1209            spec_hash: [12; 32],
1210            role: CanisterRole::new("project_hub"),
1211            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1212            fleet_subnet_root: root,
1213            canister_id: Principal::from_slice(&[16; 29]),
1214        };
1215        let head = ComponentRegistryHead {
1216            component,
1217            revision: 1,
1218            content_hash: [18; 32],
1219        };
1220        let committed = RootComponentCommitResponse {
1221            allocation: RootComponentAllocationResponse {
1222                operation_id: [10; 32],
1223                allocation_sequence: 1,
1224                component,
1225                component_spec,
1226                spec_hash: binding.spec_hash,
1227                role: binding.role.clone(),
1228                provisioning_origin: provisioning_origin.clone(),
1229                release_set,
1230                phase: RootComponentAllocationPhase::Committed,
1231                creation: Some(RootComponentCreationEvidence {
1232                    wasm_store: Principal::from_slice(&[14; 29]),
1233                    payload_hash: [15; 32],
1234                    payload_size_bytes: 4_096,
1235                    initial_cycles: Cycles::new(5_000_000_000_000),
1236                    controller: root,
1237                    canister: Some(binding.canister_id),
1238                }),
1239                installation: Some(RootComponentInstallEvidence {
1240                    raw_module_hash: [20; 32],
1241                    chunk_hashes: vec![vec![21; 32]],
1242                    binding: binding.clone(),
1243                }),
1244            },
1245            registry: ComponentRegistryPartitionResponse {
1246                head: head.clone(),
1247                binding: binding.clone(),
1248                provisioning_origin,
1249                release_set,
1250                status: ComponentLifecycleStatus::Prepared,
1251                reserved_descendants: 0,
1252                committed_descendants: 0,
1253                encoded_bytes: 2_048,
1254            },
1255            directory: ComponentDirectoryHead {
1256                provenance: ComponentDirectoryProvenance {
1257                    component: binding,
1258                    source_fleet_subnet_root: root,
1259                    component_registry_revision: head.revision,
1260                    component_registry_content_hash: head.content_hash,
1261                    synchronized_at_ns: 19,
1262                },
1263                descendant_count: 0,
1264            },
1265        };
1266        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1267
1268        assert_eq!(
1269            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1270                .expect("decode committed allocation"),
1271            committed
1272        );
1273    }
1274
1275    #[test]
1276    fn component_directory_page_contracts_round_trip_through_candid() {
1277        let root = Principal::from_slice(&[6; 29]);
1278        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1279        let binding = ComponentBinding {
1280            authority: fleet_registry_authority(),
1281            component,
1282            component_spec: "projects".parse().expect("Component Spec ID"),
1283            spec_hash: [12; 32],
1284            role: CanisterRole::new("project_hub"),
1285            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1286            fleet_subnet_root: root,
1287            canister_id: Principal::from_slice(&[16; 29]),
1288        };
1289        let directory = ComponentDirectoryHead {
1290            provenance: ComponentDirectoryProvenance {
1291                component: binding.clone(),
1292                source_fleet_subnet_root: root,
1293                component_registry_revision: 3,
1294                component_registry_content_hash: [18; 32],
1295                synchronized_at_ns: 19,
1296            },
1297            descendant_count: 1,
1298        };
1299        let request = ComponentDirectoryPageRequest {
1300            directory: directory.clone(),
1301            parent_canister_id: Some(binding.canister_id),
1302            role: Some(CanisterRole::new("project_instance")),
1303            status: Some(ComponentLifecycleStatus::Active),
1304            cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1305            limit: 50,
1306        };
1307        let response = ComponentDirectoryPageResponse {
1308            directory,
1309            entries: vec![ComponentDirectoryChildEntry {
1310                binding: ComponentChildBinding {
1311                    component: binding.clone(),
1312                    parent_canister_id: binding.canister_id,
1313                    role: CanisterRole::new("project_instance"),
1314                    canister_id: Principal::from_slice(&[21; 29]),
1315                },
1316                kind: ComponentChildKind::Instance,
1317                installed_artifact_hash: [22; 32],
1318                status: ComponentLifecycleStatus::Active,
1319            }],
1320            next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1321        };
1322        let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1323        let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1324
1325        assert_eq!(
1326            candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1327                .expect("decode Directory page request"),
1328            request
1329        );
1330        assert_eq!(
1331            candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1332                .expect("decode Directory page response"),
1333            response
1334        );
1335    }
1336
1337    fn fleet_registry_authority() -> FleetRegistryAuthority {
1338        FleetRegistryAuthority {
1339            binding: FleetCoordinatorBinding {
1340                fleet: crate::ids::FleetBinding {
1341                    fleet: FleetKey {
1342                        canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1343                        fleet_id: FleetId::from_generated_bytes([1; 32]),
1344                    },
1345                    app: AppId::from("toko"),
1346                },
1347                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1348                coordinator: Principal::from_slice(&[3; 29]),
1349            },
1350            epoch: 1,
1351        }
1352    }
1353
1354    #[test]
1355    fn component_creation_request_round_trips_through_candid() {
1356        let request = RootComponentCreationRequest {
1357            operation_id: [10; 32],
1358        };
1359        let bytes = candid::encode_one(request).expect("encode creation request");
1360
1361        assert_eq!(
1362            candid::decode_one::<RootComponentCreationRequest>(&bytes)
1363                .expect("decode creation request"),
1364            request
1365        );
1366    }
1367
1368    #[test]
1369    #[expect(
1370        clippy::too_many_lines,
1371        reason = "one Candid contract test covers every subtree-removal phase receipt"
1372    )]
1373    fn component_subtree_removal_contracts_round_trip_through_candid() {
1374        let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1375        let registry = ComponentRegistryHead {
1376            component,
1377            revision: 7,
1378            content_hash: [42; 32],
1379        };
1380        let request = RootComponentSubtreeRemovalRequest {
1381            operation_id: [43; 32],
1382            component,
1383            target_canister_id: Principal::from_slice(&[44; 29]),
1384            expected_registry: registry.clone(),
1385        };
1386        let status_request = RootComponentSubtreeRemovalStatusRequest {
1387            operation_id: request.operation_id,
1388            component,
1389        };
1390        let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1391            operation_id: request.operation_id,
1392            component,
1393            expected_traversal_steps: 1,
1394        };
1395        let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1396            operation_id: request.operation_id,
1397            component,
1398            expected_traversal_steps: 2,
1399            expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1400            expected_leaf_parent_canister_id: request.target_canister_id,
1401        };
1402        let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1403            observed_module_hash: [49; 32],
1404            stop: RootComponentSubtreeRemovalStopIntent {
1405                controller: Principal::from_slice(&[48; 29]),
1406                leaf: RootComponentSubtreeRemovalNode {
1407                    canister_id: Principal::from_slice(&[46; 29]),
1408                    parent_canister_id: request.target_canister_id,
1409                    role: CanisterRole::new("project_ledger"),
1410                    kind: ComponentChildKind::Singleton,
1411                    installed_artifact_hash: [47; 32],
1412                    status: ComponentLifecycleStatus::Active,
1413                },
1414            },
1415        };
1416        let response = RootComponentSubtreeRemovalResponse {
1417            operation_id: request.operation_id,
1418            component,
1419            target_canister_id: request.target_canister_id,
1420            target_parent_canister_id: Principal::from_slice(&[45; 29]),
1421            target_role: CanisterRole::new("project_instance"),
1422            target_status: ComponentLifecycleStatus::Active,
1423            reserved_against_registry: registry,
1424            maximum_completed_leaves: 4,
1425            completed_leaves: 1,
1426            traversal_steps: 2,
1427            phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1428                RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1429                    membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1430                        deleted: RootComponentSubtreeRemovalDeletedReceipt {
1431                            deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1432                        },
1433                        removed_from_registry: ComponentRegistryHead {
1434                            component,
1435                            revision: 8,
1436                            content_hash: [50; 32],
1437                        },
1438                        previous_descendant_content_hash: [51; 32],
1439                        previous_committed_descendants: 4,
1440                        registry: ComponentRegistryHead {
1441                            component,
1442                            revision: 9,
1443                            content_hash: [52; 32],
1444                        },
1445                        descendant_content_hash: [53; 32],
1446                        registry_encoded_bytes: 4_096,
1447                        reserved_descendants: 1,
1448                        committed_descendants: 3,
1449                        directory_synchronized_at_ns: 54,
1450                        directory_authority_hash: [55; 32],
1451                        parent_role_instances: 0,
1452                        root_managed_descendants: 4,
1453                        root_known_created_component_canisters: 4,
1454                    },
1455                    covered_fleet_registry_revision: 6,
1456                    covered_fleet_registry_content_hash: [56; 32],
1457                    covered_component_registry: ComponentRegistryHead {
1458                        component,
1459                        revision: 9,
1460                        content_hash: [52; 32],
1461                    },
1462                    covered_authority_hash: [55; 32],
1463                    owning_component: RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1464                        operation_id: [57; 32],
1465                        canister_id: Principal::from_slice(&[58; 29]),
1466                        activation: ComponentRuntimeActivationEvidence {
1467                            directory_authority_hash: [59; 32],
1468                            activated_at_ns: 60,
1469                        },
1470                    },
1471                    parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1472                        operation_id: [61; 32],
1473                        canister_id: request.target_canister_id,
1474                        activation: ComponentRuntimeActivationEvidence {
1475                            directory_authority_hash: [62; 32],
1476                            activated_at_ns: 63,
1477                        },
1478                    }),
1479                },
1480            ),
1481        };
1482
1483        let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1484        let advance_bytes =
1485            candid::encode_one(advance_request).expect("encode subtree removal advance request");
1486        let stop_bytes =
1487            candid::encode_one(stop_request).expect("encode subtree removal stop request");
1488        let status_bytes =
1489            candid::encode_one(status_request).expect("encode subtree removal status request");
1490        let response_bytes =
1491            candid::encode_one(&response).expect("encode subtree removal response");
1492
1493        assert_eq!(
1494            candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1495                .expect("decode subtree removal request"),
1496            request
1497        );
1498        assert_eq!(
1499            candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1500                .expect("decode subtree removal advance request"),
1501            advance_request
1502        );
1503        assert_eq!(
1504            candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1505                .expect("decode subtree removal stop request"),
1506            stop_request
1507        );
1508        assert_eq!(
1509            candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1510                .expect("decode subtree removal status request"),
1511            status_request
1512        );
1513        assert_eq!(
1514            candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1515                .expect("decode subtree removal response"),
1516            response
1517        );
1518    }
1519
1520    #[test]
1521    fn component_draining_contracts_round_trip_through_candid() {
1522        let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1523        let previous_registry = ComponentRegistryHead {
1524            component,
1525            revision: 7,
1526            content_hash: [61; 32],
1527        };
1528        let request = RootComponentDrainingRequest {
1529            operation_id: [62; 32],
1530            component,
1531            expected_registry: previous_registry.clone(),
1532        };
1533        let status_request = RootComponentDrainingStatusRequest {
1534            operation_id: request.operation_id,
1535            component,
1536        };
1537        let response = RootComponentDrainingResponse {
1538            operation_id: request.operation_id,
1539            component,
1540            previous_registry,
1541            registry: ComponentRegistryHead {
1542                component,
1543                revision: 8,
1544                content_hash: [63; 32],
1545            },
1546            descendant_count: 20_000,
1547            descendant_content_hash: [64; 32],
1548            directory_authority_hash: [65; 32],
1549            started_at_ns: 66,
1550        };
1551
1552        let request_bytes =
1553            candid::encode_one(&request).expect("encode Component draining request");
1554        let status_bytes =
1555            candid::encode_one(status_request).expect("encode Component draining status request");
1556        let response_bytes =
1557            candid::encode_one(&response).expect("encode Component draining response");
1558
1559        assert_eq!(
1560            candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1561                .expect("decode Component draining request"),
1562            request
1563        );
1564        assert_eq!(
1565            candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1566                .expect("decode Component draining status request"),
1567            status_request
1568        );
1569        assert_eq!(
1570            candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1571                .expect("decode Component draining response"),
1572            response
1573        );
1574    }
1575
1576    #[test]
1577    fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
1578        let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
1579            operation_id: [50; 32],
1580            component: ComponentInstanceId::from_generated_bytes([51; 32]),
1581            expected_traversal_steps: 3,
1582            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1583            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1584        };
1585        let request = RootComponentSubtreeRemovalDeleteRequest {
1586            operation_id: prepare.operation_id,
1587            component: prepare.component,
1588            expected_traversal_steps: prepare.expected_traversal_steps,
1589            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1590            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1591        };
1592        let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
1593            operation_id: prepare.operation_id,
1594            component: prepare.component,
1595            expected_traversal_steps: prepare.expected_traversal_steps,
1596            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1597            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1598        };
1599        let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
1600            operation_id: prepare.operation_id,
1601            component: prepare.component,
1602            expected_traversal_steps: prepare.expected_traversal_steps,
1603            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1604            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1605        };
1606        let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
1607            operation_id: prepare.operation_id,
1608            component: prepare.component,
1609            expected_traversal_steps: prepare.expected_traversal_steps,
1610            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1611            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1612        };
1613        let prepare_bytes = candid::encode_one(prepare)
1614            .expect("encode subtree removal deletion preparation request");
1615        let request_bytes =
1616            candid::encode_one(request).expect("encode subtree removal deletion request");
1617        let membership_request_bytes = candid::encode_one(membership_request)
1618            .expect("encode subtree removal membership-removal request");
1619        let directory_request_bytes = candid::encode_one(directory_request)
1620            .expect("encode subtree removal Directory synchronization request");
1621        let finalization_request_bytes = candid::encode_one(finalization_request)
1622            .expect("encode subtree removal leaf-finalization request");
1623
1624        assert_eq!(
1625            candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
1626                &prepare_bytes
1627            )
1628            .expect("decode subtree removal deletion preparation request"),
1629            prepare
1630        );
1631        assert_eq!(
1632            candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
1633                .expect("decode subtree removal deletion request"),
1634            request
1635        );
1636        assert_eq!(
1637            candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
1638                &membership_request_bytes
1639            )
1640            .expect("decode subtree removal membership-removal request"),
1641            membership_request
1642        );
1643        assert_eq!(
1644            candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
1645                &directory_request_bytes
1646            )
1647            .expect("decode subtree removal Directory synchronization request"),
1648            directory_request
1649        );
1650        assert_eq!(
1651            candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
1652                &finalization_request_bytes
1653            )
1654            .expect("decode subtree removal leaf-finalization request"),
1655            finalization_request
1656        );
1657    }
1658
1659    #[test]
1660    fn component_subtree_removal_stop_request_round_trips_through_candid() {
1661        let request = RootComponentSubtreeRemovalStopRequest {
1662            operation_id: [50; 32],
1663            component: ComponentInstanceId::from_generated_bytes([51; 32]),
1664            expected_traversal_steps: 3,
1665            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1666            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1667        };
1668        let bytes =
1669            candid::encode_one(request).expect("encode subtree removal stop execution request");
1670
1671        assert_eq!(
1672            candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
1673                .expect("decode subtree removal stop execution request"),
1674            request
1675        );
1676    }
1677
1678    #[test]
1679    #[expect(
1680        clippy::too_many_lines,
1681        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1682    )]
1683    fn component_child_lifecycle_contracts_round_trip_through_candid() {
1684        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1685        let registry = ComponentRegistryHead {
1686            component,
1687            revision: 2,
1688            content_hash: [12; 32],
1689        };
1690        let request = RootComponentChildAllocationRequest {
1691            operation_id: [13; 32],
1692            component,
1693            expected_registry: registry.clone(),
1694            child_role: CanisterRole::new("project_instance"),
1695        };
1696        let status_request = RootComponentChildAllocationStatusRequest {
1697            operation_id: request.operation_id,
1698            component,
1699        };
1700        let creation_request = RootComponentChildCreationRequest {
1701            operation_id: request.operation_id,
1702            component,
1703        };
1704        let install_request = RootComponentChildInstallRequest {
1705            operation_id: request.operation_id,
1706            component,
1707        };
1708        let commit_request = RootComponentChildCommitRequest {
1709            operation_id: request.operation_id,
1710            component,
1711        };
1712        let directory_request = RootComponentChildDirectoryPreparationRequest {
1713            operation_id: request.operation_id,
1714            component,
1715        };
1716        let activation_request = RootComponentChildRuntimeActivationRequest {
1717            operation_id: request.operation_id,
1718            component,
1719        };
1720        let membership_request = RootComponentChildMembershipActivationRequest {
1721            operation_id: request.operation_id,
1722            component,
1723        };
1724        let root = Principal::from_slice(&[17; 29]);
1725        let parent = Principal::from_slice(&[14; 29]);
1726        let child = Principal::from_slice(&[18; 29]);
1727        let child_binding = ComponentChildBinding {
1728            component: ComponentBinding {
1729                authority: fleet_registry_authority(),
1730                component,
1731                component_spec: "projects".parse().expect("Component Spec"),
1732                spec_hash: [19; 32],
1733                role: CanisterRole::new("project_hub"),
1734                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1735                fleet_subnet_root: root,
1736                canister_id: parent,
1737            },
1738            parent_canister_id: parent,
1739            role: request.child_role.clone(),
1740            canister_id: child,
1741        };
1742        let response = RootComponentChildAllocationResponse {
1743            operation_id: request.operation_id,
1744            component,
1745            parent_canister_id: parent,
1746            parent_role: CanisterRole::new("project_hub"),
1747            child_role: request.child_role.clone(),
1748            child_kind: ComponentChildKind::Instance,
1749            maximum_instances_per_parent: 10_000,
1750            maximum_descendants: 20_000,
1751            maximum_registry_bytes: 16_777_216,
1752            reserved_against_registry: registry,
1753            release_set: FleetSubnetRootReleaseSet {
1754                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1755                    [15; 32],
1756                )),
1757                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1758            },
1759            phase: RootComponentAllocationPhase::Verified,
1760            creation: Some(RootComponentCreationEvidence {
1761                wasm_store: Principal::from_slice(&[21; 29]),
1762                payload_hash: [22; 32],
1763                payload_size_bytes: 4_096,
1764                initial_cycles: Cycles::new(5_000_000_000_000),
1765                controller: root,
1766                canister: Some(child),
1767            }),
1768            installation: Some(RootComponentChildInstallEvidence {
1769                raw_module_hash: [23; 32],
1770                chunk_hashes: vec![vec![24; 32]],
1771                binding: child_binding.clone(),
1772            }),
1773        };
1774        let commit_response = RootComponentChildCommitResponse {
1775            allocation: response.clone(),
1776            registry: ComponentRegistryPartitionResponse {
1777                head: ComponentRegistryHead {
1778                    component,
1779                    revision: 3,
1780                    content_hash: [25; 32],
1781                },
1782                binding: child_binding.component.clone(),
1783                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1784                    caller: Principal::from_slice(&[26; 29]),
1785                },
1786                release_set: response.release_set,
1787                status: ComponentLifecycleStatus::Active,
1788                reserved_descendants: 0,
1789                committed_descendants: 1,
1790                encoded_bytes: 8_192,
1791            },
1792            directory: ComponentDirectoryHead {
1793                provenance: ComponentDirectoryProvenance {
1794                    component: child_binding.component.clone(),
1795                    source_fleet_subnet_root: root,
1796                    component_registry_revision: 3,
1797                    component_registry_content_hash: [25; 32],
1798                    synchronized_at_ns: 27,
1799                },
1800                descendant_count: 1,
1801            },
1802        };
1803        let runtime_authority = ComponentRuntimeDirectoryAuthority {
1804            fleet: FleetDirectorySnapshot {
1805                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1806                    registry: FleetRegistryVersion {
1807                        authority: fleet_registry_authority(),
1808                        revision: 4,
1809                        content_hash: [28; 32],
1810                    },
1811                    source_fleet_subnet_root: root,
1812                },
1813                fleet_subnet_roots: vec![
1814                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1815                        placement_subnet: commit_response.registry.binding.placement_subnet,
1816                        fleet_subnet_root: root,
1817                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1818                    },
1819                ],
1820            },
1821            component: commit_response.directory.clone(),
1822        };
1823        let activation = ComponentRuntimeActivationEvidence {
1824            directory_authority_hash: [29; 32],
1825            activated_at_ns: 30,
1826        };
1827        let directory_response = RootComponentChildDirectoryPreparationResponse {
1828            committed: commit_response.clone(),
1829            child: ComponentRuntimeStatusResponse {
1830                operation_id: request.operation_id,
1831                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1832                phase: ComponentRuntimePhase::DirectoryPrepared,
1833                authority: Some(runtime_authority.clone()),
1834                authority_hash: Some([31; 32]),
1835                activation: None,
1836            },
1837            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1838                operation_id: [32; 32],
1839                binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1840                covered_authority: runtime_authority.clone(),
1841                covered_authority_hash: [31; 32],
1842                activation,
1843            },
1844            parent: None,
1845        };
1846        let activation_response = RootComponentChildRuntimeActivationResponse {
1847            committed: commit_response.clone(),
1848            child: ComponentRuntimeStatusResponse {
1849                operation_id: request.operation_id,
1850                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1851                phase: ComponentRuntimePhase::Active,
1852                authority: Some(runtime_authority.clone()),
1853                authority_hash: Some([31; 32]),
1854                activation: Some(ComponentRuntimeActivationEvidence {
1855                    directory_authority_hash: [31; 32],
1856                    activated_at_ns: 33,
1857                }),
1858            },
1859        };
1860        let active_directory = ComponentDirectoryHead {
1861            provenance: ComponentDirectoryProvenance {
1862                component: child_binding.component.clone(),
1863                source_fleet_subnet_root: root,
1864                component_registry_revision: 4,
1865                component_registry_content_hash: [34; 32],
1866                synchronized_at_ns: 35,
1867            },
1868            descendant_count: 1,
1869        };
1870        let active_authority = ComponentRuntimeDirectoryAuthority {
1871            fleet: runtime_authority.fleet,
1872            component: active_directory.clone(),
1873        };
1874        let membership_response = RootComponentChildMembershipActivationResponse {
1875            committed: commit_response.clone(),
1876            registry: ComponentRegistryPartitionResponse {
1877                head: ComponentRegistryHead {
1878                    component,
1879                    revision: 4,
1880                    content_hash: [34; 32],
1881                },
1882                binding: child_binding.component.clone(),
1883                provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1884                release_set: commit_response.registry.release_set,
1885                status: ComponentLifecycleStatus::Active,
1886                reserved_descendants: 0,
1887                committed_descendants: 1,
1888                encoded_bytes: 8_256,
1889            },
1890            directory: active_directory,
1891            child: ComponentRuntimeStatusResponse {
1892                operation_id: request.operation_id,
1893                binding: ManagedCanisterBinding::ComponentChild(child_binding),
1894                phase: ComponentRuntimePhase::Active,
1895                authority: Some(active_authority),
1896                authority_hash: Some([36; 32]),
1897                activation: Some(ComponentRuntimeActivationEvidence {
1898                    directory_authority_hash: [31; 32],
1899                    activated_at_ns: 33,
1900                }),
1901            },
1902        };
1903
1904        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1905        let status_bytes =
1906            candid::encode_one(status_request).expect("encode child reservation status");
1907        let creation_bytes =
1908            candid::encode_one(creation_request).expect("encode child creation request");
1909        let install_bytes =
1910            candid::encode_one(install_request).expect("encode child install request");
1911        let response_bytes = candid::encode_one(&response).expect("encode child response");
1912        let commit_request_bytes =
1913            candid::encode_one(commit_request).expect("encode child commit request");
1914        let directory_request_bytes =
1915            candid::encode_one(directory_request).expect("encode child Directory request");
1916        let activation_request_bytes =
1917            candid::encode_one(activation_request).expect("encode child activation request");
1918        let membership_request_bytes =
1919            candid::encode_one(membership_request).expect("encode child membership request");
1920        let commit_response_bytes =
1921            candid::encode_one(&commit_response).expect("encode child commit response");
1922        let directory_response_bytes =
1923            candid::encode_one(&directory_response).expect("encode child Directory response");
1924        let activation_response_bytes =
1925            candid::encode_one(&activation_response).expect("encode child activation response");
1926        let membership_response_bytes =
1927            candid::encode_one(&membership_response).expect("encode child membership response");
1928
1929        assert_eq!(
1930            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1931                .expect("decode child reservation"),
1932            request
1933        );
1934        assert_eq!(
1935            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1936                .expect("decode child reservation status"),
1937            status_request
1938        );
1939        assert_eq!(
1940            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1941                .expect("decode child creation request"),
1942            creation_request
1943        );
1944        assert_eq!(
1945            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1946                .expect("decode child install request"),
1947            install_request
1948        );
1949        assert_eq!(
1950            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1951                .expect("decode child response"),
1952            response
1953        );
1954        assert_eq!(
1955            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1956                .expect("decode child commit request"),
1957            commit_request
1958        );
1959        assert_eq!(
1960            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1961                &directory_request_bytes
1962            )
1963            .expect("decode child Directory request"),
1964            directory_request
1965        );
1966        assert_eq!(
1967            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1968                .expect("decode child commit response"),
1969            commit_response
1970        );
1971        assert_eq!(
1972            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1973                &directory_response_bytes
1974            )
1975            .expect("decode child Directory response"),
1976            directory_response
1977        );
1978        assert_eq!(
1979            candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1980                &activation_request_bytes
1981            )
1982            .expect("decode child activation request"),
1983            activation_request
1984        );
1985        assert_eq!(
1986            candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1987                &activation_response_bytes
1988            )
1989            .expect("decode child activation response"),
1990            activation_response
1991        );
1992        assert_eq!(
1993            candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1994                &membership_request_bytes
1995            )
1996            .expect("decode child membership request"),
1997            membership_request
1998        );
1999        assert_eq!(
2000            candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2001                &membership_response_bytes
2002            )
2003            .expect("decode child membership response"),
2004            membership_response
2005        );
2006    }
2007
2008    #[test]
2009    fn component_install_request_round_trips_through_candid() {
2010        let request = RootComponentInstallRequest {
2011            operation_id: [10; 32],
2012        };
2013        let bytes = candid::encode_one(request).expect("encode install request");
2014
2015        assert_eq!(
2016            candid::decode_one::<RootComponentInstallRequest>(&bytes)
2017                .expect("decode install request"),
2018            request
2019        );
2020    }
2021
2022    #[test]
2023    fn component_commit_request_round_trips_through_candid() {
2024        let request = RootComponentCommitRequest {
2025            operation_id: [10; 32],
2026        };
2027        let bytes = candid::encode_one(request).expect("encode commit request");
2028
2029        assert_eq!(
2030            candid::decode_one::<RootComponentCommitRequest>(&bytes)
2031                .expect("decode commit request"),
2032            request
2033        );
2034    }
2035
2036    #[test]
2037    fn component_runtime_activation_requests_round_trip_through_candid() {
2038        let root_request = RootComponentRuntimeActivationRequest {
2039            operation_id: [22; 32],
2040        };
2041        let target_request = ComponentRuntimeActivationRequest {
2042            operation_id: root_request.operation_id,
2043            directory_authority_hash: [23; 32],
2044        };
2045        let membership_request = RootComponentMembershipActivationRequest {
2046            operation_id: root_request.operation_id,
2047        };
2048        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2049        let target_bytes =
2050            candid::encode_one(target_request).expect("encode target activation request");
2051        let membership_bytes =
2052            candid::encode_one(membership_request).expect("encode membership activation request");
2053
2054        assert_eq!(
2055            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2056                .expect("decode root activation request"),
2057            root_request
2058        );
2059        assert_eq!(
2060            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2061                .expect("decode target activation request"),
2062            target_request
2063        );
2064        assert_eq!(
2065            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2066                .expect("decode membership activation request"),
2067            membership_request
2068        );
2069    }
2070}