Skip to main content

canic_core/dto/
component_registry.rs

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