Skip to main content

canic_core/dto/
component_registry.rs

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