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/// RootComponentQuiescenceRequest
292///
293/// Controller command converging and stopping one exact draining Component.
294///
295
296#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297pub struct RootComponentQuiescenceRequest {
298    pub operation_id: [u8; 32],
299    pub component: ComponentInstanceId,
300    pub expected_registry: ComponentRegistryHead,
301}
302
303///
304/// RootComponentQuiescenceStatusRequest
305///
306/// Read-only lookup key for one draining Component's quiescence progress.
307///
308
309#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
310pub struct RootComponentQuiescenceStatusRequest {
311    pub operation_id: [u8; 32],
312    pub component: ComponentInstanceId,
313}
314
315///
316/// RootComponentDrainingAdvanceRequest
317///
318/// Controller command advancing at most one deterministic draining-removal phase.
319///
320
321#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
322pub struct RootComponentDrainingAdvanceRequest {
323    pub operation_id: [u8; 32],
324    pub component: ComponentInstanceId,
325}
326
327///
328/// RootComponentFinalInventoryRequest
329///
330/// Controller command freezing one exact empty draining Component inventory.
331///
332
333#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
334pub struct RootComponentFinalInventoryRequest {
335    pub operation_id: [u8; 32],
336    pub component: ComponentInstanceId,
337    pub expected_registry: ComponentRegistryHead,
338}
339
340///
341/// RootComponentDeletionRequest
342///
343/// Controller command reconciling one top-level deletion from frozen final inventory.
344///
345
346#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
347pub struct RootComponentDeletionRequest {
348    pub operation_id: [u8; 32],
349    pub component: ComponentInstanceId,
350    pub expected_inventory_hash: [u8; 32],
351}
352
353///
354/// RootComponentDeletionStatusRequest
355///
356/// Read-only lookup key for one durable top-level Component deletion.
357///
358
359#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
360pub struct RootComponentDeletionStatusRequest {
361    pub operation_id: [u8; 32],
362    pub component: ComponentInstanceId,
363}
364
365///
366/// RootComponentChildCreationRequest
367///
368/// Parent command continuing one already reserved direct-child operation.
369///
370
371#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
372pub struct RootComponentChildCreationRequest {
373    pub operation_id: [u8; 32],
374    pub component: ComponentInstanceId,
375}
376
377///
378/// RootComponentChildInstallRequest
379///
380/// Parent command installing and verifying one already created direct-child operation.
381///
382
383#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
384pub struct RootComponentChildInstallRequest {
385    pub operation_id: [u8; 32],
386    pub component: ComponentInstanceId,
387}
388
389///
390/// RootComponentChildCommitRequest
391///
392/// Parent command committing one already verified direct-child operation.
393///
394
395#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
396pub struct RootComponentChildCommitRequest {
397    pub operation_id: [u8; 32],
398    pub component: ComponentInstanceId,
399}
400
401///
402/// RootComponentChildDirectoryPreparationRequest
403///
404/// Parent command distributing one committed child's Directory and converging its affected members.
405///
406
407#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
408pub struct RootComponentChildDirectoryPreparationRequest {
409    pub operation_id: [u8; 32],
410    pub component: ComponentInstanceId,
411}
412
413///
414/// RootComponentChildRuntimeActivationRequest
415///
416/// Parent command activating one Directory-prepared direct-child runtime.
417///
418
419#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
420pub struct RootComponentChildRuntimeActivationRequest {
421    pub operation_id: [u8; 32],
422    pub component: ComponentInstanceId,
423}
424
425///
426/// RootComponentChildMembershipActivationRequest
427///
428/// Parent command activating one runtime-active direct child's Registry membership.
429///
430
431#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
432pub struct RootComponentChildMembershipActivationRequest {
433    pub operation_id: [u8; 32],
434    pub component: ComponentInstanceId,
435}
436
437///
438/// RootComponentCreationRequest
439///
440/// Controller command continuing one already reserved top-level Component operation.
441///
442
443#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
444pub struct RootComponentCreationRequest {
445    pub operation_id: [u8; 32],
446}
447
448///
449/// RootComponentInstallRequest
450///
451/// Controller command continuing one already created top-level Component operation.
452///
453
454#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
455pub struct RootComponentInstallRequest {
456    pub operation_id: [u8; 32],
457}
458
459///
460/// RootComponentCommitRequest
461///
462/// Controller command committing one already verified top-level Component operation.
463///
464
465#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
466pub struct RootComponentCommitRequest {
467    pub operation_id: [u8; 32],
468}
469
470///
471/// RootComponentDirectoryPreparationRequest
472///
473/// Controller command distributing exact Directories to one committed top-level Component.
474///
475
476#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
477pub struct RootComponentDirectoryPreparationRequest {
478    pub operation_id: [u8; 32],
479}
480
481///
482/// RootComponentRuntimeActivationRequest
483///
484/// Controller command activating one Directory-prepared top-level Component runtime.
485///
486
487#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
488pub struct RootComponentRuntimeActivationRequest {
489    pub operation_id: [u8; 32],
490}
491
492///
493/// RootComponentMembershipActivationRequest
494///
495/// Controller command activating one runtime-active Component's Registry membership.
496///
497
498#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
499pub struct RootComponentMembershipActivationRequest {
500    pub operation_id: [u8; 32],
501}
502
503///
504/// ComponentProvisioningOrigin
505///
506/// Authenticated causal authority retained with one top-level Component allocation.
507///
508
509#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub enum ComponentProvisioningOrigin {
511    FleetAdministrator { caller: Principal },
512}
513
514///
515/// RootComponentAllocationPhase
516///
517/// Durable root-local progress of one top-level Component allocation operation.
518///
519
520#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
521pub enum RootComponentAllocationPhase {
522    Reserved,
523    CreationIntent,
524    Created,
525    InstallIntent,
526    Installed,
527    Verified,
528    Committed,
529    Removed,
530}
531
532///
533/// RootComponentSubtreeRemovalPhase
534///
535/// Durable root-local progress of one child-subtree removal operation.
536///
537
538#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
539#[expect(
540    clippy::large_enum_variant,
541    reason = "wire phases retain complete inline receipts for deterministic Candid responses"
542)]
543pub enum RootComponentSubtreeRemovalPhase {
544    Fenced,
545    Traversing(RootComponentSubtreeRemovalNode),
546    LeafSelected(RootComponentSubtreeRemovalNode),
547    StopIntent(RootComponentSubtreeRemovalStopIntent),
548    Stopped(RootComponentSubtreeRemovalStoppedReceipt),
549    DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
550    Deleted(RootComponentSubtreeRemovalDeletedReceipt),
551    MembershipRemoved(RootComponentSubtreeRemovalMembershipRemovedReceipt),
552    DirectorySynchronized(RootComponentSubtreeRemovalDirectorySynchronizedReceipt),
553    Completed(RootComponentSubtreeRemovalCompletedReceipt),
554}
555
556///
557/// RootComponentSubtreeRemovalNode
558///
559/// Exact registered child selected as a traversal cursor or removable leaf.
560///
561
562#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
563pub struct RootComponentSubtreeRemovalNode {
564    pub canister_id: Principal,
565    pub parent_canister_id: Principal,
566    pub role: CanisterRole,
567    pub kind: ComponentChildKind,
568    pub installed_artifact_hash: [u8; 32],
569    pub status: ComponentLifecycleStatus,
570}
571
572///
573/// RootComponentSubtreeRemovalStopIntent
574///
575/// Exact registered leaf and sole root controller frozen before a stop call.
576///
577
578#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
579pub struct RootComponentSubtreeRemovalStopIntent {
580    pub leaf: RootComponentSubtreeRemovalNode,
581    pub controller: Principal,
582}
583
584///
585/// RootComponentSubtreeRemovalStoppedReceipt
586///
587/// Frozen stop authority plus the independently observed installed module.
588///
589
590#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
591pub struct RootComponentSubtreeRemovalStoppedReceipt {
592    pub stop: RootComponentSubtreeRemovalStopIntent,
593    pub observed_module_hash: [u8; 32],
594}
595
596///
597/// RootComponentSubtreeRemovalDeleteIntent
598///
599/// Exact stopped receipt frozen before the destructive management call.
600///
601
602#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603pub struct RootComponentSubtreeRemovalDeleteIntent {
604    pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
605}
606
607///
608/// RootComponentSubtreeRemovalDeletedReceipt
609///
610/// Frozen deletion authority committed only after independently observed absence.
611///
612
613#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
614pub struct RootComponentSubtreeRemovalDeletedReceipt {
615    pub deletion: RootComponentSubtreeRemovalDeleteIntent,
616}
617
618///
619/// RootComponentSubtreeRemovalMembershipRemovedReceipt
620///
621/// Exact Registry transition retained after the independently deleted leaf is unregistered.
622///
623
624#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
625pub struct RootComponentSubtreeRemovalMembershipRemovedReceipt {
626    pub deleted: RootComponentSubtreeRemovalDeletedReceipt,
627    pub removed_from_registry: ComponentRegistryHead,
628    pub previous_descendant_content_hash: [u8; 32],
629    pub previous_committed_descendants: u32,
630    pub registry: ComponentRegistryHead,
631    pub descendant_content_hash: [u8; 32],
632    pub registry_encoded_bytes: u64,
633    pub reserved_descendants: u32,
634    pub committed_descendants: u32,
635    pub directory_synchronized_at_ns: u64,
636    pub directory_authority_hash: [u8; 32],
637    pub parent_role_instances: u32,
638    pub root_managed_descendants: u32,
639    pub root_known_created_component_canisters: u32,
640}
641
642///
643/// RootComponentSubtreeRemovalDirectoryConvergenceEvidence
644///
645/// Compact durable proof that one surviving member covered the required Directory.
646///
647
648#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
649pub struct RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
650    pub operation_id: [u8; 32],
651    pub canister_id: Principal,
652    pub activation: ComponentRuntimeActivationEvidence,
653}
654
655///
656/// RootComponentSubtreeRemovalDirectorySynchronizedReceipt
657///
658/// Membership removal plus independently verified surviving-member convergence.
659///
660/// The owner is absent only when its top-level Component is durably quiescent.
661///
662
663#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
664pub struct RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
665    pub membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt,
666    pub covered_fleet_registry_revision: u64,
667    pub covered_fleet_registry_content_hash: [u8; 32],
668    pub covered_component_registry: ComponentRegistryHead,
669    pub covered_authority_hash: [u8; 32],
670    pub owning_component: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
671    pub parent: Option<RootComponentSubtreeRemovalDirectoryConvergenceEvidence>,
672}
673
674///
675/// RootComponentSubtreeRemovalCompletedReceipt
676///
677/// Terminal Registry and Directory authority after the fenced target is finalized.
678///
679
680#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
681pub struct RootComponentSubtreeRemovalCompletedReceipt {
682    pub registry: ComponentRegistryHead,
683    pub directory_authority_hash: [u8; 32],
684}
685
686///
687/// ComponentLifecycleStatus
688///
689/// Root-owned runtime lifecycle state of one committed Component Registry member.
690///
691
692#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
693pub enum ComponentLifecycleStatus {
694    Prepared,
695    Active,
696    Draining,
697    Removed,
698}
699
700///
701/// ComponentRegistryHead
702///
703/// Exact independently versioned authority of one Component Registry partition.
704///
705
706#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
707pub struct ComponentRegistryHead {
708    pub component: ComponentInstanceId,
709    pub revision: u64,
710    pub content_hash: [u8; 32],
711}
712
713///
714/// ComponentRegistryPartitionRequest
715///
716/// Read-only lookup key for one committed Component Registry partition.
717///
718
719#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
720pub struct ComponentRegistryPartitionRequest {
721    pub component: ComponentInstanceId,
722}
723
724///
725/// ComponentRegistryPartitionResponse
726///
727/// Protected top-level row and independent head of one Component Registry partition.
728///
729
730#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
731pub struct ComponentRegistryPartitionResponse {
732    pub head: ComponentRegistryHead,
733    pub binding: ComponentBinding,
734    pub provisioning_origin: ComponentProvisioningOrigin,
735    pub release_set: FleetSubnetRootReleaseSet,
736    pub status: ComponentLifecycleStatus,
737    pub reserved_descendants: u32,
738    pub committed_descendants: u32,
739    pub encoded_bytes: u64,
740}
741
742///
743/// ComponentDirectoryProvenance
744///
745/// Exact Component Registry authority from which one Component Directory is derived.
746///
747
748#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
749pub struct ComponentDirectoryProvenance {
750    pub component: ComponentBinding,
751    pub source_fleet_subnet_root: Principal,
752    pub component_registry_revision: u64,
753    pub component_registry_content_hash: [u8; 32],
754    pub synchronized_at_ns: u64,
755}
756
757///
758/// ComponentDirectoryHead
759///
760/// Compact independently versioned discovery projection for one Component tree.
761///
762
763#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
764pub struct ComponentDirectoryHead {
765    pub provenance: ComponentDirectoryProvenance,
766    pub descendant_count: u32,
767}
768
769///
770/// ComponentDirectoryHeadRequest
771///
772/// Read-only lookup key for one committed Component Directory head.
773///
774
775#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
776pub struct ComponentDirectoryHeadRequest {
777    pub component: ComponentInstanceId,
778}
779
780///
781/// ComponentDirectoryPageCursor
782///
783/// Opaque revision- and filter-bound continuation for one bounded Directory page.
784///
785
786#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
787pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
788
789///
790/// ComponentDirectoryPageRequest
791///
792/// Bounded member query against one exact current Component Directory authority.
793///
794
795#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
796pub struct ComponentDirectoryPageRequest {
797    pub directory: ComponentDirectoryHead,
798    pub parent_canister_id: Option<Principal>,
799    pub role: Option<CanisterRole>,
800    pub status: Option<ComponentLifecycleStatus>,
801    pub cursor: Option<ComponentDirectoryPageCursor>,
802    pub limit: u16,
803}
804
805///
806/// ComponentDirectoryChildEntry
807///
808/// One authoritative normalized child projected with its complete protected binding.
809///
810
811#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
812pub struct ComponentDirectoryChildEntry {
813    pub binding: ComponentChildBinding,
814    pub kind: ComponentChildKind,
815    pub installed_artifact_hash: [u8; 32],
816    pub status: ComponentLifecycleStatus,
817}
818
819///
820/// ComponentDirectoryPageResponse
821///
822/// One bounded caller-scoped page under the exact requested Directory head.
823///
824
825#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
826pub struct ComponentDirectoryPageResponse {
827    pub directory: ComponentDirectoryHead,
828    pub entries: Vec<ComponentDirectoryChildEntry>,
829    pub next_cursor: Option<ComponentDirectoryPageCursor>,
830}
831
832///
833/// ComponentRuntimeDirectoryAuthority
834///
835/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
836///
837
838#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
839pub struct ComponentRuntimeDirectoryAuthority {
840    pub fleet: FleetDirectorySnapshot,
841    pub component: ComponentDirectoryHead,
842}
843
844///
845/// ComponentRuntimeDirectoryPreparationRequest
846///
847/// Root-issued exact Directory preparation command for one managed Component-tree node.
848///
849
850#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
851pub struct ComponentRuntimeDirectoryPreparationRequest {
852    pub operation_id: [u8; 32],
853    pub authority: ComponentRuntimeDirectoryAuthority,
854}
855
856///
857/// ComponentRuntimeDirectorySynchronizationRequest
858///
859/// Root-issued replacement of one active managed Component node's current Directory authority.
860///
861
862#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
863pub struct ComponentRuntimeDirectorySynchronizationRequest {
864    pub operation_id: [u8; 32],
865    pub authority: ComponentRuntimeDirectoryAuthority,
866}
867
868///
869/// ComponentRuntimePhase
870///
871/// Target-local progress from installation through Component runtime activation.
872///
873
874#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
875pub enum ComponentRuntimePhase {
876    AwaitingDirectory,
877    DirectoryPrepared,
878    Active,
879}
880
881///
882/// ComponentRuntimeActivationEvidence
883///
884/// Exact retained Directory authority under which one Component runtime became Active.
885///
886
887#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
888pub struct ComponentRuntimeActivationEvidence {
889    pub directory_authority_hash: [u8; 32],
890    pub activated_at_ns: u64,
891}
892
893///
894/// ComponentRuntimeActivationRequest
895///
896/// Root-issued exact activation command for one Directory-prepared managed Component node.
897///
898
899#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
900pub struct ComponentRuntimeActivationRequest {
901    pub operation_id: [u8; 32],
902    pub directory_authority_hash: [u8; 32],
903}
904
905///
906/// ComponentRuntimeStatusResponse
907///
908/// Independently observable target-local binding and exact retained Directory authority.
909///
910
911#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
912pub struct ComponentRuntimeStatusResponse {
913    pub operation_id: [u8; 32],
914    pub binding: ManagedCanisterBinding,
915    pub phase: ComponentRuntimePhase,
916    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
917    pub authority_hash: Option<[u8; 32]>,
918    pub activation: Option<ComponentRuntimeActivationEvidence>,
919}
920
921///
922/// ComponentRuntimeDirectoryConvergenceEvidence
923///
924/// Stable root evidence that one active member covered at least the required Directory authority.
925///
926
927#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
928pub struct ComponentRuntimeDirectoryConvergenceEvidence {
929    pub operation_id: [u8; 32],
930    pub binding: ManagedCanisterBinding,
931    pub covered_authority: ComponentRuntimeDirectoryAuthority,
932    pub covered_authority_hash: [u8; 32],
933    pub activation: ComponentRuntimeActivationEvidence,
934}
935
936///
937/// RootComponentCreationEvidence
938///
939/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
940///
941
942#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
943pub struct RootComponentCreationEvidence {
944    pub wasm_store: Principal,
945    pub payload_hash: [u8; 32],
946    pub payload_size_bytes: u64,
947    pub initial_cycles: Cycles,
948    pub controller: Principal,
949    pub canister: Option<Principal>,
950}
951
952///
953/// RootComponentInstallEvidence
954///
955/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
956///
957
958#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
959pub struct RootComponentInstallEvidence {
960    pub raw_module_hash: [u8; 32],
961    pub chunk_hashes: Vec<Vec<u8>>,
962    pub binding: ComponentBinding,
963}
964
965///
966/// RootComponentChildInstallEvidence
967///
968/// Exact child module and immutable retained binding frozen before installation.
969///
970
971#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
972pub struct RootComponentChildInstallEvidence {
973    pub raw_module_hash: [u8; 32],
974    pub chunk_hashes: Vec<Vec<u8>>,
975    pub binding: ComponentChildBinding,
976}
977
978///
979/// RootComponentAllocationResponse
980///
981/// Durable identity reservation returned identically for exact operation retry.
982///
983
984#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
985pub struct RootComponentAllocationResponse {
986    pub operation_id: [u8; 32],
987    pub allocation_sequence: u64,
988    pub component: ComponentInstanceId,
989    pub component_spec: ComponentSpecId,
990    pub spec_hash: [u8; 32],
991    pub role: CanisterRole,
992    pub provisioning_origin: ComponentProvisioningOrigin,
993    pub release_set: FleetSubnetRootReleaseSet,
994    pub phase: RootComponentAllocationPhase,
995    pub creation: Option<RootComponentCreationEvidence>,
996    pub installation: Option<RootComponentInstallEvidence>,
997}
998
999///
1000/// RootComponentChildAllocationResponse
1001///
1002/// Durable direct-child lifecycle progress returned identically for exact parent retry.
1003///
1004
1005#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1006pub struct RootComponentChildAllocationResponse {
1007    pub operation_id: [u8; 32],
1008    pub component: ComponentInstanceId,
1009    pub parent_canister_id: Principal,
1010    pub parent_role: CanisterRole,
1011    pub child_role: CanisterRole,
1012    pub child_kind: ComponentChildKind,
1013    pub maximum_instances_per_parent: u32,
1014    pub maximum_descendants: u32,
1015    pub maximum_registry_bytes: u64,
1016    pub reserved_against_registry: ComponentRegistryHead,
1017    pub release_set: FleetSubnetRootReleaseSet,
1018    pub phase: RootComponentAllocationPhase,
1019    pub creation: Option<RootComponentCreationEvidence>,
1020    pub installation: Option<RootComponentChildInstallEvidence>,
1021}
1022
1023///
1024/// RootComponentSubtreeRemovalResponse
1025///
1026/// Current durable snapshot of one monotonic subtree-removal operation.
1027///
1028
1029#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1030pub struct RootComponentSubtreeRemovalResponse {
1031    pub operation_id: [u8; 32],
1032    pub component: ComponentInstanceId,
1033    pub target_canister_id: Principal,
1034    pub target_parent_canister_id: Principal,
1035    pub target_role: CanisterRole,
1036    pub target_status: ComponentLifecycleStatus,
1037    pub reserved_against_registry: ComponentRegistryHead,
1038    pub maximum_completed_leaves: u32,
1039    pub completed_leaves: u32,
1040    pub traversal_steps: u32,
1041    pub phase: RootComponentSubtreeRemovalPhase,
1042}
1043
1044///
1045/// RootComponentDrainingResponse
1046///
1047/// Exact Registry and Directory authority produced by the durable draining fence.
1048///
1049
1050#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1051pub struct RootComponentDrainingResponse {
1052    pub operation_id: [u8; 32],
1053    pub component: ComponentInstanceId,
1054    pub previous_registry: ComponentRegistryHead,
1055    pub registry: ComponentRegistryHead,
1056    pub descendant_count: u32,
1057    pub descendant_content_hash: [u8; 32],
1058    pub directory_authority_hash: [u8; 32],
1059    pub started_at_ns: u64,
1060}
1061
1062///
1063/// RootComponentQuiescenceStopIntent
1064///
1065/// Exact runtime, Directory, module and controller authority frozen before stopping.
1066///
1067
1068#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1069pub struct RootComponentQuiescenceStopIntent {
1070    pub registry: ComponentRegistryHead,
1071    pub descendant_count: u32,
1072    pub descendant_content_hash: [u8; 32],
1073    pub canister_id: Principal,
1074    pub controller: Principal,
1075    pub expected_module_hash: [u8; 32],
1076    pub covered_fleet_registry_revision: u64,
1077    pub covered_fleet_registry_content_hash: [u8; 32],
1078    pub covered_authority_hash: [u8; 32],
1079    pub runtime_operation_id: [u8; 32],
1080    pub activation: ComponentRuntimeActivationEvidence,
1081    pub prepared_at_ns: u64,
1082}
1083
1084///
1085/// RootComponentQuiescentReceipt
1086///
1087/// Durable evidence that the exact prepared Component was independently observed stopped.
1088///
1089
1090#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1091pub struct RootComponentQuiescentReceipt {
1092    pub stop: RootComponentQuiescenceStopIntent,
1093    pub observed_module_hash: [u8; 32],
1094    pub quiesced_at_ns: u64,
1095}
1096
1097///
1098/// RootComponentQuiescencePhase
1099///
1100/// Monotonic progress from pre-effect stop authority to observed quiescence.
1101///
1102
1103#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1104pub enum RootComponentQuiescencePhase {
1105    StopIntent(RootComponentQuiescenceStopIntent),
1106    Quiescent(RootComponentQuiescentReceipt),
1107}
1108
1109///
1110/// RootComponentQuiescenceResponse
1111///
1112/// Current durable quiescence progress for one draining Component.
1113///
1114
1115#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1116pub struct RootComponentQuiescenceResponse {
1117    pub operation_id: [u8; 32],
1118    pub component: ComponentInstanceId,
1119    pub phase: RootComponentQuiescencePhase,
1120}
1121
1122///
1123/// RootComponentDrainingDescendantsEmpty
1124///
1125/// Exact current Registry proof that one draining Component has no descendants.
1126///
1127
1128#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1129pub struct RootComponentDrainingDescendantsEmpty {
1130    pub registry: ComponentRegistryHead,
1131    pub descendant_content_hash: [u8; 32],
1132}
1133
1134///
1135/// RootComponentDrainingAdvancePhase
1136///
1137/// One bounded driver result: current subtree progress or exact empty inventory.
1138///
1139
1140#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1141#[expect(
1142    clippy::large_enum_variant,
1143    reason = "wire result embeds the current durable subtree snapshot without a Rust-only indirection"
1144)]
1145pub enum RootComponentDrainingAdvancePhase {
1146    DescendantRemoval(RootComponentSubtreeRemovalResponse),
1147    DescendantsEmpty(RootComponentDrainingDescendantsEmpty),
1148}
1149
1150///
1151/// RootComponentDrainingAdvanceResponse
1152///
1153/// Current bounded progress of one terminally quiescent Component drain.
1154///
1155
1156#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1157pub struct RootComponentDrainingAdvanceResponse {
1158    pub operation_id: [u8; 32],
1159    pub component: ComponentInstanceId,
1160    pub phase: RootComponentDrainingAdvancePhase,
1161}
1162
1163///
1164/// RootComponentFinalInventory
1165///
1166/// Exact empty Component Registry and current Fleet Directory authority frozen before deletion.
1167///
1168
1169#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1170pub struct RootComponentFinalInventory {
1171    pub registry: ComponentRegistryHead,
1172    pub descendant_content_hash: [u8; 32],
1173    pub registry_encoded_bytes: u64,
1174    pub directory_synchronized_at_ns: u64,
1175    pub covered_fleet_registry_revision: u64,
1176    pub covered_fleet_registry_content_hash: [u8; 32],
1177    pub directory_authority_hash: [u8; 32],
1178    pub inventory_hash: [u8; 32],
1179    pub finalized_at_ns: u64,
1180}
1181
1182///
1183/// RootComponentFinalInventoryResponse
1184///
1185/// Response-idempotent receipt for one finalized empty Component inventory.
1186///
1187
1188#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1189pub struct RootComponentFinalInventoryResponse {
1190    pub operation_id: [u8; 32],
1191    pub component: ComponentInstanceId,
1192    pub inventory: RootComponentFinalInventory,
1193}
1194
1195///
1196/// RootComponentDeletionIntent
1197///
1198/// Complete final-inventory and quiescence authority frozen before top-level deletion.
1199///
1200
1201#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1202pub struct RootComponentDeletionIntent {
1203    pub final_inventory: RootComponentFinalInventory,
1204    pub quiescence: RootComponentQuiescentReceipt,
1205    pub prepared_at_ns: u64,
1206}
1207
1208///
1209/// RootComponentDeletedReceipt
1210///
1211/// Terminal authority retained after independently observed top-level absence.
1212///
1213
1214#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1215pub struct RootComponentDeletedReceipt {
1216    pub deletion: RootComponentDeletionIntent,
1217    pub deleted_at_ns: u64,
1218}
1219
1220///
1221/// RootComponentMembershipRemovedReceipt
1222///
1223/// Terminal local-membership removal and settled root/Spec accounting authority.
1224///
1225
1226#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1227pub struct RootComponentMembershipRemovedReceipt {
1228    pub deleted: RootComponentDeletedReceipt,
1229    pub allocation_operation_id: [u8; 32],
1230    pub remaining_spec_committed_instances: u32,
1231    pub root_committed_component_instances: u32,
1232    pub root_known_created_component_canisters: u32,
1233    pub root_registry_encoded_bytes: u64,
1234    pub removed_at_ns: u64,
1235    pub removal_hash: [u8; 32],
1236}
1237
1238///
1239/// RootComponentDeletionPhase
1240///
1241/// Monotonic top-level deletion progress through terminal local-membership removal.
1242///
1243
1244#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1245pub enum RootComponentDeletionPhase {
1246    DeleteIntent(RootComponentDeletionIntent),
1247    Deleted(RootComponentDeletedReceipt),
1248    MembershipRemoved(RootComponentMembershipRemovedReceipt),
1249}
1250
1251///
1252/// RootComponentDeletionResponse
1253///
1254/// Current durable deletion progress for one finalized top-level Component.
1255///
1256
1257#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1258pub struct RootComponentDeletionResponse {
1259    pub operation_id: [u8; 32],
1260    pub component: ComponentInstanceId,
1261    pub phase: RootComponentDeletionPhase,
1262}
1263
1264///
1265/// RootComponentChildCommitResponse
1266///
1267/// Exact committed child operation, authoritative Component Registry and next Directory head.
1268///
1269
1270#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1271pub struct RootComponentChildCommitResponse {
1272    pub allocation: RootComponentChildAllocationResponse,
1273    pub registry: ComponentRegistryPartitionResponse,
1274    pub directory: ComponentDirectoryHead,
1275}
1276
1277///
1278/// RootComponentChildDirectoryPreparationResponse
1279///
1280/// Exact child preparation plus stable bounded active-member Directory coverage.
1281///
1282
1283#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1284pub struct RootComponentChildDirectoryPreparationResponse {
1285    pub committed: RootComponentChildCommitResponse,
1286    pub child: ComponentRuntimeStatusResponse,
1287    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
1288    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
1289}
1290
1291///
1292/// RootComponentChildRuntimeActivationResponse
1293///
1294/// Exact child commitment plus independently observed Directory-bound runtime activation.
1295///
1296
1297#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1298pub struct RootComponentChildRuntimeActivationResponse {
1299    pub committed: RootComponentChildCommitResponse,
1300    pub child: ComponentRuntimeStatusResponse,
1301}
1302
1303///
1304/// RootComponentChildMembershipActivationResponse
1305///
1306/// Original child commitment plus active Registry, Directory and target convergence evidence.
1307///
1308
1309#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1310pub struct RootComponentChildMembershipActivationResponse {
1311    pub committed: RootComponentChildCommitResponse,
1312    pub registry: ComponentRegistryPartitionResponse,
1313    pub directory: ComponentDirectoryHead,
1314    pub child: ComponentRuntimeStatusResponse,
1315}
1316
1317///
1318/// RootComponentCommitResponse
1319///
1320/// Exact committed allocation, authoritative Registry row and derived Directory head.
1321///
1322
1323#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1324pub struct RootComponentCommitResponse {
1325    pub allocation: RootComponentAllocationResponse,
1326    pub registry: ComponentRegistryPartitionResponse,
1327    pub directory: ComponentDirectoryHead,
1328}
1329
1330///
1331/// RootComponentDirectoryPreparationResponse
1332///
1333/// Exact root authority plus independently observed target-local Directory preparation.
1334///
1335
1336#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1337pub struct RootComponentDirectoryPreparationResponse {
1338    pub committed: RootComponentCommitResponse,
1339    pub target: ComponentRuntimeStatusResponse,
1340}
1341
1342///
1343/// RootComponentRuntimeActivationResponse
1344///
1345/// Exact root authority plus independently observed target-local runtime activation.
1346///
1347
1348#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1349pub struct RootComponentRuntimeActivationResponse {
1350    pub committed: RootComponentCommitResponse,
1351    pub target: ComponentRuntimeStatusResponse,
1352}
1353
1354///
1355/// RootComponentMembershipActivationResponse
1356///
1357/// Exact active Registry authority plus independently observed current target Directory.
1358///
1359
1360#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1361pub struct RootComponentMembershipActivationResponse {
1362    pub allocation: RootComponentAllocationResponse,
1363    pub registry: ComponentRegistryPartitionResponse,
1364    pub directory: ComponentDirectoryHead,
1365    pub target: ComponentRuntimeStatusResponse,
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370    use super::*;
1371    use crate::{
1372        dto::root_store::RootStoreBootstrapRequest,
1373        ids::{
1374            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
1375            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
1376        },
1377    };
1378
1379    #[test]
1380    fn component_registry_contracts_round_trip_through_candid() {
1381        let request = RootComponentRegistryPreparationRequest {
1382            store_bootstrap: RootStoreBootstrapRequest {
1383                manifest_payload_size_bytes: 128,
1384            },
1385            expected_fleet_registry: FleetRegistryVersion {
1386                authority: fleet_registry_authority(),
1387                revision: 4,
1388                content_hash: [5; 32],
1389            },
1390        };
1391        let response = RootComponentRegistryStatusResponse {
1392            fleet_subnet_root: Principal::from_slice(&[6; 29]),
1393            prepared_against_registry: request.expected_fleet_registry.clone(),
1394            release_set: FleetSubnetRootReleaseSet {
1395                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1396                    [7; 32],
1397                )),
1398                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1399            },
1400            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
1401            next_allocation_sequence: 1,
1402            reserved_component_instances: 0,
1403            committed_component_instances: 0,
1404            managed_descendants: 0,
1405            known_created_component_canisters: 0,
1406            encoded_bytes: 0,
1407            initial_inventory: Some(RootComponentInitialInventoryStatus {
1408                fleet_activation_operation_id: [10; 32],
1409                component_count: 0,
1410                inventory_hash: [11; 32],
1411                sealed_at_ns: 12,
1412                directories_converged: true,
1413                root_runtime_activated: true,
1414            }),
1415        };
1416        let allocation = RootComponentAllocationResponse {
1417            operation_id: [10; 32],
1418            allocation_sequence: 1,
1419            component: ComponentInstanceId::from_generated_bytes([11; 32]),
1420            component_spec: "projects".parse().expect("Component Spec ID"),
1421            spec_hash: [12; 32],
1422            role: CanisterRole::new("project_hub"),
1423            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1424                caller: Principal::from_slice(&[13; 29]),
1425            },
1426            release_set: response.release_set,
1427            phase: RootComponentAllocationPhase::Reserved,
1428            creation: None,
1429            installation: None,
1430        };
1431        let created = RootComponentAllocationResponse {
1432            phase: RootComponentAllocationPhase::Created,
1433            creation: Some(RootComponentCreationEvidence {
1434                wasm_store: Principal::from_slice(&[14; 29]),
1435                payload_hash: [15; 32],
1436                payload_size_bytes: 4_096,
1437                initial_cycles: Cycles::new(5_000_000_000_000),
1438                controller: Principal::from_slice(&[6; 29]),
1439                canister: Some(Principal::from_slice(&[16; 29])),
1440            }),
1441            installation: None,
1442            ..allocation.clone()
1443        };
1444        let request_bytes = candid::encode_one(&request).expect("encode request");
1445        let response_bytes = candid::encode_one(&response).expect("encode response");
1446        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1447        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1448
1449        assert_eq!(
1450            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1451                .expect("decode request"),
1452            request
1453        );
1454        assert_eq!(
1455            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1456                .expect("decode response"),
1457            response
1458        );
1459        assert_eq!(
1460            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1461                .expect("decode allocation"),
1462            allocation
1463        );
1464        assert_eq!(
1465            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1466                .expect("decode created allocation"),
1467            created
1468        );
1469    }
1470
1471    #[test]
1472    fn component_commit_response_round_trips_through_candid() {
1473        let root = Principal::from_slice(&[6; 29]);
1474        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1475        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1476        let release_set = FleetSubnetRootReleaseSet {
1477            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1478                [7; 32],
1479            )),
1480            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1481        };
1482        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1483            caller: Principal::from_slice(&[13; 29]),
1484        };
1485        let binding = ComponentBinding {
1486            authority: fleet_registry_authority(),
1487            component,
1488            component_spec: component_spec.clone(),
1489            spec_hash: [12; 32],
1490            role: CanisterRole::new("project_hub"),
1491            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1492            fleet_subnet_root: root,
1493            canister_id: Principal::from_slice(&[16; 29]),
1494        };
1495        let head = ComponentRegistryHead {
1496            component,
1497            revision: 1,
1498            content_hash: [18; 32],
1499        };
1500        let committed = RootComponentCommitResponse {
1501            allocation: RootComponentAllocationResponse {
1502                operation_id: [10; 32],
1503                allocation_sequence: 1,
1504                component,
1505                component_spec,
1506                spec_hash: binding.spec_hash,
1507                role: binding.role.clone(),
1508                provisioning_origin: provisioning_origin.clone(),
1509                release_set,
1510                phase: RootComponentAllocationPhase::Committed,
1511                creation: Some(RootComponentCreationEvidence {
1512                    wasm_store: Principal::from_slice(&[14; 29]),
1513                    payload_hash: [15; 32],
1514                    payload_size_bytes: 4_096,
1515                    initial_cycles: Cycles::new(5_000_000_000_000),
1516                    controller: root,
1517                    canister: Some(binding.canister_id),
1518                }),
1519                installation: Some(RootComponentInstallEvidence {
1520                    raw_module_hash: [20; 32],
1521                    chunk_hashes: vec![vec![21; 32]],
1522                    binding: binding.clone(),
1523                }),
1524            },
1525            registry: ComponentRegistryPartitionResponse {
1526                head: head.clone(),
1527                binding: binding.clone(),
1528                provisioning_origin,
1529                release_set,
1530                status: ComponentLifecycleStatus::Prepared,
1531                reserved_descendants: 0,
1532                committed_descendants: 0,
1533                encoded_bytes: 2_048,
1534            },
1535            directory: ComponentDirectoryHead {
1536                provenance: ComponentDirectoryProvenance {
1537                    component: binding,
1538                    source_fleet_subnet_root: root,
1539                    component_registry_revision: head.revision,
1540                    component_registry_content_hash: head.content_hash,
1541                    synchronized_at_ns: 19,
1542                },
1543                descendant_count: 0,
1544            },
1545        };
1546        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1547
1548        assert_eq!(
1549            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1550                .expect("decode committed allocation"),
1551            committed
1552        );
1553    }
1554
1555    #[test]
1556    fn component_directory_page_contracts_round_trip_through_candid() {
1557        let root = Principal::from_slice(&[6; 29]);
1558        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1559        let binding = ComponentBinding {
1560            authority: fleet_registry_authority(),
1561            component,
1562            component_spec: "projects".parse().expect("Component Spec ID"),
1563            spec_hash: [12; 32],
1564            role: CanisterRole::new("project_hub"),
1565            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1566            fleet_subnet_root: root,
1567            canister_id: Principal::from_slice(&[16; 29]),
1568        };
1569        let directory = ComponentDirectoryHead {
1570            provenance: ComponentDirectoryProvenance {
1571                component: binding.clone(),
1572                source_fleet_subnet_root: root,
1573                component_registry_revision: 3,
1574                component_registry_content_hash: [18; 32],
1575                synchronized_at_ns: 19,
1576            },
1577            descendant_count: 1,
1578        };
1579        let request = ComponentDirectoryPageRequest {
1580            directory: directory.clone(),
1581            parent_canister_id: Some(binding.canister_id),
1582            role: Some(CanisterRole::new("project_instance")),
1583            status: Some(ComponentLifecycleStatus::Active),
1584            cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1585            limit: 50,
1586        };
1587        let response = ComponentDirectoryPageResponse {
1588            directory,
1589            entries: vec![ComponentDirectoryChildEntry {
1590                binding: ComponentChildBinding {
1591                    component: binding.clone(),
1592                    parent_canister_id: binding.canister_id,
1593                    role: CanisterRole::new("project_instance"),
1594                    canister_id: Principal::from_slice(&[21; 29]),
1595                },
1596                kind: ComponentChildKind::Instance,
1597                installed_artifact_hash: [22; 32],
1598                status: ComponentLifecycleStatus::Active,
1599            }],
1600            next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1601        };
1602        let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1603        let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1604
1605        assert_eq!(
1606            candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1607                .expect("decode Directory page request"),
1608            request
1609        );
1610        assert_eq!(
1611            candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1612                .expect("decode Directory page response"),
1613            response
1614        );
1615    }
1616
1617    fn fleet_registry_authority() -> FleetRegistryAuthority {
1618        FleetRegistryAuthority {
1619            binding: FleetCoordinatorBinding {
1620                fleet: crate::ids::FleetBinding {
1621                    fleet: FleetKey {
1622                        canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1623                        fleet_id: FleetId::from_generated_bytes([1; 32]),
1624                    },
1625                    app: AppId::from("toko"),
1626                },
1627                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1628                coordinator: Principal::from_slice(&[3; 29]),
1629            },
1630            epoch: 1,
1631        }
1632    }
1633
1634    #[test]
1635    fn component_creation_request_round_trips_through_candid() {
1636        let request = RootComponentCreationRequest {
1637            operation_id: [10; 32],
1638        };
1639        let bytes = candid::encode_one(request).expect("encode creation request");
1640
1641        assert_eq!(
1642            candid::decode_one::<RootComponentCreationRequest>(&bytes)
1643                .expect("decode creation request"),
1644            request
1645        );
1646    }
1647
1648    #[test]
1649    #[expect(
1650        clippy::too_many_lines,
1651        reason = "one Candid contract test covers every subtree-removal phase receipt"
1652    )]
1653    fn component_subtree_removal_contracts_round_trip_through_candid() {
1654        let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1655        let registry = ComponentRegistryHead {
1656            component,
1657            revision: 7,
1658            content_hash: [42; 32],
1659        };
1660        let request = RootComponentSubtreeRemovalRequest {
1661            operation_id: [43; 32],
1662            component,
1663            target_canister_id: Principal::from_slice(&[44; 29]),
1664            expected_registry: registry.clone(),
1665        };
1666        let status_request = RootComponentSubtreeRemovalStatusRequest {
1667            operation_id: request.operation_id,
1668            component,
1669        };
1670        let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1671            operation_id: request.operation_id,
1672            component,
1673            expected_traversal_steps: 1,
1674        };
1675        let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1676            operation_id: request.operation_id,
1677            component,
1678            expected_traversal_steps: 2,
1679            expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1680            expected_leaf_parent_canister_id: request.target_canister_id,
1681        };
1682        let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1683            observed_module_hash: [49; 32],
1684            stop: RootComponentSubtreeRemovalStopIntent {
1685                controller: Principal::from_slice(&[48; 29]),
1686                leaf: RootComponentSubtreeRemovalNode {
1687                    canister_id: Principal::from_slice(&[46; 29]),
1688                    parent_canister_id: request.target_canister_id,
1689                    role: CanisterRole::new("project_ledger"),
1690                    kind: ComponentChildKind::Singleton,
1691                    installed_artifact_hash: [47; 32],
1692                    status: ComponentLifecycleStatus::Active,
1693                },
1694            },
1695        };
1696        let response = RootComponentSubtreeRemovalResponse {
1697            operation_id: request.operation_id,
1698            component,
1699            target_canister_id: request.target_canister_id,
1700            target_parent_canister_id: Principal::from_slice(&[45; 29]),
1701            target_role: CanisterRole::new("project_instance"),
1702            target_status: ComponentLifecycleStatus::Active,
1703            reserved_against_registry: registry,
1704            maximum_completed_leaves: 4,
1705            completed_leaves: 1,
1706            traversal_steps: 2,
1707            phase: RootComponentSubtreeRemovalPhase::DirectorySynchronized(
1708                RootComponentSubtreeRemovalDirectorySynchronizedReceipt {
1709                    membership_removed: RootComponentSubtreeRemovalMembershipRemovedReceipt {
1710                        deleted: RootComponentSubtreeRemovalDeletedReceipt {
1711                            deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1712                        },
1713                        removed_from_registry: ComponentRegistryHead {
1714                            component,
1715                            revision: 8,
1716                            content_hash: [50; 32],
1717                        },
1718                        previous_descendant_content_hash: [51; 32],
1719                        previous_committed_descendants: 4,
1720                        registry: ComponentRegistryHead {
1721                            component,
1722                            revision: 9,
1723                            content_hash: [52; 32],
1724                        },
1725                        descendant_content_hash: [53; 32],
1726                        registry_encoded_bytes: 4_096,
1727                        reserved_descendants: 1,
1728                        committed_descendants: 3,
1729                        directory_synchronized_at_ns: 54,
1730                        directory_authority_hash: [55; 32],
1731                        parent_role_instances: 0,
1732                        root_managed_descendants: 4,
1733                        root_known_created_component_canisters: 4,
1734                    },
1735                    covered_fleet_registry_revision: 6,
1736                    covered_fleet_registry_content_hash: [56; 32],
1737                    covered_component_registry: ComponentRegistryHead {
1738                        component,
1739                        revision: 9,
1740                        content_hash: [52; 32],
1741                    },
1742                    covered_authority_hash: [55; 32],
1743                    owning_component: Some(
1744                        RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1745                            operation_id: [57; 32],
1746                            canister_id: Principal::from_slice(&[58; 29]),
1747                            activation: ComponentRuntimeActivationEvidence {
1748                                directory_authority_hash: [59; 32],
1749                                activated_at_ns: 60,
1750                            },
1751                        },
1752                    ),
1753                    parent: Some(RootComponentSubtreeRemovalDirectoryConvergenceEvidence {
1754                        operation_id: [61; 32],
1755                        canister_id: request.target_canister_id,
1756                        activation: ComponentRuntimeActivationEvidence {
1757                            directory_authority_hash: [62; 32],
1758                            activated_at_ns: 63,
1759                        },
1760                    }),
1761                },
1762            ),
1763        };
1764
1765        let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1766        let advance_bytes =
1767            candid::encode_one(advance_request).expect("encode subtree removal advance request");
1768        let stop_bytes =
1769            candid::encode_one(stop_request).expect("encode subtree removal stop request");
1770        let status_bytes =
1771            candid::encode_one(status_request).expect("encode subtree removal status request");
1772        let response_bytes =
1773            candid::encode_one(&response).expect("encode subtree removal response");
1774
1775        assert_eq!(
1776            candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1777                .expect("decode subtree removal request"),
1778            request
1779        );
1780        assert_eq!(
1781            candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1782                .expect("decode subtree removal advance request"),
1783            advance_request
1784        );
1785        assert_eq!(
1786            candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1787                .expect("decode subtree removal stop request"),
1788            stop_request
1789        );
1790        assert_eq!(
1791            candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1792                .expect("decode subtree removal status request"),
1793            status_request
1794        );
1795        assert_eq!(
1796            candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1797                .expect("decode subtree removal response"),
1798            response
1799        );
1800
1801        let mut quiescent_owner_response = response;
1802        let RootComponentSubtreeRemovalPhase::DirectorySynchronized(receipt) =
1803            &mut quiescent_owner_response.phase
1804        else {
1805            panic!("Directory-synchronized response");
1806        };
1807        receipt.owning_component = None;
1808        let quiescent_owner_bytes = candid::encode_one(&quiescent_owner_response)
1809            .expect("encode quiescent-owner subtree response");
1810        assert_eq!(
1811            candid::decode_one::<RootComponentSubtreeRemovalResponse>(&quiescent_owner_bytes)
1812                .expect("decode quiescent-owner subtree response"),
1813            quiescent_owner_response
1814        );
1815    }
1816
1817    #[test]
1818    fn component_draining_contracts_round_trip_through_candid() {
1819        let component = ComponentInstanceId::from_generated_bytes([60; 32]);
1820        let previous_registry = ComponentRegistryHead {
1821            component,
1822            revision: 7,
1823            content_hash: [61; 32],
1824        };
1825        let request = RootComponentDrainingRequest {
1826            operation_id: [62; 32],
1827            component,
1828            expected_registry: previous_registry.clone(),
1829        };
1830        let status_request = RootComponentDrainingStatusRequest {
1831            operation_id: request.operation_id,
1832            component,
1833        };
1834        let response = RootComponentDrainingResponse {
1835            operation_id: request.operation_id,
1836            component,
1837            previous_registry,
1838            registry: ComponentRegistryHead {
1839                component,
1840                revision: 8,
1841                content_hash: [63; 32],
1842            },
1843            descendant_count: 20_000,
1844            descendant_content_hash: [64; 32],
1845            directory_authority_hash: [65; 32],
1846            started_at_ns: 66,
1847        };
1848
1849        let request_bytes =
1850            candid::encode_one(&request).expect("encode Component draining request");
1851        let status_bytes =
1852            candid::encode_one(status_request).expect("encode Component draining status request");
1853        let response_bytes =
1854            candid::encode_one(&response).expect("encode Component draining response");
1855
1856        assert_eq!(
1857            candid::decode_one::<RootComponentDrainingRequest>(&request_bytes)
1858                .expect("decode Component draining request"),
1859            request
1860        );
1861        assert_eq!(
1862            candid::decode_one::<RootComponentDrainingStatusRequest>(&status_bytes)
1863                .expect("decode Component draining status request"),
1864            status_request
1865        );
1866        assert_eq!(
1867            candid::decode_one::<RootComponentDrainingResponse>(&response_bytes)
1868                .expect("decode Component draining response"),
1869            response
1870        );
1871    }
1872
1873    #[test]
1874    fn component_quiescence_contracts_round_trip_through_candid() {
1875        let component = ComponentInstanceId::from_generated_bytes([67; 32]);
1876        let registry = ComponentRegistryHead {
1877            component,
1878            revision: 9,
1879            content_hash: [68; 32],
1880        };
1881        let request = RootComponentQuiescenceRequest {
1882            operation_id: [69; 32],
1883            component,
1884            expected_registry: registry.clone(),
1885        };
1886        let status_request = RootComponentQuiescenceStatusRequest {
1887            operation_id: request.operation_id,
1888            component,
1889        };
1890        let stop = RootComponentQuiescenceStopIntent {
1891            registry,
1892            descendant_count: 20_000,
1893            descendant_content_hash: [70; 32],
1894            canister_id: Principal::from_slice(&[71; 29]),
1895            controller: Principal::from_slice(&[72; 29]),
1896            expected_module_hash: [73; 32],
1897            covered_fleet_registry_revision: 10,
1898            covered_fleet_registry_content_hash: [74; 32],
1899            covered_authority_hash: [75; 32],
1900            runtime_operation_id: [76; 32],
1901            activation: ComponentRuntimeActivationEvidence {
1902                directory_authority_hash: [77; 32],
1903                activated_at_ns: 78,
1904            },
1905            prepared_at_ns: 79,
1906        };
1907        let response = RootComponentQuiescenceResponse {
1908            operation_id: request.operation_id,
1909            component,
1910            phase: RootComponentQuiescencePhase::Quiescent(RootComponentQuiescentReceipt {
1911                stop,
1912                observed_module_hash: [73; 32],
1913                quiesced_at_ns: 80,
1914            }),
1915        };
1916
1917        let request_bytes = candid::encode_one(&request).expect("encode quiescence request");
1918        let status_bytes =
1919            candid::encode_one(status_request).expect("encode quiescence status request");
1920        let response_bytes = candid::encode_one(&response).expect("encode quiescence response");
1921        assert_eq!(
1922            candid::decode_one::<RootComponentQuiescenceRequest>(&request_bytes)
1923                .expect("decode quiescence request"),
1924            request
1925        );
1926        assert_eq!(
1927            candid::decode_one::<RootComponentQuiescenceStatusRequest>(&status_bytes)
1928                .expect("decode quiescence status request"),
1929            status_request
1930        );
1931        assert_eq!(
1932            candid::decode_one::<RootComponentQuiescenceResponse>(&response_bytes)
1933                .expect("decode quiescence response"),
1934            response
1935        );
1936    }
1937
1938    #[test]
1939    fn component_draining_advance_contracts_round_trip_through_candid() {
1940        let component = ComponentInstanceId::from_generated_bytes([81; 32]);
1941        let registry = ComponentRegistryHead {
1942            component,
1943            revision: 12,
1944            content_hash: [82; 32],
1945        };
1946        let request = RootComponentDrainingAdvanceRequest {
1947            operation_id: [83; 32],
1948            component,
1949        };
1950        let descendant_removal = RootComponentDrainingAdvanceResponse {
1951            operation_id: request.operation_id,
1952            component,
1953            phase: RootComponentDrainingAdvancePhase::DescendantRemoval(
1954                RootComponentSubtreeRemovalResponse {
1955                    operation_id: [84; 32],
1956                    component,
1957                    target_canister_id: Principal::from_slice(&[85; 29]),
1958                    target_parent_canister_id: Principal::from_slice(&[86; 29]),
1959                    target_role: CanisterRole::new("project_instance"),
1960                    target_status: ComponentLifecycleStatus::Active,
1961                    reserved_against_registry: registry.clone(),
1962                    maximum_completed_leaves: 20_000,
1963                    completed_leaves: 0,
1964                    traversal_steps: 0,
1965                    phase: RootComponentSubtreeRemovalPhase::Fenced,
1966                },
1967            ),
1968        };
1969        let descendants_empty = RootComponentDrainingAdvanceResponse {
1970            operation_id: request.operation_id,
1971            component,
1972            phase: RootComponentDrainingAdvancePhase::DescendantsEmpty(
1973                RootComponentDrainingDescendantsEmpty {
1974                    registry,
1975                    descendant_content_hash: [87; 32],
1976                },
1977            ),
1978        };
1979
1980        let request_bytes =
1981            candid::encode_one(request).expect("encode Component draining advance request");
1982        let removal_bytes = candid::encode_one(&descendant_removal)
1983            .expect("encode Component draining removal response");
1984        let empty_bytes = candid::encode_one(&descendants_empty)
1985            .expect("encode Component draining empty response");
1986
1987        assert_eq!(
1988            candid::decode_one::<RootComponentDrainingAdvanceRequest>(&request_bytes)
1989                .expect("decode Component draining advance request"),
1990            request
1991        );
1992        assert_eq!(
1993            candid::decode_one::<RootComponentDrainingAdvanceResponse>(&removal_bytes)
1994                .expect("decode Component draining removal response"),
1995            descendant_removal
1996        );
1997        assert_eq!(
1998            candid::decode_one::<RootComponentDrainingAdvanceResponse>(&empty_bytes)
1999                .expect("decode Component draining empty response"),
2000            descendants_empty
2001        );
2002    }
2003
2004    #[test]
2005    #[expect(
2006        clippy::too_many_lines,
2007        reason = "one wire-contract test keeps final inventory and its deletion authority aligned"
2008    )]
2009    fn component_final_inventory_contracts_round_trip_through_candid() {
2010        let component = ComponentInstanceId::from_generated_bytes([88; 32]);
2011        let registry = ComponentRegistryHead {
2012            component,
2013            revision: 21,
2014            content_hash: [89; 32],
2015        };
2016        let request = RootComponentFinalInventoryRequest {
2017            operation_id: [90; 32],
2018            component,
2019            expected_registry: registry.clone(),
2020        };
2021        let inventory = RootComponentFinalInventory {
2022            registry,
2023            descendant_content_hash: [91; 32],
2024            registry_encoded_bytes: 4_096,
2025            directory_synchronized_at_ns: 92,
2026            covered_fleet_registry_revision: 93,
2027            covered_fleet_registry_content_hash: [94; 32],
2028            directory_authority_hash: [95; 32],
2029            inventory_hash: [96; 32],
2030            finalized_at_ns: 97,
2031        };
2032        let response = RootComponentFinalInventoryResponse {
2033            operation_id: request.operation_id,
2034            component,
2035            inventory: inventory.clone(),
2036        };
2037        let deletion_request = RootComponentDeletionRequest {
2038            operation_id: request.operation_id,
2039            component,
2040            expected_inventory_hash: inventory.inventory_hash,
2041        };
2042        let deletion_status_request = RootComponentDeletionStatusRequest {
2043            operation_id: request.operation_id,
2044            component,
2045        };
2046        let deletion = RootComponentDeletionIntent {
2047            final_inventory: inventory,
2048            quiescence: RootComponentQuiescentReceipt {
2049                stop: RootComponentQuiescenceStopIntent {
2050                    registry: response.inventory.registry.clone(),
2051                    descendant_count: 0,
2052                    descendant_content_hash: response.inventory.descendant_content_hash,
2053                    canister_id: Principal::from_slice(&[98; 29]),
2054                    controller: Principal::from_slice(&[99; 29]),
2055                    expected_module_hash: [100; 32],
2056                    covered_fleet_registry_revision: 93,
2057                    covered_fleet_registry_content_hash: [94; 32],
2058                    covered_authority_hash: [101; 32],
2059                    runtime_operation_id: [102; 32],
2060                    activation: ComponentRuntimeActivationEvidence {
2061                        directory_authority_hash: [103; 32],
2062                        activated_at_ns: 104,
2063                    },
2064                    prepared_at_ns: 105,
2065                },
2066                observed_module_hash: [100; 32],
2067                quiesced_at_ns: 106,
2068            },
2069            prepared_at_ns: 107,
2070        };
2071        let deleted_receipt = RootComponentDeletedReceipt {
2072            deletion,
2073            deleted_at_ns: 108,
2074        };
2075        let deletion_response = RootComponentDeletionResponse {
2076            operation_id: request.operation_id,
2077            component,
2078            phase: RootComponentDeletionPhase::Deleted(deleted_receipt.clone()),
2079        };
2080        let membership_removed_response = RootComponentDeletionResponse {
2081            operation_id: request.operation_id,
2082            component,
2083            phase: RootComponentDeletionPhase::MembershipRemoved(
2084                RootComponentMembershipRemovedReceipt {
2085                    deleted: deleted_receipt,
2086                    allocation_operation_id: [109; 32],
2087                    remaining_spec_committed_instances: 2,
2088                    root_committed_component_instances: 3,
2089                    root_known_created_component_canisters: 4,
2090                    root_registry_encoded_bytes: 5_000,
2091                    removed_at_ns: 110,
2092                    removal_hash: [111; 32],
2093                },
2094            ),
2095        };
2096
2097        let request_bytes =
2098            candid::encode_one(&request).expect("encode Component final inventory request");
2099        let response_bytes =
2100            candid::encode_one(&response).expect("encode Component final inventory response");
2101        let deletion_request_bytes =
2102            candid::encode_one(deletion_request).expect("encode Component deletion request");
2103        let deletion_status_bytes = candid::encode_one(deletion_status_request)
2104            .expect("encode Component deletion status request");
2105        let deletion_response_bytes =
2106            candid::encode_one(&deletion_response).expect("encode Component deletion response");
2107        let membership_removed_response_bytes = candid::encode_one(&membership_removed_response)
2108            .expect("encode Component membership-removal response");
2109        assert_eq!(
2110            candid::decode_one::<RootComponentFinalInventoryRequest>(&request_bytes)
2111                .expect("decode Component final inventory request"),
2112            request
2113        );
2114        assert_eq!(
2115            candid::decode_one::<RootComponentFinalInventoryResponse>(&response_bytes)
2116                .expect("decode Component final inventory response"),
2117            response
2118        );
2119        assert_eq!(
2120            candid::decode_one::<RootComponentDeletionRequest>(&deletion_request_bytes)
2121                .expect("decode Component deletion request"),
2122            deletion_request
2123        );
2124        assert_eq!(
2125            candid::decode_one::<RootComponentDeletionStatusRequest>(&deletion_status_bytes)
2126                .expect("decode Component deletion status request"),
2127            deletion_status_request
2128        );
2129        assert_eq!(
2130            candid::decode_one::<RootComponentDeletionResponse>(&deletion_response_bytes)
2131                .expect("decode Component deletion response"),
2132            deletion_response
2133        );
2134        assert_eq!(
2135            candid::decode_one::<RootComponentDeletionResponse>(&membership_removed_response_bytes)
2136                .expect("decode Component membership-removal response"),
2137            membership_removed_response
2138        );
2139    }
2140
2141    #[test]
2142    fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
2143        let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
2144            operation_id: [50; 32],
2145            component: ComponentInstanceId::from_generated_bytes([51; 32]),
2146            expected_traversal_steps: 3,
2147            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2148            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2149        };
2150        let request = RootComponentSubtreeRemovalDeleteRequest {
2151            operation_id: prepare.operation_id,
2152            component: prepare.component,
2153            expected_traversal_steps: prepare.expected_traversal_steps,
2154            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2155            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2156        };
2157        let membership_request = RootComponentSubtreeRemovalMembershipRemovalRequest {
2158            operation_id: prepare.operation_id,
2159            component: prepare.component,
2160            expected_traversal_steps: prepare.expected_traversal_steps,
2161            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2162            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2163        };
2164        let directory_request = RootComponentSubtreeRemovalDirectorySynchronizationRequest {
2165            operation_id: prepare.operation_id,
2166            component: prepare.component,
2167            expected_traversal_steps: prepare.expected_traversal_steps,
2168            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2169            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2170        };
2171        let finalization_request = RootComponentSubtreeRemovalLeafFinalizationRequest {
2172            operation_id: prepare.operation_id,
2173            component: prepare.component,
2174            expected_traversal_steps: prepare.expected_traversal_steps,
2175            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
2176            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
2177        };
2178        let prepare_bytes = candid::encode_one(prepare)
2179            .expect("encode subtree removal deletion preparation request");
2180        let request_bytes =
2181            candid::encode_one(request).expect("encode subtree removal deletion request");
2182        let membership_request_bytes = candid::encode_one(membership_request)
2183            .expect("encode subtree removal membership-removal request");
2184        let directory_request_bytes = candid::encode_one(directory_request)
2185            .expect("encode subtree removal Directory synchronization request");
2186        let finalization_request_bytes = candid::encode_one(finalization_request)
2187            .expect("encode subtree removal leaf-finalization request");
2188
2189        assert_eq!(
2190            candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
2191                &prepare_bytes
2192            )
2193            .expect("decode subtree removal deletion preparation request"),
2194            prepare
2195        );
2196        assert_eq!(
2197            candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
2198                .expect("decode subtree removal deletion request"),
2199            request
2200        );
2201        assert_eq!(
2202            candid::decode_one::<RootComponentSubtreeRemovalMembershipRemovalRequest>(
2203                &membership_request_bytes
2204            )
2205            .expect("decode subtree removal membership-removal request"),
2206            membership_request
2207        );
2208        assert_eq!(
2209            candid::decode_one::<RootComponentSubtreeRemovalDirectorySynchronizationRequest>(
2210                &directory_request_bytes
2211            )
2212            .expect("decode subtree removal Directory synchronization request"),
2213            directory_request
2214        );
2215        assert_eq!(
2216            candid::decode_one::<RootComponentSubtreeRemovalLeafFinalizationRequest>(
2217                &finalization_request_bytes
2218            )
2219            .expect("decode subtree removal leaf-finalization request"),
2220            finalization_request
2221        );
2222    }
2223
2224    #[test]
2225    fn component_subtree_removal_stop_request_round_trips_through_candid() {
2226        let request = RootComponentSubtreeRemovalStopRequest {
2227            operation_id: [50; 32],
2228            component: ComponentInstanceId::from_generated_bytes([51; 32]),
2229            expected_traversal_steps: 3,
2230            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
2231            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
2232        };
2233        let bytes =
2234            candid::encode_one(request).expect("encode subtree removal stop execution request");
2235
2236        assert_eq!(
2237            candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
2238                .expect("decode subtree removal stop execution request"),
2239            request
2240        );
2241    }
2242
2243    #[test]
2244    #[expect(
2245        clippy::too_many_lines,
2246        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
2247    )]
2248    fn component_child_lifecycle_contracts_round_trip_through_candid() {
2249        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
2250        let registry = ComponentRegistryHead {
2251            component,
2252            revision: 2,
2253            content_hash: [12; 32],
2254        };
2255        let request = RootComponentChildAllocationRequest {
2256            operation_id: [13; 32],
2257            component,
2258            expected_registry: registry.clone(),
2259            child_role: CanisterRole::new("project_instance"),
2260        };
2261        let status_request = RootComponentChildAllocationStatusRequest {
2262            operation_id: request.operation_id,
2263            component,
2264        };
2265        let creation_request = RootComponentChildCreationRequest {
2266            operation_id: request.operation_id,
2267            component,
2268        };
2269        let install_request = RootComponentChildInstallRequest {
2270            operation_id: request.operation_id,
2271            component,
2272        };
2273        let commit_request = RootComponentChildCommitRequest {
2274            operation_id: request.operation_id,
2275            component,
2276        };
2277        let directory_request = RootComponentChildDirectoryPreparationRequest {
2278            operation_id: request.operation_id,
2279            component,
2280        };
2281        let activation_request = RootComponentChildRuntimeActivationRequest {
2282            operation_id: request.operation_id,
2283            component,
2284        };
2285        let membership_request = RootComponentChildMembershipActivationRequest {
2286            operation_id: request.operation_id,
2287            component,
2288        };
2289        let root = Principal::from_slice(&[17; 29]);
2290        let parent = Principal::from_slice(&[14; 29]);
2291        let child = Principal::from_slice(&[18; 29]);
2292        let child_binding = ComponentChildBinding {
2293            component: ComponentBinding {
2294                authority: fleet_registry_authority(),
2295                component,
2296                component_spec: "projects".parse().expect("Component Spec"),
2297                spec_hash: [19; 32],
2298                role: CanisterRole::new("project_hub"),
2299                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
2300                fleet_subnet_root: root,
2301                canister_id: parent,
2302            },
2303            parent_canister_id: parent,
2304            role: request.child_role.clone(),
2305            canister_id: child,
2306        };
2307        let response = RootComponentChildAllocationResponse {
2308            operation_id: request.operation_id,
2309            component,
2310            parent_canister_id: parent,
2311            parent_role: CanisterRole::new("project_hub"),
2312            child_role: request.child_role.clone(),
2313            child_kind: ComponentChildKind::Instance,
2314            maximum_instances_per_parent: 10_000,
2315            maximum_descendants: 20_000,
2316            maximum_registry_bytes: 16_777_216,
2317            reserved_against_registry: registry,
2318            release_set: FleetSubnetRootReleaseSet {
2319                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
2320                    [15; 32],
2321                )),
2322                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
2323            },
2324            phase: RootComponentAllocationPhase::Verified,
2325            creation: Some(RootComponentCreationEvidence {
2326                wasm_store: Principal::from_slice(&[21; 29]),
2327                payload_hash: [22; 32],
2328                payload_size_bytes: 4_096,
2329                initial_cycles: Cycles::new(5_000_000_000_000),
2330                controller: root,
2331                canister: Some(child),
2332            }),
2333            installation: Some(RootComponentChildInstallEvidence {
2334                raw_module_hash: [23; 32],
2335                chunk_hashes: vec![vec![24; 32]],
2336                binding: child_binding.clone(),
2337            }),
2338        };
2339        let commit_response = RootComponentChildCommitResponse {
2340            allocation: response.clone(),
2341            registry: ComponentRegistryPartitionResponse {
2342                head: ComponentRegistryHead {
2343                    component,
2344                    revision: 3,
2345                    content_hash: [25; 32],
2346                },
2347                binding: child_binding.component.clone(),
2348                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
2349                    caller: Principal::from_slice(&[26; 29]),
2350                },
2351                release_set: response.release_set,
2352                status: ComponentLifecycleStatus::Active,
2353                reserved_descendants: 0,
2354                committed_descendants: 1,
2355                encoded_bytes: 8_192,
2356            },
2357            directory: ComponentDirectoryHead {
2358                provenance: ComponentDirectoryProvenance {
2359                    component: child_binding.component.clone(),
2360                    source_fleet_subnet_root: root,
2361                    component_registry_revision: 3,
2362                    component_registry_content_hash: [25; 32],
2363                    synchronized_at_ns: 27,
2364                },
2365                descendant_count: 1,
2366            },
2367        };
2368        let runtime_authority = ComponentRuntimeDirectoryAuthority {
2369            fleet: FleetDirectorySnapshot {
2370                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
2371                    registry: FleetRegistryVersion {
2372                        authority: fleet_registry_authority(),
2373                        revision: 4,
2374                        content_hash: [28; 32],
2375                    },
2376                    source_fleet_subnet_root: root,
2377                },
2378                fleet_subnet_roots: vec![
2379                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
2380                        placement_subnet: commit_response.registry.binding.placement_subnet,
2381                        fleet_subnet_root: root,
2382                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
2383                    },
2384                ],
2385            },
2386            component: commit_response.directory.clone(),
2387        };
2388        let activation = ComponentRuntimeActivationEvidence {
2389            directory_authority_hash: [29; 32],
2390            activated_at_ns: 30,
2391        };
2392        let directory_response = RootComponentChildDirectoryPreparationResponse {
2393            committed: commit_response.clone(),
2394            child: ComponentRuntimeStatusResponse {
2395                operation_id: request.operation_id,
2396                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2397                phase: ComponentRuntimePhase::DirectoryPrepared,
2398                authority: Some(runtime_authority.clone()),
2399                authority_hash: Some([31; 32]),
2400                activation: None,
2401            },
2402            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
2403                operation_id: [32; 32],
2404                binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
2405                covered_authority: runtime_authority.clone(),
2406                covered_authority_hash: [31; 32],
2407                activation,
2408            },
2409            parent: None,
2410        };
2411        let activation_response = RootComponentChildRuntimeActivationResponse {
2412            committed: commit_response.clone(),
2413            child: ComponentRuntimeStatusResponse {
2414                operation_id: request.operation_id,
2415                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
2416                phase: ComponentRuntimePhase::Active,
2417                authority: Some(runtime_authority.clone()),
2418                authority_hash: Some([31; 32]),
2419                activation: Some(ComponentRuntimeActivationEvidence {
2420                    directory_authority_hash: [31; 32],
2421                    activated_at_ns: 33,
2422                }),
2423            },
2424        };
2425        let active_directory = ComponentDirectoryHead {
2426            provenance: ComponentDirectoryProvenance {
2427                component: child_binding.component.clone(),
2428                source_fleet_subnet_root: root,
2429                component_registry_revision: 4,
2430                component_registry_content_hash: [34; 32],
2431                synchronized_at_ns: 35,
2432            },
2433            descendant_count: 1,
2434        };
2435        let active_authority = ComponentRuntimeDirectoryAuthority {
2436            fleet: runtime_authority.fleet,
2437            component: active_directory.clone(),
2438        };
2439        let membership_response = RootComponentChildMembershipActivationResponse {
2440            committed: commit_response.clone(),
2441            registry: ComponentRegistryPartitionResponse {
2442                head: ComponentRegistryHead {
2443                    component,
2444                    revision: 4,
2445                    content_hash: [34; 32],
2446                },
2447                binding: child_binding.component.clone(),
2448                provisioning_origin: commit_response.registry.provisioning_origin.clone(),
2449                release_set: commit_response.registry.release_set,
2450                status: ComponentLifecycleStatus::Active,
2451                reserved_descendants: 0,
2452                committed_descendants: 1,
2453                encoded_bytes: 8_256,
2454            },
2455            directory: active_directory,
2456            child: ComponentRuntimeStatusResponse {
2457                operation_id: request.operation_id,
2458                binding: ManagedCanisterBinding::ComponentChild(child_binding),
2459                phase: ComponentRuntimePhase::Active,
2460                authority: Some(active_authority),
2461                authority_hash: Some([36; 32]),
2462                activation: Some(ComponentRuntimeActivationEvidence {
2463                    directory_authority_hash: [31; 32],
2464                    activated_at_ns: 33,
2465                }),
2466            },
2467        };
2468
2469        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
2470        let status_bytes =
2471            candid::encode_one(status_request).expect("encode child reservation status");
2472        let creation_bytes =
2473            candid::encode_one(creation_request).expect("encode child creation request");
2474        let install_bytes =
2475            candid::encode_one(install_request).expect("encode child install request");
2476        let response_bytes = candid::encode_one(&response).expect("encode child response");
2477        let commit_request_bytes =
2478            candid::encode_one(commit_request).expect("encode child commit request");
2479        let directory_request_bytes =
2480            candid::encode_one(directory_request).expect("encode child Directory request");
2481        let activation_request_bytes =
2482            candid::encode_one(activation_request).expect("encode child activation request");
2483        let membership_request_bytes =
2484            candid::encode_one(membership_request).expect("encode child membership request");
2485        let commit_response_bytes =
2486            candid::encode_one(&commit_response).expect("encode child commit response");
2487        let directory_response_bytes =
2488            candid::encode_one(&directory_response).expect("encode child Directory response");
2489        let activation_response_bytes =
2490            candid::encode_one(&activation_response).expect("encode child activation response");
2491        let membership_response_bytes =
2492            candid::encode_one(&membership_response).expect("encode child membership response");
2493
2494        assert_eq!(
2495            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
2496                .expect("decode child reservation"),
2497            request
2498        );
2499        assert_eq!(
2500            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
2501                .expect("decode child reservation status"),
2502            status_request
2503        );
2504        assert_eq!(
2505            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
2506                .expect("decode child creation request"),
2507            creation_request
2508        );
2509        assert_eq!(
2510            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
2511                .expect("decode child install request"),
2512            install_request
2513        );
2514        assert_eq!(
2515            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
2516                .expect("decode child response"),
2517            response
2518        );
2519        assert_eq!(
2520            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
2521                .expect("decode child commit request"),
2522            commit_request
2523        );
2524        assert_eq!(
2525            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
2526                &directory_request_bytes
2527            )
2528            .expect("decode child Directory request"),
2529            directory_request
2530        );
2531        assert_eq!(
2532            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
2533                .expect("decode child commit response"),
2534            commit_response
2535        );
2536        assert_eq!(
2537            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
2538                &directory_response_bytes
2539            )
2540            .expect("decode child Directory response"),
2541            directory_response
2542        );
2543        assert_eq!(
2544            candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
2545                &activation_request_bytes
2546            )
2547            .expect("decode child activation request"),
2548            activation_request
2549        );
2550        assert_eq!(
2551            candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
2552                &activation_response_bytes
2553            )
2554            .expect("decode child activation response"),
2555            activation_response
2556        );
2557        assert_eq!(
2558            candid::decode_one::<RootComponentChildMembershipActivationRequest>(
2559                &membership_request_bytes
2560            )
2561            .expect("decode child membership request"),
2562            membership_request
2563        );
2564        assert_eq!(
2565            candid::decode_one::<RootComponentChildMembershipActivationResponse>(
2566                &membership_response_bytes
2567            )
2568            .expect("decode child membership response"),
2569            membership_response
2570        );
2571    }
2572
2573    #[test]
2574    fn component_install_request_round_trips_through_candid() {
2575        let request = RootComponentInstallRequest {
2576            operation_id: [10; 32],
2577        };
2578        let bytes = candid::encode_one(request).expect("encode install request");
2579
2580        assert_eq!(
2581            candid::decode_one::<RootComponentInstallRequest>(&bytes)
2582                .expect("decode install request"),
2583            request
2584        );
2585    }
2586
2587    #[test]
2588    fn component_commit_request_round_trips_through_candid() {
2589        let request = RootComponentCommitRequest {
2590            operation_id: [10; 32],
2591        };
2592        let bytes = candid::encode_one(request).expect("encode commit request");
2593
2594        assert_eq!(
2595            candid::decode_one::<RootComponentCommitRequest>(&bytes)
2596                .expect("decode commit request"),
2597            request
2598        );
2599    }
2600
2601    #[test]
2602    fn component_runtime_activation_requests_round_trip_through_candid() {
2603        let root_request = RootComponentRuntimeActivationRequest {
2604            operation_id: [22; 32],
2605        };
2606        let target_request = ComponentRuntimeActivationRequest {
2607            operation_id: root_request.operation_id,
2608            directory_authority_hash: [23; 32],
2609        };
2610        let membership_request = RootComponentMembershipActivationRequest {
2611            operation_id: root_request.operation_id,
2612        };
2613        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
2614        let target_bytes =
2615            candid::encode_one(target_request).expect("encode target activation request");
2616        let membership_bytes =
2617            candid::encode_one(membership_request).expect("encode membership activation request");
2618
2619        assert_eq!(
2620            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
2621                .expect("decode root activation request"),
2622            root_request
2623        );
2624        assert_eq!(
2625            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
2626                .expect("decode target activation request"),
2627            target_request
2628        );
2629        assert_eq!(
2630            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
2631                .expect("decode membership activation request"),
2632            membership_request
2633        );
2634    }
2635}