Skip to main content

canic_core/dto/
component_registry.rs

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