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