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/// RootComponentSubtreeRemovalStatusRequest
210///
211/// Controller lookup key for one durable child-subtree removal operation.
212///
213
214#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub struct RootComponentSubtreeRemovalStatusRequest {
216    pub operation_id: [u8; 32],
217    pub component: ComponentInstanceId,
218}
219
220///
221/// RootComponentChildCreationRequest
222///
223/// Parent command continuing one already reserved direct-child operation.
224///
225
226#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
227pub struct RootComponentChildCreationRequest {
228    pub operation_id: [u8; 32],
229    pub component: ComponentInstanceId,
230}
231
232///
233/// RootComponentChildInstallRequest
234///
235/// Parent command installing and verifying one already created direct-child operation.
236///
237
238#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
239pub struct RootComponentChildInstallRequest {
240    pub operation_id: [u8; 32],
241    pub component: ComponentInstanceId,
242}
243
244///
245/// RootComponentChildCommitRequest
246///
247/// Parent command committing one already verified direct-child operation.
248///
249
250#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
251pub struct RootComponentChildCommitRequest {
252    pub operation_id: [u8; 32],
253    pub component: ComponentInstanceId,
254}
255
256///
257/// RootComponentChildDirectoryPreparationRequest
258///
259/// Parent command distributing one committed child's Directory and converging its affected members.
260///
261
262#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
263pub struct RootComponentChildDirectoryPreparationRequest {
264    pub operation_id: [u8; 32],
265    pub component: ComponentInstanceId,
266}
267
268///
269/// RootComponentChildRuntimeActivationRequest
270///
271/// Parent command activating one Directory-prepared direct-child runtime.
272///
273
274#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
275pub struct RootComponentChildRuntimeActivationRequest {
276    pub operation_id: [u8; 32],
277    pub component: ComponentInstanceId,
278}
279
280///
281/// RootComponentChildMembershipActivationRequest
282///
283/// Parent command activating one runtime-active direct child's Registry membership.
284///
285
286#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
287pub struct RootComponentChildMembershipActivationRequest {
288    pub operation_id: [u8; 32],
289    pub component: ComponentInstanceId,
290}
291
292///
293/// RootComponentCreationRequest
294///
295/// Controller command continuing one already reserved top-level Component operation.
296///
297
298#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
299pub struct RootComponentCreationRequest {
300    pub operation_id: [u8; 32],
301}
302
303///
304/// RootComponentInstallRequest
305///
306/// Controller command continuing one already created top-level Component operation.
307///
308
309#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
310pub struct RootComponentInstallRequest {
311    pub operation_id: [u8; 32],
312}
313
314///
315/// RootComponentCommitRequest
316///
317/// Controller command committing one already verified top-level Component operation.
318///
319
320#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct RootComponentCommitRequest {
322    pub operation_id: [u8; 32],
323}
324
325///
326/// RootComponentDirectoryPreparationRequest
327///
328/// Controller command distributing exact Directories to one committed top-level Component.
329///
330
331#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
332pub struct RootComponentDirectoryPreparationRequest {
333    pub operation_id: [u8; 32],
334}
335
336///
337/// RootComponentRuntimeActivationRequest
338///
339/// Controller command activating one Directory-prepared top-level Component runtime.
340///
341
342#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
343pub struct RootComponentRuntimeActivationRequest {
344    pub operation_id: [u8; 32],
345}
346
347///
348/// RootComponentMembershipActivationRequest
349///
350/// Controller command activating one runtime-active Component's Registry membership.
351///
352
353#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
354pub struct RootComponentMembershipActivationRequest {
355    pub operation_id: [u8; 32],
356}
357
358///
359/// ComponentProvisioningOrigin
360///
361/// Authenticated causal authority retained with one top-level Component allocation.
362///
363
364#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub enum ComponentProvisioningOrigin {
366    FleetAdministrator { caller: Principal },
367}
368
369///
370/// RootComponentAllocationPhase
371///
372/// Durable root-local progress of one top-level Component allocation operation.
373///
374
375#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
376pub enum RootComponentAllocationPhase {
377    Reserved,
378    CreationIntent,
379    Created,
380    InstallIntent,
381    Installed,
382    Verified,
383    Committed,
384}
385
386///
387/// RootComponentSubtreeRemovalPhase
388///
389/// Durable root-local progress of one child-subtree removal operation.
390///
391
392#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
393pub enum RootComponentSubtreeRemovalPhase {
394    Fenced,
395    Traversing(RootComponentSubtreeRemovalNode),
396    LeafSelected(RootComponentSubtreeRemovalNode),
397    StopIntent(RootComponentSubtreeRemovalStopIntent),
398    Stopped(RootComponentSubtreeRemovalStoppedReceipt),
399    DeleteIntent(RootComponentSubtreeRemovalDeleteIntent),
400    Deleted(RootComponentSubtreeRemovalDeletedReceipt),
401}
402
403///
404/// RootComponentSubtreeRemovalNode
405///
406/// Exact registered child selected as a traversal cursor or removable leaf.
407///
408
409#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
410pub struct RootComponentSubtreeRemovalNode {
411    pub canister_id: Principal,
412    pub parent_canister_id: Principal,
413    pub role: CanisterRole,
414    pub kind: ComponentChildKind,
415    pub installed_artifact_hash: [u8; 32],
416    pub status: ComponentLifecycleStatus,
417}
418
419///
420/// RootComponentSubtreeRemovalStopIntent
421///
422/// Exact registered leaf and sole root controller frozen before a stop call.
423///
424
425#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
426pub struct RootComponentSubtreeRemovalStopIntent {
427    pub leaf: RootComponentSubtreeRemovalNode,
428    pub controller: Principal,
429}
430
431///
432/// RootComponentSubtreeRemovalStoppedReceipt
433///
434/// Frozen stop authority plus the independently observed installed module.
435///
436
437#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
438pub struct RootComponentSubtreeRemovalStoppedReceipt {
439    pub stop: RootComponentSubtreeRemovalStopIntent,
440    pub observed_module_hash: [u8; 32],
441}
442
443///
444/// RootComponentSubtreeRemovalDeleteIntent
445///
446/// Exact stopped receipt frozen before the destructive management call.
447///
448
449#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
450pub struct RootComponentSubtreeRemovalDeleteIntent {
451    pub stopped: RootComponentSubtreeRemovalStoppedReceipt,
452}
453
454///
455/// RootComponentSubtreeRemovalDeletedReceipt
456///
457/// Frozen deletion authority committed only after independently observed absence.
458///
459
460#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
461pub struct RootComponentSubtreeRemovalDeletedReceipt {
462    pub deletion: RootComponentSubtreeRemovalDeleteIntent,
463}
464
465///
466/// ComponentLifecycleStatus
467///
468/// Root-owned runtime lifecycle state of one committed Component Registry member.
469///
470
471#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
472pub enum ComponentLifecycleStatus {
473    Prepared,
474    Active,
475    Draining,
476    Removed,
477}
478
479///
480/// ComponentRegistryHead
481///
482/// Exact independently versioned authority of one Component Registry partition.
483///
484
485#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
486pub struct ComponentRegistryHead {
487    pub component: ComponentInstanceId,
488    pub revision: u64,
489    pub content_hash: [u8; 32],
490}
491
492///
493/// ComponentRegistryPartitionRequest
494///
495/// Read-only lookup key for one committed Component Registry partition.
496///
497
498#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
499pub struct ComponentRegistryPartitionRequest {
500    pub component: ComponentInstanceId,
501}
502
503///
504/// ComponentRegistryPartitionResponse
505///
506/// Protected top-level row and independent head of one Component Registry partition.
507///
508
509#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
510pub struct ComponentRegistryPartitionResponse {
511    pub head: ComponentRegistryHead,
512    pub binding: ComponentBinding,
513    pub provisioning_origin: ComponentProvisioningOrigin,
514    pub release_set: FleetSubnetRootReleaseSet,
515    pub status: ComponentLifecycleStatus,
516    pub reserved_descendants: u32,
517    pub committed_descendants: u32,
518    pub encoded_bytes: u64,
519}
520
521///
522/// ComponentDirectoryProvenance
523///
524/// Exact Component Registry authority from which one Component Directory is derived.
525///
526
527#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
528pub struct ComponentDirectoryProvenance {
529    pub component: ComponentBinding,
530    pub source_fleet_subnet_root: Principal,
531    pub component_registry_revision: u64,
532    pub component_registry_content_hash: [u8; 32],
533    pub synchronized_at_ns: u64,
534}
535
536///
537/// ComponentDirectoryHead
538///
539/// Compact independently versioned discovery projection for one Component tree.
540///
541
542#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
543pub struct ComponentDirectoryHead {
544    pub provenance: ComponentDirectoryProvenance,
545    pub descendant_count: u32,
546}
547
548///
549/// ComponentDirectoryHeadRequest
550///
551/// Read-only lookup key for one committed Component Directory head.
552///
553
554#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
555pub struct ComponentDirectoryHeadRequest {
556    pub component: ComponentInstanceId,
557}
558
559///
560/// ComponentDirectoryPageCursor
561///
562/// Opaque revision- and filter-bound continuation for one bounded Directory page.
563///
564
565#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
566pub struct ComponentDirectoryPageCursor(pub Vec<u8>);
567
568///
569/// ComponentDirectoryPageRequest
570///
571/// Bounded member query against one exact current Component Directory authority.
572///
573
574#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
575pub struct ComponentDirectoryPageRequest {
576    pub directory: ComponentDirectoryHead,
577    pub parent_canister_id: Option<Principal>,
578    pub role: Option<CanisterRole>,
579    pub status: Option<ComponentLifecycleStatus>,
580    pub cursor: Option<ComponentDirectoryPageCursor>,
581    pub limit: u16,
582}
583
584///
585/// ComponentDirectoryChildEntry
586///
587/// One authoritative normalized child projected with its complete protected binding.
588///
589
590#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
591pub struct ComponentDirectoryChildEntry {
592    pub binding: ComponentChildBinding,
593    pub kind: ComponentChildKind,
594    pub installed_artifact_hash: [u8; 32],
595    pub status: ComponentLifecycleStatus,
596}
597
598///
599/// ComponentDirectoryPageResponse
600///
601/// One bounded caller-scoped page under the exact requested Directory head.
602///
603
604#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
605pub struct ComponentDirectoryPageResponse {
606    pub directory: ComponentDirectoryHead,
607    pub entries: Vec<ComponentDirectoryChildEntry>,
608    pub next_cursor: Option<ComponentDirectoryPageCursor>,
609}
610
611///
612/// ComponentRuntimeDirectoryAuthority
613///
614/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
615///
616
617#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
618pub struct ComponentRuntimeDirectoryAuthority {
619    pub fleet: FleetDirectorySnapshot,
620    pub component: ComponentDirectoryHead,
621}
622
623///
624/// ComponentRuntimeDirectoryPreparationRequest
625///
626/// Root-issued exact Directory preparation command for one managed Component-tree node.
627///
628
629#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
630pub struct ComponentRuntimeDirectoryPreparationRequest {
631    pub operation_id: [u8; 32],
632    pub authority: ComponentRuntimeDirectoryAuthority,
633}
634
635///
636/// ComponentRuntimeDirectorySynchronizationRequest
637///
638/// Root-issued replacement of one active managed Component node's current Directory authority.
639///
640
641#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
642pub struct ComponentRuntimeDirectorySynchronizationRequest {
643    pub operation_id: [u8; 32],
644    pub authority: ComponentRuntimeDirectoryAuthority,
645}
646
647///
648/// ComponentRuntimePhase
649///
650/// Target-local progress from installation through Component runtime activation.
651///
652
653#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
654pub enum ComponentRuntimePhase {
655    AwaitingDirectory,
656    DirectoryPrepared,
657    Active,
658}
659
660///
661/// ComponentRuntimeActivationEvidence
662///
663/// Exact retained Directory authority under which one Component runtime became Active.
664///
665
666#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
667pub struct ComponentRuntimeActivationEvidence {
668    pub directory_authority_hash: [u8; 32],
669    pub activated_at_ns: u64,
670}
671
672///
673/// ComponentRuntimeActivationRequest
674///
675/// Root-issued exact activation command for one Directory-prepared managed Component node.
676///
677
678#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
679pub struct ComponentRuntimeActivationRequest {
680    pub operation_id: [u8; 32],
681    pub directory_authority_hash: [u8; 32],
682}
683
684///
685/// ComponentRuntimeStatusResponse
686///
687/// Independently observable target-local binding and exact retained Directory authority.
688///
689
690#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
691pub struct ComponentRuntimeStatusResponse {
692    pub operation_id: [u8; 32],
693    pub binding: ManagedCanisterBinding,
694    pub phase: ComponentRuntimePhase,
695    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
696    pub authority_hash: Option<[u8; 32]>,
697    pub activation: Option<ComponentRuntimeActivationEvidence>,
698}
699
700///
701/// ComponentRuntimeDirectoryConvergenceEvidence
702///
703/// Stable root evidence that one active member covered at least the required Directory authority.
704///
705
706#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
707pub struct ComponentRuntimeDirectoryConvergenceEvidence {
708    pub operation_id: [u8; 32],
709    pub binding: ManagedCanisterBinding,
710    pub covered_authority: ComponentRuntimeDirectoryAuthority,
711    pub covered_authority_hash: [u8; 32],
712    pub activation: ComponentRuntimeActivationEvidence,
713}
714
715///
716/// RootComponentCreationEvidence
717///
718/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
719///
720
721#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
722pub struct RootComponentCreationEvidence {
723    pub wasm_store: Principal,
724    pub payload_hash: [u8; 32],
725    pub payload_size_bytes: u64,
726    pub initial_cycles: Cycles,
727    pub controller: Principal,
728    pub canister: Option<Principal>,
729}
730
731///
732/// RootComponentInstallEvidence
733///
734/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
735///
736
737#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
738pub struct RootComponentInstallEvidence {
739    pub raw_module_hash: [u8; 32],
740    pub chunk_hashes: Vec<Vec<u8>>,
741    pub binding: ComponentBinding,
742}
743
744///
745/// RootComponentChildInstallEvidence
746///
747/// Exact child module and immutable retained binding frozen before installation.
748///
749
750#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
751pub struct RootComponentChildInstallEvidence {
752    pub raw_module_hash: [u8; 32],
753    pub chunk_hashes: Vec<Vec<u8>>,
754    pub binding: ComponentChildBinding,
755}
756
757///
758/// RootComponentAllocationResponse
759///
760/// Durable identity reservation returned identically for exact operation retry.
761///
762
763#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
764pub struct RootComponentAllocationResponse {
765    pub operation_id: [u8; 32],
766    pub allocation_sequence: u64,
767    pub component: ComponentInstanceId,
768    pub component_spec: ComponentSpecId,
769    pub spec_hash: [u8; 32],
770    pub role: CanisterRole,
771    pub provisioning_origin: ComponentProvisioningOrigin,
772    pub release_set: FleetSubnetRootReleaseSet,
773    pub phase: RootComponentAllocationPhase,
774    pub creation: Option<RootComponentCreationEvidence>,
775    pub installation: Option<RootComponentInstallEvidence>,
776}
777
778///
779/// RootComponentChildAllocationResponse
780///
781/// Durable direct-child lifecycle progress returned identically for exact parent retry.
782///
783
784#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
785pub struct RootComponentChildAllocationResponse {
786    pub operation_id: [u8; 32],
787    pub component: ComponentInstanceId,
788    pub parent_canister_id: Principal,
789    pub parent_role: CanisterRole,
790    pub child_role: CanisterRole,
791    pub child_kind: ComponentChildKind,
792    pub maximum_instances_per_parent: u32,
793    pub maximum_descendants: u32,
794    pub maximum_registry_bytes: u64,
795    pub reserved_against_registry: ComponentRegistryHead,
796    pub release_set: FleetSubnetRootReleaseSet,
797    pub phase: RootComponentAllocationPhase,
798    pub creation: Option<RootComponentCreationEvidence>,
799    pub installation: Option<RootComponentChildInstallEvidence>,
800}
801
802///
803/// RootComponentSubtreeRemovalResponse
804///
805/// Current durable snapshot of one monotonic subtree-removal operation.
806///
807
808#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
809pub struct RootComponentSubtreeRemovalResponse {
810    pub operation_id: [u8; 32],
811    pub component: ComponentInstanceId,
812    pub target_canister_id: Principal,
813    pub target_parent_canister_id: Principal,
814    pub target_role: CanisterRole,
815    pub target_status: ComponentLifecycleStatus,
816    pub reserved_against_registry: ComponentRegistryHead,
817    pub traversal_steps: u32,
818    pub phase: RootComponentSubtreeRemovalPhase,
819}
820
821///
822/// RootComponentChildCommitResponse
823///
824/// Exact committed child operation, authoritative Component Registry and next Directory head.
825///
826
827#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
828pub struct RootComponentChildCommitResponse {
829    pub allocation: RootComponentChildAllocationResponse,
830    pub registry: ComponentRegistryPartitionResponse,
831    pub directory: ComponentDirectoryHead,
832}
833
834///
835/// RootComponentChildDirectoryPreparationResponse
836///
837/// Exact child preparation plus stable bounded active-member Directory coverage.
838///
839
840#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
841pub struct RootComponentChildDirectoryPreparationResponse {
842    pub committed: RootComponentChildCommitResponse,
843    pub child: ComponentRuntimeStatusResponse,
844    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
845    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
846}
847
848///
849/// RootComponentChildRuntimeActivationResponse
850///
851/// Exact child commitment plus independently observed Directory-bound runtime activation.
852///
853
854#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
855pub struct RootComponentChildRuntimeActivationResponse {
856    pub committed: RootComponentChildCommitResponse,
857    pub child: ComponentRuntimeStatusResponse,
858}
859
860///
861/// RootComponentChildMembershipActivationResponse
862///
863/// Original child commitment plus active Registry, Directory and target convergence evidence.
864///
865
866#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
867pub struct RootComponentChildMembershipActivationResponse {
868    pub committed: RootComponentChildCommitResponse,
869    pub registry: ComponentRegistryPartitionResponse,
870    pub directory: ComponentDirectoryHead,
871    pub child: ComponentRuntimeStatusResponse,
872}
873
874///
875/// RootComponentCommitResponse
876///
877/// Exact committed allocation, authoritative Registry row and derived Directory head.
878///
879
880#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
881pub struct RootComponentCommitResponse {
882    pub allocation: RootComponentAllocationResponse,
883    pub registry: ComponentRegistryPartitionResponse,
884    pub directory: ComponentDirectoryHead,
885}
886
887///
888/// RootComponentDirectoryPreparationResponse
889///
890/// Exact root authority plus independently observed target-local Directory preparation.
891///
892
893#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
894pub struct RootComponentDirectoryPreparationResponse {
895    pub committed: RootComponentCommitResponse,
896    pub target: ComponentRuntimeStatusResponse,
897}
898
899///
900/// RootComponentRuntimeActivationResponse
901///
902/// Exact root authority plus independently observed target-local runtime activation.
903///
904
905#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
906pub struct RootComponentRuntimeActivationResponse {
907    pub committed: RootComponentCommitResponse,
908    pub target: ComponentRuntimeStatusResponse,
909}
910
911///
912/// RootComponentMembershipActivationResponse
913///
914/// Exact active Registry authority plus independently observed current target Directory.
915///
916
917#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
918pub struct RootComponentMembershipActivationResponse {
919    pub allocation: RootComponentAllocationResponse,
920    pub registry: ComponentRegistryPartitionResponse,
921    pub directory: ComponentDirectoryHead,
922    pub target: ComponentRuntimeStatusResponse,
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use crate::{
929        dto::root_store::RootStoreBootstrapRequest,
930        ids::{
931            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
932            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
933        },
934    };
935
936    #[test]
937    fn component_registry_contracts_round_trip_through_candid() {
938        let request = RootComponentRegistryPreparationRequest {
939            store_bootstrap: RootStoreBootstrapRequest {
940                manifest_payload_size_bytes: 128,
941            },
942            expected_fleet_registry: FleetRegistryVersion {
943                authority: fleet_registry_authority(),
944                revision: 4,
945                content_hash: [5; 32],
946            },
947        };
948        let response = RootComponentRegistryStatusResponse {
949            fleet_subnet_root: Principal::from_slice(&[6; 29]),
950            prepared_against_registry: request.expected_fleet_registry.clone(),
951            release_set: FleetSubnetRootReleaseSet {
952                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
953                    [7; 32],
954                )),
955                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
956            },
957            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
958            next_allocation_sequence: 1,
959            reserved_component_instances: 0,
960            committed_component_instances: 0,
961            managed_descendants: 0,
962            known_created_component_canisters: 0,
963            encoded_bytes: 0,
964            initial_inventory: Some(RootComponentInitialInventoryStatus {
965                fleet_activation_operation_id: [10; 32],
966                component_count: 0,
967                inventory_hash: [11; 32],
968                sealed_at_ns: 12,
969                directories_converged: true,
970                root_runtime_activated: true,
971            }),
972        };
973        let allocation = RootComponentAllocationResponse {
974            operation_id: [10; 32],
975            allocation_sequence: 1,
976            component: ComponentInstanceId::from_generated_bytes([11; 32]),
977            component_spec: "projects".parse().expect("Component Spec ID"),
978            spec_hash: [12; 32],
979            role: CanisterRole::new("project_hub"),
980            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
981                caller: Principal::from_slice(&[13; 29]),
982            },
983            release_set: response.release_set,
984            phase: RootComponentAllocationPhase::Reserved,
985            creation: None,
986            installation: None,
987        };
988        let created = RootComponentAllocationResponse {
989            phase: RootComponentAllocationPhase::Created,
990            creation: Some(RootComponentCreationEvidence {
991                wasm_store: Principal::from_slice(&[14; 29]),
992                payload_hash: [15; 32],
993                payload_size_bytes: 4_096,
994                initial_cycles: Cycles::new(5_000_000_000_000),
995                controller: Principal::from_slice(&[6; 29]),
996                canister: Some(Principal::from_slice(&[16; 29])),
997            }),
998            installation: None,
999            ..allocation.clone()
1000        };
1001        let request_bytes = candid::encode_one(&request).expect("encode request");
1002        let response_bytes = candid::encode_one(&response).expect("encode response");
1003        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
1004        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
1005
1006        assert_eq!(
1007            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
1008                .expect("decode request"),
1009            request
1010        );
1011        assert_eq!(
1012            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
1013                .expect("decode response"),
1014            response
1015        );
1016        assert_eq!(
1017            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
1018                .expect("decode allocation"),
1019            allocation
1020        );
1021        assert_eq!(
1022            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
1023                .expect("decode created allocation"),
1024            created
1025        );
1026    }
1027
1028    #[test]
1029    fn component_commit_response_round_trips_through_candid() {
1030        let root = Principal::from_slice(&[6; 29]);
1031        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1032        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
1033        let release_set = FleetSubnetRootReleaseSet {
1034            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1035                [7; 32],
1036            )),
1037            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
1038        };
1039        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
1040            caller: Principal::from_slice(&[13; 29]),
1041        };
1042        let binding = ComponentBinding {
1043            authority: fleet_registry_authority(),
1044            component,
1045            component_spec: component_spec.clone(),
1046            spec_hash: [12; 32],
1047            role: CanisterRole::new("project_hub"),
1048            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1049            fleet_subnet_root: root,
1050            canister_id: Principal::from_slice(&[16; 29]),
1051        };
1052        let head = ComponentRegistryHead {
1053            component,
1054            revision: 1,
1055            content_hash: [18; 32],
1056        };
1057        let committed = RootComponentCommitResponse {
1058            allocation: RootComponentAllocationResponse {
1059                operation_id: [10; 32],
1060                allocation_sequence: 1,
1061                component,
1062                component_spec,
1063                spec_hash: binding.spec_hash,
1064                role: binding.role.clone(),
1065                provisioning_origin: provisioning_origin.clone(),
1066                release_set,
1067                phase: RootComponentAllocationPhase::Committed,
1068                creation: Some(RootComponentCreationEvidence {
1069                    wasm_store: Principal::from_slice(&[14; 29]),
1070                    payload_hash: [15; 32],
1071                    payload_size_bytes: 4_096,
1072                    initial_cycles: Cycles::new(5_000_000_000_000),
1073                    controller: root,
1074                    canister: Some(binding.canister_id),
1075                }),
1076                installation: Some(RootComponentInstallEvidence {
1077                    raw_module_hash: [20; 32],
1078                    chunk_hashes: vec![vec![21; 32]],
1079                    binding: binding.clone(),
1080                }),
1081            },
1082            registry: ComponentRegistryPartitionResponse {
1083                head: head.clone(),
1084                binding: binding.clone(),
1085                provisioning_origin,
1086                release_set,
1087                status: ComponentLifecycleStatus::Prepared,
1088                reserved_descendants: 0,
1089                committed_descendants: 0,
1090                encoded_bytes: 2_048,
1091            },
1092            directory: ComponentDirectoryHead {
1093                provenance: ComponentDirectoryProvenance {
1094                    component: binding,
1095                    source_fleet_subnet_root: root,
1096                    component_registry_revision: head.revision,
1097                    component_registry_content_hash: head.content_hash,
1098                    synchronized_at_ns: 19,
1099                },
1100                descendant_count: 0,
1101            },
1102        };
1103        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
1104
1105        assert_eq!(
1106            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
1107                .expect("decode committed allocation"),
1108            committed
1109        );
1110    }
1111
1112    #[test]
1113    fn component_directory_page_contracts_round_trip_through_candid() {
1114        let root = Principal::from_slice(&[6; 29]);
1115        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1116        let binding = ComponentBinding {
1117            authority: fleet_registry_authority(),
1118            component,
1119            component_spec: "projects".parse().expect("Component Spec ID"),
1120            spec_hash: [12; 32],
1121            role: CanisterRole::new("project_hub"),
1122            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
1123            fleet_subnet_root: root,
1124            canister_id: Principal::from_slice(&[16; 29]),
1125        };
1126        let directory = ComponentDirectoryHead {
1127            provenance: ComponentDirectoryProvenance {
1128                component: binding.clone(),
1129                source_fleet_subnet_root: root,
1130                component_registry_revision: 3,
1131                component_registry_content_hash: [18; 32],
1132                synchronized_at_ns: 19,
1133            },
1134            descendant_count: 1,
1135        };
1136        let request = ComponentDirectoryPageRequest {
1137            directory: directory.clone(),
1138            parent_canister_id: Some(binding.canister_id),
1139            role: Some(CanisterRole::new("project_instance")),
1140            status: Some(ComponentLifecycleStatus::Active),
1141            cursor: Some(ComponentDirectoryPageCursor(vec![20; 64])),
1142            limit: 50,
1143        };
1144        let response = ComponentDirectoryPageResponse {
1145            directory,
1146            entries: vec![ComponentDirectoryChildEntry {
1147                binding: ComponentChildBinding {
1148                    component: binding.clone(),
1149                    parent_canister_id: binding.canister_id,
1150                    role: CanisterRole::new("project_instance"),
1151                    canister_id: Principal::from_slice(&[21; 29]),
1152                },
1153                kind: ComponentChildKind::Instance,
1154                installed_artifact_hash: [22; 32],
1155                status: ComponentLifecycleStatus::Active,
1156            }],
1157            next_cursor: Some(ComponentDirectoryPageCursor(vec![23; 64])),
1158        };
1159        let request_bytes = candid::encode_one(&request).expect("encode Directory page request");
1160        let response_bytes = candid::encode_one(&response).expect("encode Directory page response");
1161
1162        assert_eq!(
1163            candid::decode_one::<ComponentDirectoryPageRequest>(&request_bytes)
1164                .expect("decode Directory page request"),
1165            request
1166        );
1167        assert_eq!(
1168            candid::decode_one::<ComponentDirectoryPageResponse>(&response_bytes)
1169                .expect("decode Directory page response"),
1170            response
1171        );
1172    }
1173
1174    fn fleet_registry_authority() -> FleetRegistryAuthority {
1175        FleetRegistryAuthority {
1176            binding: FleetCoordinatorBinding {
1177                fleet: crate::ids::FleetBinding {
1178                    fleet: FleetKey {
1179                        canonical_network_id: CanonicalNetworkId::ic_mainnet(),
1180                        fleet_id: FleetId::from_generated_bytes([1; 32]),
1181                    },
1182                    app: AppId::from("toko"),
1183                },
1184                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
1185                coordinator: Principal::from_slice(&[3; 29]),
1186            },
1187            epoch: 1,
1188        }
1189    }
1190
1191    #[test]
1192    fn component_creation_request_round_trips_through_candid() {
1193        let request = RootComponentCreationRequest {
1194            operation_id: [10; 32],
1195        };
1196        let bytes = candid::encode_one(request).expect("encode creation request");
1197
1198        assert_eq!(
1199            candid::decode_one::<RootComponentCreationRequest>(&bytes)
1200                .expect("decode creation request"),
1201            request
1202        );
1203    }
1204
1205    #[test]
1206    fn component_subtree_removal_contracts_round_trip_through_candid() {
1207        let component = ComponentInstanceId::from_generated_bytes([41; 32]);
1208        let registry = ComponentRegistryHead {
1209            component,
1210            revision: 7,
1211            content_hash: [42; 32],
1212        };
1213        let request = RootComponentSubtreeRemovalRequest {
1214            operation_id: [43; 32],
1215            component,
1216            target_canister_id: Principal::from_slice(&[44; 29]),
1217            expected_registry: registry.clone(),
1218        };
1219        let status_request = RootComponentSubtreeRemovalStatusRequest {
1220            operation_id: request.operation_id,
1221            component,
1222        };
1223        let advance_request = RootComponentSubtreeRemovalAdvanceRequest {
1224            operation_id: request.operation_id,
1225            component,
1226            expected_traversal_steps: 1,
1227        };
1228        let stop_request = RootComponentSubtreeRemovalStopPreparationRequest {
1229            operation_id: request.operation_id,
1230            component,
1231            expected_traversal_steps: 2,
1232            expected_leaf_canister_id: Principal::from_slice(&[46; 29]),
1233            expected_leaf_parent_canister_id: request.target_canister_id,
1234        };
1235        let stopped = RootComponentSubtreeRemovalStoppedReceipt {
1236            observed_module_hash: [49; 32],
1237            stop: RootComponentSubtreeRemovalStopIntent {
1238                controller: Principal::from_slice(&[48; 29]),
1239                leaf: RootComponentSubtreeRemovalNode {
1240                    canister_id: Principal::from_slice(&[46; 29]),
1241                    parent_canister_id: request.target_canister_id,
1242                    role: CanisterRole::new("project_ledger"),
1243                    kind: ComponentChildKind::Singleton,
1244                    installed_artifact_hash: [47; 32],
1245                    status: ComponentLifecycleStatus::Active,
1246                },
1247            },
1248        };
1249        let response = RootComponentSubtreeRemovalResponse {
1250            operation_id: request.operation_id,
1251            component,
1252            target_canister_id: request.target_canister_id,
1253            target_parent_canister_id: Principal::from_slice(&[45; 29]),
1254            target_role: CanisterRole::new("project_instance"),
1255            target_status: ComponentLifecycleStatus::Active,
1256            reserved_against_registry: registry,
1257            traversal_steps: 2,
1258            phase: RootComponentSubtreeRemovalPhase::Deleted(
1259                RootComponentSubtreeRemovalDeletedReceipt {
1260                    deletion: RootComponentSubtreeRemovalDeleteIntent { stopped },
1261                },
1262            ),
1263        };
1264
1265        let request_bytes = candid::encode_one(&request).expect("encode subtree removal request");
1266        let advance_bytes =
1267            candid::encode_one(advance_request).expect("encode subtree removal advance request");
1268        let stop_bytes =
1269            candid::encode_one(stop_request).expect("encode subtree removal stop request");
1270        let status_bytes =
1271            candid::encode_one(status_request).expect("encode subtree removal status request");
1272        let response_bytes =
1273            candid::encode_one(&response).expect("encode subtree removal response");
1274
1275        assert_eq!(
1276            candid::decode_one::<RootComponentSubtreeRemovalRequest>(&request_bytes)
1277                .expect("decode subtree removal request"),
1278            request
1279        );
1280        assert_eq!(
1281            candid::decode_one::<RootComponentSubtreeRemovalAdvanceRequest>(&advance_bytes)
1282                .expect("decode subtree removal advance request"),
1283            advance_request
1284        );
1285        assert_eq!(
1286            candid::decode_one::<RootComponentSubtreeRemovalStopPreparationRequest>(&stop_bytes)
1287                .expect("decode subtree removal stop request"),
1288            stop_request
1289        );
1290        assert_eq!(
1291            candid::decode_one::<RootComponentSubtreeRemovalStatusRequest>(&status_bytes)
1292                .expect("decode subtree removal status request"),
1293            status_request
1294        );
1295        assert_eq!(
1296            candid::decode_one::<RootComponentSubtreeRemovalResponse>(&response_bytes)
1297                .expect("decode subtree removal response"),
1298            response
1299        );
1300    }
1301
1302    #[test]
1303    fn component_subtree_removal_deletion_requests_round_trip_through_candid() {
1304        let prepare = RootComponentSubtreeRemovalDeletePreparationRequest {
1305            operation_id: [50; 32],
1306            component: ComponentInstanceId::from_generated_bytes([51; 32]),
1307            expected_traversal_steps: 3,
1308            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1309            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1310        };
1311        let request = RootComponentSubtreeRemovalDeleteRequest {
1312            operation_id: prepare.operation_id,
1313            component: prepare.component,
1314            expected_traversal_steps: prepare.expected_traversal_steps,
1315            expected_leaf_canister_id: prepare.expected_leaf_canister_id,
1316            expected_leaf_parent_canister_id: prepare.expected_leaf_parent_canister_id,
1317        };
1318        let prepare_bytes = candid::encode_one(prepare)
1319            .expect("encode subtree removal deletion preparation request");
1320        let request_bytes =
1321            candid::encode_one(request).expect("encode subtree removal deletion request");
1322
1323        assert_eq!(
1324            candid::decode_one::<RootComponentSubtreeRemovalDeletePreparationRequest>(
1325                &prepare_bytes
1326            )
1327            .expect("decode subtree removal deletion preparation request"),
1328            prepare
1329        );
1330        assert_eq!(
1331            candid::decode_one::<RootComponentSubtreeRemovalDeleteRequest>(&request_bytes)
1332                .expect("decode subtree removal deletion request"),
1333            request
1334        );
1335    }
1336
1337    #[test]
1338    fn component_subtree_removal_stop_request_round_trips_through_candid() {
1339        let request = RootComponentSubtreeRemovalStopRequest {
1340            operation_id: [50; 32],
1341            component: ComponentInstanceId::from_generated_bytes([51; 32]),
1342            expected_traversal_steps: 3,
1343            expected_leaf_canister_id: Principal::from_slice(&[52; 29]),
1344            expected_leaf_parent_canister_id: Principal::from_slice(&[53; 29]),
1345        };
1346        let bytes =
1347            candid::encode_one(request).expect("encode subtree removal stop execution request");
1348
1349        assert_eq!(
1350            candid::decode_one::<RootComponentSubtreeRemovalStopRequest>(&bytes)
1351                .expect("decode subtree removal stop execution request"),
1352            request
1353        );
1354    }
1355
1356    #[test]
1357    #[expect(
1358        clippy::too_many_lines,
1359        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
1360    )]
1361    fn component_child_lifecycle_contracts_round_trip_through_candid() {
1362        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
1363        let registry = ComponentRegistryHead {
1364            component,
1365            revision: 2,
1366            content_hash: [12; 32],
1367        };
1368        let request = RootComponentChildAllocationRequest {
1369            operation_id: [13; 32],
1370            component,
1371            expected_registry: registry.clone(),
1372            child_role: CanisterRole::new("project_instance"),
1373        };
1374        let status_request = RootComponentChildAllocationStatusRequest {
1375            operation_id: request.operation_id,
1376            component,
1377        };
1378        let creation_request = RootComponentChildCreationRequest {
1379            operation_id: request.operation_id,
1380            component,
1381        };
1382        let install_request = RootComponentChildInstallRequest {
1383            operation_id: request.operation_id,
1384            component,
1385        };
1386        let commit_request = RootComponentChildCommitRequest {
1387            operation_id: request.operation_id,
1388            component,
1389        };
1390        let directory_request = RootComponentChildDirectoryPreparationRequest {
1391            operation_id: request.operation_id,
1392            component,
1393        };
1394        let activation_request = RootComponentChildRuntimeActivationRequest {
1395            operation_id: request.operation_id,
1396            component,
1397        };
1398        let membership_request = RootComponentChildMembershipActivationRequest {
1399            operation_id: request.operation_id,
1400            component,
1401        };
1402        let root = Principal::from_slice(&[17; 29]);
1403        let parent = Principal::from_slice(&[14; 29]);
1404        let child = Principal::from_slice(&[18; 29]);
1405        let child_binding = ComponentChildBinding {
1406            component: ComponentBinding {
1407                authority: fleet_registry_authority(),
1408                component,
1409                component_spec: "projects".parse().expect("Component Spec"),
1410                spec_hash: [19; 32],
1411                role: CanisterRole::new("project_hub"),
1412                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
1413                fleet_subnet_root: root,
1414                canister_id: parent,
1415            },
1416            parent_canister_id: parent,
1417            role: request.child_role.clone(),
1418            canister_id: child,
1419        };
1420        let response = RootComponentChildAllocationResponse {
1421            operation_id: request.operation_id,
1422            component,
1423            parent_canister_id: parent,
1424            parent_role: CanisterRole::new("project_hub"),
1425            child_role: request.child_role.clone(),
1426            child_kind: ComponentChildKind::Instance,
1427            maximum_instances_per_parent: 10_000,
1428            maximum_descendants: 20_000,
1429            maximum_registry_bytes: 16_777_216,
1430            reserved_against_registry: registry,
1431            release_set: FleetSubnetRootReleaseSet {
1432                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
1433                    [15; 32],
1434                )),
1435                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
1436            },
1437            phase: RootComponentAllocationPhase::Verified,
1438            creation: Some(RootComponentCreationEvidence {
1439                wasm_store: Principal::from_slice(&[21; 29]),
1440                payload_hash: [22; 32],
1441                payload_size_bytes: 4_096,
1442                initial_cycles: Cycles::new(5_000_000_000_000),
1443                controller: root,
1444                canister: Some(child),
1445            }),
1446            installation: Some(RootComponentChildInstallEvidence {
1447                raw_module_hash: [23; 32],
1448                chunk_hashes: vec![vec![24; 32]],
1449                binding: child_binding.clone(),
1450            }),
1451        };
1452        let commit_response = RootComponentChildCommitResponse {
1453            allocation: response.clone(),
1454            registry: ComponentRegistryPartitionResponse {
1455                head: ComponentRegistryHead {
1456                    component,
1457                    revision: 3,
1458                    content_hash: [25; 32],
1459                },
1460                binding: child_binding.component.clone(),
1461                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1462                    caller: Principal::from_slice(&[26; 29]),
1463                },
1464                release_set: response.release_set,
1465                status: ComponentLifecycleStatus::Active,
1466                reserved_descendants: 0,
1467                committed_descendants: 1,
1468                encoded_bytes: 8_192,
1469            },
1470            directory: ComponentDirectoryHead {
1471                provenance: ComponentDirectoryProvenance {
1472                    component: child_binding.component.clone(),
1473                    source_fleet_subnet_root: root,
1474                    component_registry_revision: 3,
1475                    component_registry_content_hash: [25; 32],
1476                    synchronized_at_ns: 27,
1477                },
1478                descendant_count: 1,
1479            },
1480        };
1481        let runtime_authority = ComponentRuntimeDirectoryAuthority {
1482            fleet: FleetDirectorySnapshot {
1483                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1484                    registry: FleetRegistryVersion {
1485                        authority: fleet_registry_authority(),
1486                        revision: 4,
1487                        content_hash: [28; 32],
1488                    },
1489                    source_fleet_subnet_root: root,
1490                },
1491                fleet_subnet_roots: vec![
1492                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1493                        placement_subnet: commit_response.registry.binding.placement_subnet,
1494                        fleet_subnet_root: root,
1495                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1496                    },
1497                ],
1498            },
1499            component: commit_response.directory.clone(),
1500        };
1501        let activation = ComponentRuntimeActivationEvidence {
1502            directory_authority_hash: [29; 32],
1503            activated_at_ns: 30,
1504        };
1505        let directory_response = RootComponentChildDirectoryPreparationResponse {
1506            committed: commit_response.clone(),
1507            child: ComponentRuntimeStatusResponse {
1508                operation_id: request.operation_id,
1509                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1510                phase: ComponentRuntimePhase::DirectoryPrepared,
1511                authority: Some(runtime_authority.clone()),
1512                authority_hash: Some([31; 32]),
1513                activation: None,
1514            },
1515            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1516                operation_id: [32; 32],
1517                binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1518                covered_authority: runtime_authority.clone(),
1519                covered_authority_hash: [31; 32],
1520                activation,
1521            },
1522            parent: None,
1523        };
1524        let activation_response = RootComponentChildRuntimeActivationResponse {
1525            committed: commit_response.clone(),
1526            child: ComponentRuntimeStatusResponse {
1527                operation_id: request.operation_id,
1528                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1529                phase: ComponentRuntimePhase::Active,
1530                authority: Some(runtime_authority.clone()),
1531                authority_hash: Some([31; 32]),
1532                activation: Some(ComponentRuntimeActivationEvidence {
1533                    directory_authority_hash: [31; 32],
1534                    activated_at_ns: 33,
1535                }),
1536            },
1537        };
1538        let active_directory = ComponentDirectoryHead {
1539            provenance: ComponentDirectoryProvenance {
1540                component: child_binding.component.clone(),
1541                source_fleet_subnet_root: root,
1542                component_registry_revision: 4,
1543                component_registry_content_hash: [34; 32],
1544                synchronized_at_ns: 35,
1545            },
1546            descendant_count: 1,
1547        };
1548        let active_authority = ComponentRuntimeDirectoryAuthority {
1549            fleet: runtime_authority.fleet,
1550            component: active_directory.clone(),
1551        };
1552        let membership_response = RootComponentChildMembershipActivationResponse {
1553            committed: commit_response.clone(),
1554            registry: ComponentRegistryPartitionResponse {
1555                head: ComponentRegistryHead {
1556                    component,
1557                    revision: 4,
1558                    content_hash: [34; 32],
1559                },
1560                binding: child_binding.component.clone(),
1561                provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1562                release_set: commit_response.registry.release_set,
1563                status: ComponentLifecycleStatus::Active,
1564                reserved_descendants: 0,
1565                committed_descendants: 1,
1566                encoded_bytes: 8_256,
1567            },
1568            directory: active_directory,
1569            child: ComponentRuntimeStatusResponse {
1570                operation_id: request.operation_id,
1571                binding: ManagedCanisterBinding::ComponentChild(child_binding),
1572                phase: ComponentRuntimePhase::Active,
1573                authority: Some(active_authority),
1574                authority_hash: Some([36; 32]),
1575                activation: Some(ComponentRuntimeActivationEvidence {
1576                    directory_authority_hash: [31; 32],
1577                    activated_at_ns: 33,
1578                }),
1579            },
1580        };
1581
1582        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1583        let status_bytes =
1584            candid::encode_one(status_request).expect("encode child reservation status");
1585        let creation_bytes =
1586            candid::encode_one(creation_request).expect("encode child creation request");
1587        let install_bytes =
1588            candid::encode_one(install_request).expect("encode child install request");
1589        let response_bytes = candid::encode_one(&response).expect("encode child response");
1590        let commit_request_bytes =
1591            candid::encode_one(commit_request).expect("encode child commit request");
1592        let directory_request_bytes =
1593            candid::encode_one(directory_request).expect("encode child Directory request");
1594        let activation_request_bytes =
1595            candid::encode_one(activation_request).expect("encode child activation request");
1596        let membership_request_bytes =
1597            candid::encode_one(membership_request).expect("encode child membership request");
1598        let commit_response_bytes =
1599            candid::encode_one(&commit_response).expect("encode child commit response");
1600        let directory_response_bytes =
1601            candid::encode_one(&directory_response).expect("encode child Directory response");
1602        let activation_response_bytes =
1603            candid::encode_one(&activation_response).expect("encode child activation response");
1604        let membership_response_bytes =
1605            candid::encode_one(&membership_response).expect("encode child membership response");
1606
1607        assert_eq!(
1608            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1609                .expect("decode child reservation"),
1610            request
1611        );
1612        assert_eq!(
1613            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1614                .expect("decode child reservation status"),
1615            status_request
1616        );
1617        assert_eq!(
1618            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1619                .expect("decode child creation request"),
1620            creation_request
1621        );
1622        assert_eq!(
1623            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1624                .expect("decode child install request"),
1625            install_request
1626        );
1627        assert_eq!(
1628            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1629                .expect("decode child response"),
1630            response
1631        );
1632        assert_eq!(
1633            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1634                .expect("decode child commit request"),
1635            commit_request
1636        );
1637        assert_eq!(
1638            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1639                &directory_request_bytes
1640            )
1641            .expect("decode child Directory request"),
1642            directory_request
1643        );
1644        assert_eq!(
1645            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1646                .expect("decode child commit response"),
1647            commit_response
1648        );
1649        assert_eq!(
1650            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1651                &directory_response_bytes
1652            )
1653            .expect("decode child Directory response"),
1654            directory_response
1655        );
1656        assert_eq!(
1657            candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1658                &activation_request_bytes
1659            )
1660            .expect("decode child activation request"),
1661            activation_request
1662        );
1663        assert_eq!(
1664            candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1665                &activation_response_bytes
1666            )
1667            .expect("decode child activation response"),
1668            activation_response
1669        );
1670        assert_eq!(
1671            candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1672                &membership_request_bytes
1673            )
1674            .expect("decode child membership request"),
1675            membership_request
1676        );
1677        assert_eq!(
1678            candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1679                &membership_response_bytes
1680            )
1681            .expect("decode child membership response"),
1682            membership_response
1683        );
1684    }
1685
1686    #[test]
1687    fn component_install_request_round_trips_through_candid() {
1688        let request = RootComponentInstallRequest {
1689            operation_id: [10; 32],
1690        };
1691        let bytes = candid::encode_one(request).expect("encode install request");
1692
1693        assert_eq!(
1694            candid::decode_one::<RootComponentInstallRequest>(&bytes)
1695                .expect("decode install request"),
1696            request
1697        );
1698    }
1699
1700    #[test]
1701    fn component_commit_request_round_trips_through_candid() {
1702        let request = RootComponentCommitRequest {
1703            operation_id: [10; 32],
1704        };
1705        let bytes = candid::encode_one(request).expect("encode commit request");
1706
1707        assert_eq!(
1708            candid::decode_one::<RootComponentCommitRequest>(&bytes)
1709                .expect("decode commit request"),
1710            request
1711        );
1712    }
1713
1714    #[test]
1715    fn component_runtime_activation_requests_round_trip_through_candid() {
1716        let root_request = RootComponentRuntimeActivationRequest {
1717            operation_id: [22; 32],
1718        };
1719        let target_request = ComponentRuntimeActivationRequest {
1720            operation_id: root_request.operation_id,
1721            directory_authority_hash: [23; 32],
1722        };
1723        let membership_request = RootComponentMembershipActivationRequest {
1724            operation_id: root_request.operation_id,
1725        };
1726        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1727        let target_bytes =
1728            candid::encode_one(target_request).expect("encode target activation request");
1729        let membership_bytes =
1730            candid::encode_one(membership_request).expect("encode membership activation request");
1731
1732        assert_eq!(
1733            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1734                .expect("decode root activation request"),
1735            root_request
1736        );
1737        assert_eq!(
1738            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1739                .expect("decode target activation request"),
1740            target_request
1741        );
1742        assert_eq!(
1743            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1744                .expect("decode membership activation request"),
1745            membership_request
1746        );
1747    }
1748}