Skip to main content

canic_core/dto/
component_registry.rs

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