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