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