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