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/// RootComponentChildCreationRequest
123///
124/// Parent command continuing one already reserved direct-child operation.
125///
126
127#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
128pub struct RootComponentChildCreationRequest {
129    pub operation_id: [u8; 32],
130    pub component: ComponentInstanceId,
131}
132
133///
134/// RootComponentChildInstallRequest
135///
136/// Parent command installing and verifying one already created direct-child operation.
137///
138
139#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
140pub struct RootComponentChildInstallRequest {
141    pub operation_id: [u8; 32],
142    pub component: ComponentInstanceId,
143}
144
145///
146/// RootComponentChildCommitRequest
147///
148/// Parent command committing one already verified direct-child operation.
149///
150
151#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152pub struct RootComponentChildCommitRequest {
153    pub operation_id: [u8; 32],
154    pub component: ComponentInstanceId,
155}
156
157///
158/// RootComponentChildDirectoryPreparationRequest
159///
160/// Parent command distributing one committed child's Directory and converging its affected members.
161///
162
163#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
164pub struct RootComponentChildDirectoryPreparationRequest {
165    pub operation_id: [u8; 32],
166    pub component: ComponentInstanceId,
167}
168
169///
170/// RootComponentChildRuntimeActivationRequest
171///
172/// Parent command activating one Directory-prepared direct-child runtime.
173///
174
175#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
176pub struct RootComponentChildRuntimeActivationRequest {
177    pub operation_id: [u8; 32],
178    pub component: ComponentInstanceId,
179}
180
181///
182/// RootComponentChildMembershipActivationRequest
183///
184/// Parent command activating one runtime-active direct child's Registry membership.
185///
186
187#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
188pub struct RootComponentChildMembershipActivationRequest {
189    pub operation_id: [u8; 32],
190    pub component: ComponentInstanceId,
191}
192
193///
194/// RootComponentCreationRequest
195///
196/// Controller command continuing one already reserved top-level Component operation.
197///
198
199#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct RootComponentCreationRequest {
201    pub operation_id: [u8; 32],
202}
203
204///
205/// RootComponentInstallRequest
206///
207/// Controller command continuing one already created top-level Component operation.
208///
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
211pub struct RootComponentInstallRequest {
212    pub operation_id: [u8; 32],
213}
214
215///
216/// RootComponentCommitRequest
217///
218/// Controller command committing one already verified top-level Component operation.
219///
220
221#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
222pub struct RootComponentCommitRequest {
223    pub operation_id: [u8; 32],
224}
225
226///
227/// RootComponentDirectoryPreparationRequest
228///
229/// Controller command distributing exact Directories to one committed top-level Component.
230///
231
232#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
233pub struct RootComponentDirectoryPreparationRequest {
234    pub operation_id: [u8; 32],
235}
236
237///
238/// RootComponentRuntimeActivationRequest
239///
240/// Controller command activating one Directory-prepared top-level Component runtime.
241///
242
243#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
244pub struct RootComponentRuntimeActivationRequest {
245    pub operation_id: [u8; 32],
246}
247
248///
249/// RootComponentMembershipActivationRequest
250///
251/// Controller command activating one runtime-active Component's Registry membership.
252///
253
254#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
255pub struct RootComponentMembershipActivationRequest {
256    pub operation_id: [u8; 32],
257}
258
259///
260/// ComponentProvisioningOrigin
261///
262/// Authenticated causal authority retained with one top-level Component allocation.
263///
264
265#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
266pub enum ComponentProvisioningOrigin {
267    FleetAdministrator { caller: Principal },
268}
269
270///
271/// RootComponentAllocationPhase
272///
273/// Durable root-local progress of one top-level Component allocation operation.
274///
275
276#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
277pub enum RootComponentAllocationPhase {
278    Reserved,
279    CreationIntent,
280    Created,
281    InstallIntent,
282    Installed,
283    Verified,
284    Committed,
285}
286
287///
288/// ComponentLifecycleStatus
289///
290/// Root-owned runtime lifecycle state of one committed Component Registry member.
291///
292
293#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
294pub enum ComponentLifecycleStatus {
295    Prepared,
296    Active,
297    Draining,
298    Removed,
299}
300
301///
302/// ComponentRegistryHead
303///
304/// Exact independently versioned authority of one Component Registry partition.
305///
306
307#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
308pub struct ComponentRegistryHead {
309    pub component: ComponentInstanceId,
310    pub revision: u64,
311    pub content_hash: [u8; 32],
312}
313
314///
315/// ComponentRegistryPartitionRequest
316///
317/// Read-only lookup key for one committed Component Registry partition.
318///
319
320#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
321pub struct ComponentRegistryPartitionRequest {
322    pub component: ComponentInstanceId,
323}
324
325///
326/// ComponentRegistryPartitionResponse
327///
328/// Protected top-level row and independent head of one Component Registry partition.
329///
330
331#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
332pub struct ComponentRegistryPartitionResponse {
333    pub head: ComponentRegistryHead,
334    pub binding: ComponentBinding,
335    pub provisioning_origin: ComponentProvisioningOrigin,
336    pub release_set: FleetSubnetRootReleaseSet,
337    pub status: ComponentLifecycleStatus,
338    pub reserved_descendants: u32,
339    pub committed_descendants: u32,
340    pub encoded_bytes: u64,
341}
342
343///
344/// ComponentDirectoryProvenance
345///
346/// Exact Component Registry authority from which one Component Directory is derived.
347///
348
349#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
350pub struct ComponentDirectoryProvenance {
351    pub component: ComponentBinding,
352    pub source_fleet_subnet_root: Principal,
353    pub component_registry_revision: u64,
354    pub component_registry_content_hash: [u8; 32],
355    pub synchronized_at_ns: u64,
356}
357
358///
359/// ComponentDirectoryHead
360///
361/// Compact independently versioned discovery projection for one Component tree.
362///
363
364#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365pub struct ComponentDirectoryHead {
366    pub provenance: ComponentDirectoryProvenance,
367    pub descendant_count: u32,
368}
369
370///
371/// ComponentDirectoryHeadRequest
372///
373/// Read-only lookup key for one committed Component Directory head.
374///
375
376#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
377pub struct ComponentDirectoryHeadRequest {
378    pub component: ComponentInstanceId,
379}
380
381///
382/// ComponentRuntimeDirectoryAuthority
383///
384/// Exact Fleet and Component discovery authority retained by one managed Component-tree node.
385///
386
387#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
388pub struct ComponentRuntimeDirectoryAuthority {
389    pub fleet: FleetDirectorySnapshot,
390    pub component: ComponentDirectoryHead,
391}
392
393///
394/// ComponentRuntimeDirectoryPreparationRequest
395///
396/// Root-issued exact Directory preparation command for one managed Component-tree node.
397///
398
399#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
400pub struct ComponentRuntimeDirectoryPreparationRequest {
401    pub operation_id: [u8; 32],
402    pub authority: ComponentRuntimeDirectoryAuthority,
403}
404
405///
406/// ComponentRuntimeDirectorySynchronizationRequest
407///
408/// Root-issued replacement of one active managed Component node's current Directory authority.
409///
410
411#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
412pub struct ComponentRuntimeDirectorySynchronizationRequest {
413    pub operation_id: [u8; 32],
414    pub authority: ComponentRuntimeDirectoryAuthority,
415}
416
417///
418/// ComponentRuntimePhase
419///
420/// Target-local progress from installation through Component runtime activation.
421///
422
423#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
424pub enum ComponentRuntimePhase {
425    AwaitingDirectory,
426    DirectoryPrepared,
427    Active,
428}
429
430///
431/// ComponentRuntimeActivationEvidence
432///
433/// Exact retained Directory authority under which one Component runtime became Active.
434///
435
436#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
437pub struct ComponentRuntimeActivationEvidence {
438    pub directory_authority_hash: [u8; 32],
439    pub activated_at_ns: u64,
440}
441
442///
443/// ComponentRuntimeActivationRequest
444///
445/// Root-issued exact activation command for one Directory-prepared managed Component node.
446///
447
448#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
449pub struct ComponentRuntimeActivationRequest {
450    pub operation_id: [u8; 32],
451    pub directory_authority_hash: [u8; 32],
452}
453
454///
455/// ComponentRuntimeStatusResponse
456///
457/// Independently observable target-local binding and exact retained Directory authority.
458///
459
460#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
461pub struct ComponentRuntimeStatusResponse {
462    pub operation_id: [u8; 32],
463    pub binding: ManagedCanisterBinding,
464    pub phase: ComponentRuntimePhase,
465    pub authority: Option<ComponentRuntimeDirectoryAuthority>,
466    pub authority_hash: Option<[u8; 32]>,
467    pub activation: Option<ComponentRuntimeActivationEvidence>,
468}
469
470///
471/// ComponentRuntimeDirectoryConvergenceEvidence
472///
473/// Stable root evidence that one active member covered at least the required Directory authority.
474///
475
476#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
477pub struct ComponentRuntimeDirectoryConvergenceEvidence {
478    pub operation_id: [u8; 32],
479    pub binding: ManagedCanisterBinding,
480    pub covered_authority: ComponentRuntimeDirectoryAuthority,
481    pub covered_authority_hash: [u8; 32],
482    pub activation: ComponentRuntimeActivationEvidence,
483}
484
485///
486/// RootComponentCreationEvidence
487///
488/// Exact Store artifact and root-owned creation settings frozen before the paid effect.
489///
490
491#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
492pub struct RootComponentCreationEvidence {
493    pub wasm_store: Principal,
494    pub payload_hash: [u8; 32],
495    pub payload_size_bytes: u64,
496    pub initial_cycles: Cycles,
497    pub controller: Principal,
498    pub canister: Option<Principal>,
499}
500
501///
502/// RootComponentInstallEvidence
503///
504/// Exact raw artifact, chunk source and immutable target binding frozen before installation.
505///
506
507#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
508pub struct RootComponentInstallEvidence {
509    pub raw_module_hash: [u8; 32],
510    pub chunk_hashes: Vec<Vec<u8>>,
511    pub binding: ComponentBinding,
512}
513
514///
515/// RootComponentChildInstallEvidence
516///
517/// Exact child module and immutable retained binding frozen before installation.
518///
519
520#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
521pub struct RootComponentChildInstallEvidence {
522    pub raw_module_hash: [u8; 32],
523    pub chunk_hashes: Vec<Vec<u8>>,
524    pub binding: ComponentChildBinding,
525}
526
527///
528/// RootComponentAllocationResponse
529///
530/// Durable identity reservation returned identically for exact operation retry.
531///
532
533#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
534pub struct RootComponentAllocationResponse {
535    pub operation_id: [u8; 32],
536    pub allocation_sequence: u64,
537    pub component: ComponentInstanceId,
538    pub component_spec: ComponentSpecId,
539    pub spec_hash: [u8; 32],
540    pub role: CanisterRole,
541    pub provisioning_origin: ComponentProvisioningOrigin,
542    pub release_set: FleetSubnetRootReleaseSet,
543    pub phase: RootComponentAllocationPhase,
544    pub creation: Option<RootComponentCreationEvidence>,
545    pub installation: Option<RootComponentInstallEvidence>,
546}
547
548///
549/// RootComponentChildAllocationResponse
550///
551/// Durable direct-child lifecycle progress returned identically for exact parent retry.
552///
553
554#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
555pub struct RootComponentChildAllocationResponse {
556    pub operation_id: [u8; 32],
557    pub component: ComponentInstanceId,
558    pub parent_canister_id: Principal,
559    pub parent_role: CanisterRole,
560    pub child_role: CanisterRole,
561    pub child_kind: ComponentChildKind,
562    pub maximum_instances_per_parent: u32,
563    pub maximum_descendants: u32,
564    pub maximum_registry_bytes: u64,
565    pub reserved_against_registry: ComponentRegistryHead,
566    pub release_set: FleetSubnetRootReleaseSet,
567    pub phase: RootComponentAllocationPhase,
568    pub creation: Option<RootComponentCreationEvidence>,
569    pub installation: Option<RootComponentChildInstallEvidence>,
570}
571
572///
573/// RootComponentChildCommitResponse
574///
575/// Exact committed child operation, authoritative Component Registry and next Directory head.
576///
577
578#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
579pub struct RootComponentChildCommitResponse {
580    pub allocation: RootComponentChildAllocationResponse,
581    pub registry: ComponentRegistryPartitionResponse,
582    pub directory: ComponentDirectoryHead,
583}
584
585///
586/// RootComponentChildDirectoryPreparationResponse
587///
588/// Exact child preparation plus stable bounded active-member Directory coverage.
589///
590
591#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
592pub struct RootComponentChildDirectoryPreparationResponse {
593    pub committed: RootComponentChildCommitResponse,
594    pub child: ComponentRuntimeStatusResponse,
595    pub owning_component: ComponentRuntimeDirectoryConvergenceEvidence,
596    pub parent: Option<ComponentRuntimeDirectoryConvergenceEvidence>,
597}
598
599///
600/// RootComponentChildRuntimeActivationResponse
601///
602/// Exact child commitment plus independently observed Directory-bound runtime activation.
603///
604
605#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
606pub struct RootComponentChildRuntimeActivationResponse {
607    pub committed: RootComponentChildCommitResponse,
608    pub child: ComponentRuntimeStatusResponse,
609}
610
611///
612/// RootComponentChildMembershipActivationResponse
613///
614/// Original child commitment plus active Registry, Directory and target convergence evidence.
615///
616
617#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
618pub struct RootComponentChildMembershipActivationResponse {
619    pub committed: RootComponentChildCommitResponse,
620    pub registry: ComponentRegistryPartitionResponse,
621    pub directory: ComponentDirectoryHead,
622    pub child: ComponentRuntimeStatusResponse,
623}
624
625///
626/// RootComponentCommitResponse
627///
628/// Exact committed allocation, authoritative Registry row and derived Directory head.
629///
630
631#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
632pub struct RootComponentCommitResponse {
633    pub allocation: RootComponentAllocationResponse,
634    pub registry: ComponentRegistryPartitionResponse,
635    pub directory: ComponentDirectoryHead,
636}
637
638///
639/// RootComponentDirectoryPreparationResponse
640///
641/// Exact root authority plus independently observed target-local Directory preparation.
642///
643
644#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
645pub struct RootComponentDirectoryPreparationResponse {
646    pub committed: RootComponentCommitResponse,
647    pub target: ComponentRuntimeStatusResponse,
648}
649
650///
651/// RootComponentRuntimeActivationResponse
652///
653/// Exact root authority plus independently observed target-local runtime activation.
654///
655
656#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
657pub struct RootComponentRuntimeActivationResponse {
658    pub committed: RootComponentCommitResponse,
659    pub target: ComponentRuntimeStatusResponse,
660}
661
662///
663/// RootComponentMembershipActivationResponse
664///
665/// Exact active Registry authority plus independently observed current target Directory.
666///
667
668#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
669pub struct RootComponentMembershipActivationResponse {
670    pub allocation: RootComponentAllocationResponse,
671    pub registry: ComponentRegistryPartitionResponse,
672    pub directory: ComponentDirectoryHead,
673    pub target: ComponentRuntimeStatusResponse,
674}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679    use crate::{
680        dto::root_store::RootStoreBootstrapRequest,
681        ids::{
682            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
683            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
684        },
685    };
686
687    #[test]
688    fn component_registry_contracts_round_trip_through_candid() {
689        let request = RootComponentRegistryPreparationRequest {
690            store_bootstrap: RootStoreBootstrapRequest {
691                manifest_payload_size_bytes: 128,
692            },
693            expected_fleet_registry: FleetRegistryVersion {
694                authority: fleet_registry_authority(),
695                revision: 4,
696                content_hash: [5; 32],
697            },
698        };
699        let response = RootComponentRegistryStatusResponse {
700            fleet_subnet_root: Principal::from_slice(&[6; 29]),
701            prepared_against_registry: request.expected_fleet_registry.clone(),
702            release_set: FleetSubnetRootReleaseSet {
703                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
704                    [7; 32],
705                )),
706                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
707            },
708            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
709            next_allocation_sequence: 1,
710            reserved_component_instances: 0,
711            committed_component_instances: 0,
712            managed_descendants: 0,
713            known_created_component_canisters: 0,
714            encoded_bytes: 0,
715            initial_inventory: Some(RootComponentInitialInventoryStatus {
716                fleet_activation_operation_id: [10; 32],
717                component_count: 0,
718                inventory_hash: [11; 32],
719                sealed_at_ns: 12,
720                directories_converged: true,
721                root_runtime_activated: true,
722            }),
723        };
724        let allocation = RootComponentAllocationResponse {
725            operation_id: [10; 32],
726            allocation_sequence: 1,
727            component: ComponentInstanceId::from_generated_bytes([11; 32]),
728            component_spec: "projects".parse().expect("Component Spec ID"),
729            spec_hash: [12; 32],
730            role: CanisterRole::new("project_hub"),
731            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
732                caller: Principal::from_slice(&[13; 29]),
733            },
734            release_set: response.release_set,
735            phase: RootComponentAllocationPhase::Reserved,
736            creation: None,
737            installation: None,
738        };
739        let created = RootComponentAllocationResponse {
740            phase: RootComponentAllocationPhase::Created,
741            creation: Some(RootComponentCreationEvidence {
742                wasm_store: Principal::from_slice(&[14; 29]),
743                payload_hash: [15; 32],
744                payload_size_bytes: 4_096,
745                initial_cycles: Cycles::new(5_000_000_000_000),
746                controller: Principal::from_slice(&[6; 29]),
747                canister: Some(Principal::from_slice(&[16; 29])),
748            }),
749            installation: None,
750            ..allocation.clone()
751        };
752        let request_bytes = candid::encode_one(&request).expect("encode request");
753        let response_bytes = candid::encode_one(&response).expect("encode response");
754        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
755        let created_bytes = candid::encode_one(&created).expect("encode created allocation");
756
757        assert_eq!(
758            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
759                .expect("decode request"),
760            request
761        );
762        assert_eq!(
763            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
764                .expect("decode response"),
765            response
766        );
767        assert_eq!(
768            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
769                .expect("decode allocation"),
770            allocation
771        );
772        assert_eq!(
773            candid::decode_one::<RootComponentAllocationResponse>(&created_bytes)
774                .expect("decode created allocation"),
775            created
776        );
777    }
778
779    #[test]
780    fn component_commit_response_round_trips_through_candid() {
781        let root = Principal::from_slice(&[6; 29]);
782        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
783        let component_spec: ComponentSpecId = "projects".parse().expect("Component Spec ID");
784        let release_set = FleetSubnetRootReleaseSet {
785            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
786                [7; 32],
787            )),
788            manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
789        };
790        let provisioning_origin = ComponentProvisioningOrigin::FleetAdministrator {
791            caller: Principal::from_slice(&[13; 29]),
792        };
793        let binding = ComponentBinding {
794            authority: fleet_registry_authority(),
795            component,
796            component_spec: component_spec.clone(),
797            spec_hash: [12; 32],
798            role: CanisterRole::new("project_hub"),
799            placement_subnet: SubnetId::from_principal(Principal::from_slice(&[17; 29])),
800            fleet_subnet_root: root,
801            canister_id: Principal::from_slice(&[16; 29]),
802        };
803        let head = ComponentRegistryHead {
804            component,
805            revision: 1,
806            content_hash: [18; 32],
807        };
808        let committed = RootComponentCommitResponse {
809            allocation: RootComponentAllocationResponse {
810                operation_id: [10; 32],
811                allocation_sequence: 1,
812                component,
813                component_spec,
814                spec_hash: binding.spec_hash,
815                role: binding.role.clone(),
816                provisioning_origin: provisioning_origin.clone(),
817                release_set,
818                phase: RootComponentAllocationPhase::Committed,
819                creation: Some(RootComponentCreationEvidence {
820                    wasm_store: Principal::from_slice(&[14; 29]),
821                    payload_hash: [15; 32],
822                    payload_size_bytes: 4_096,
823                    initial_cycles: Cycles::new(5_000_000_000_000),
824                    controller: root,
825                    canister: Some(binding.canister_id),
826                }),
827                installation: Some(RootComponentInstallEvidence {
828                    raw_module_hash: [20; 32],
829                    chunk_hashes: vec![vec![21; 32]],
830                    binding: binding.clone(),
831                }),
832            },
833            registry: ComponentRegistryPartitionResponse {
834                head: head.clone(),
835                binding: binding.clone(),
836                provisioning_origin,
837                release_set,
838                status: ComponentLifecycleStatus::Prepared,
839                reserved_descendants: 0,
840                committed_descendants: 0,
841                encoded_bytes: 2_048,
842            },
843            directory: ComponentDirectoryHead {
844                provenance: ComponentDirectoryProvenance {
845                    component: binding,
846                    source_fleet_subnet_root: root,
847                    component_registry_revision: head.revision,
848                    component_registry_content_hash: head.content_hash,
849                    synchronized_at_ns: 19,
850                },
851                descendant_count: 0,
852            },
853        };
854        let committed_bytes = candid::encode_one(&committed).expect("encode committed allocation");
855
856        assert_eq!(
857            candid::decode_one::<RootComponentCommitResponse>(&committed_bytes)
858                .expect("decode committed allocation"),
859            committed
860        );
861    }
862
863    fn fleet_registry_authority() -> FleetRegistryAuthority {
864        FleetRegistryAuthority {
865            binding: FleetCoordinatorBinding {
866                fleet: crate::ids::FleetBinding {
867                    fleet: FleetKey {
868                        canonical_network_id: CanonicalNetworkId::public_ic(),
869                        fleet_id: FleetId::from_generated_bytes([1; 32]),
870                    },
871                    app: AppId::from("toko"),
872                },
873                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
874                coordinator: Principal::from_slice(&[3; 29]),
875            },
876            epoch: 1,
877        }
878    }
879
880    #[test]
881    fn component_creation_request_round_trips_through_candid() {
882        let request = RootComponentCreationRequest {
883            operation_id: [10; 32],
884        };
885        let bytes = candid::encode_one(request).expect("encode creation request");
886
887        assert_eq!(
888            candid::decode_one::<RootComponentCreationRequest>(&bytes)
889                .expect("decode creation request"),
890            request
891        );
892    }
893
894    #[test]
895    #[expect(
896        clippy::too_many_lines,
897        reason = "one round-trip test keeps the complete child lifecycle boundary coherent"
898    )]
899    fn component_child_lifecycle_contracts_round_trip_through_candid() {
900        let component = ComponentInstanceId::from_generated_bytes([11; 32]);
901        let registry = ComponentRegistryHead {
902            component,
903            revision: 2,
904            content_hash: [12; 32],
905        };
906        let request = RootComponentChildAllocationRequest {
907            operation_id: [13; 32],
908            component,
909            expected_registry: registry.clone(),
910            child_role: CanisterRole::new("project_instance"),
911        };
912        let status_request = RootComponentChildAllocationStatusRequest {
913            operation_id: request.operation_id,
914            component,
915        };
916        let creation_request = RootComponentChildCreationRequest {
917            operation_id: request.operation_id,
918            component,
919        };
920        let install_request = RootComponentChildInstallRequest {
921            operation_id: request.operation_id,
922            component,
923        };
924        let commit_request = RootComponentChildCommitRequest {
925            operation_id: request.operation_id,
926            component,
927        };
928        let directory_request = RootComponentChildDirectoryPreparationRequest {
929            operation_id: request.operation_id,
930            component,
931        };
932        let activation_request = RootComponentChildRuntimeActivationRequest {
933            operation_id: request.operation_id,
934            component,
935        };
936        let membership_request = RootComponentChildMembershipActivationRequest {
937            operation_id: request.operation_id,
938            component,
939        };
940        let root = Principal::from_slice(&[17; 29]);
941        let parent = Principal::from_slice(&[14; 29]);
942        let child = Principal::from_slice(&[18; 29]);
943        let child_binding = ComponentChildBinding {
944            component: ComponentBinding {
945                authority: fleet_registry_authority(),
946                component,
947                component_spec: "projects".parse().expect("Component Spec"),
948                spec_hash: [19; 32],
949                role: CanisterRole::new("project_hub"),
950                placement_subnet: SubnetId::from_principal(Principal::from_slice(&[20; 29])),
951                fleet_subnet_root: root,
952                canister_id: parent,
953            },
954            parent_canister_id: parent,
955            role: request.child_role.clone(),
956            canister_id: child,
957        };
958        let response = RootComponentChildAllocationResponse {
959            operation_id: request.operation_id,
960            component,
961            parent_canister_id: parent,
962            parent_role: CanisterRole::new("project_hub"),
963            child_role: request.child_role.clone(),
964            child_kind: ComponentChildKind::Instance,
965            maximum_instances_per_parent: 10_000,
966            maximum_descendants: 20_000,
967            maximum_registry_bytes: 16_777_216,
968            reserved_against_registry: registry,
969            release_set: FleetSubnetRootReleaseSet {
970                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
971                    [15; 32],
972                )),
973                manifest_digest: ReleaseSetDigest::from_bytes([16; 32]),
974            },
975            phase: RootComponentAllocationPhase::Verified,
976            creation: Some(RootComponentCreationEvidence {
977                wasm_store: Principal::from_slice(&[21; 29]),
978                payload_hash: [22; 32],
979                payload_size_bytes: 4_096,
980                initial_cycles: Cycles::new(5_000_000_000_000),
981                controller: root,
982                canister: Some(child),
983            }),
984            installation: Some(RootComponentChildInstallEvidence {
985                raw_module_hash: [23; 32],
986                chunk_hashes: vec![vec![24; 32]],
987                binding: child_binding.clone(),
988            }),
989        };
990        let commit_response = RootComponentChildCommitResponse {
991            allocation: response.clone(),
992            registry: ComponentRegistryPartitionResponse {
993                head: ComponentRegistryHead {
994                    component,
995                    revision: 3,
996                    content_hash: [25; 32],
997                },
998                binding: child_binding.component.clone(),
999                provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
1000                    caller: Principal::from_slice(&[26; 29]),
1001                },
1002                release_set: response.release_set,
1003                status: ComponentLifecycleStatus::Active,
1004                reserved_descendants: 0,
1005                committed_descendants: 1,
1006                encoded_bytes: 8_192,
1007            },
1008            directory: ComponentDirectoryHead {
1009                provenance: ComponentDirectoryProvenance {
1010                    component: child_binding.component.clone(),
1011                    source_fleet_subnet_root: root,
1012                    component_registry_revision: 3,
1013                    component_registry_content_hash: [25; 32],
1014                    synchronized_at_ns: 27,
1015                },
1016                descendant_count: 1,
1017            },
1018        };
1019        let runtime_authority = ComponentRuntimeDirectoryAuthority {
1020            fleet: FleetDirectorySnapshot {
1021                provenance: crate::dto::fleet_registry::FleetDirectoryProvenance {
1022                    registry: FleetRegistryVersion {
1023                        authority: fleet_registry_authority(),
1024                        revision: 4,
1025                        content_hash: [28; 32],
1026                    },
1027                    source_fleet_subnet_root: root,
1028                },
1029                fleet_subnet_roots: vec![
1030                    crate::dto::fleet_registry::FleetSubnetRootDirectoryEntry {
1031                        placement_subnet: commit_response.registry.binding.placement_subnet,
1032                        fleet_subnet_root: root,
1033                        status: crate::dto::fleet_registry::FleetSubnetRootStatus::Active,
1034                    },
1035                ],
1036            },
1037            component: commit_response.directory.clone(),
1038        };
1039        let activation = ComponentRuntimeActivationEvidence {
1040            directory_authority_hash: [29; 32],
1041            activated_at_ns: 30,
1042        };
1043        let directory_response = RootComponentChildDirectoryPreparationResponse {
1044            committed: commit_response.clone(),
1045            child: ComponentRuntimeStatusResponse {
1046                operation_id: request.operation_id,
1047                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1048                phase: ComponentRuntimePhase::DirectoryPrepared,
1049                authority: Some(runtime_authority.clone()),
1050                authority_hash: Some([31; 32]),
1051                activation: None,
1052            },
1053            owning_component: ComponentRuntimeDirectoryConvergenceEvidence {
1054                operation_id: [32; 32],
1055                binding: ManagedCanisterBinding::Component(child_binding.component.clone()),
1056                covered_authority: runtime_authority.clone(),
1057                covered_authority_hash: [31; 32],
1058                activation,
1059            },
1060            parent: None,
1061        };
1062        let activation_response = RootComponentChildRuntimeActivationResponse {
1063            committed: commit_response.clone(),
1064            child: ComponentRuntimeStatusResponse {
1065                operation_id: request.operation_id,
1066                binding: ManagedCanisterBinding::ComponentChild(child_binding.clone()),
1067                phase: ComponentRuntimePhase::Active,
1068                authority: Some(runtime_authority.clone()),
1069                authority_hash: Some([31; 32]),
1070                activation: Some(ComponentRuntimeActivationEvidence {
1071                    directory_authority_hash: [31; 32],
1072                    activated_at_ns: 33,
1073                }),
1074            },
1075        };
1076        let active_directory = ComponentDirectoryHead {
1077            provenance: ComponentDirectoryProvenance {
1078                component: child_binding.component.clone(),
1079                source_fleet_subnet_root: root,
1080                component_registry_revision: 4,
1081                component_registry_content_hash: [34; 32],
1082                synchronized_at_ns: 35,
1083            },
1084            descendant_count: 1,
1085        };
1086        let active_authority = ComponentRuntimeDirectoryAuthority {
1087            fleet: runtime_authority.fleet,
1088            component: active_directory.clone(),
1089        };
1090        let membership_response = RootComponentChildMembershipActivationResponse {
1091            committed: commit_response.clone(),
1092            registry: ComponentRegistryPartitionResponse {
1093                head: ComponentRegistryHead {
1094                    component,
1095                    revision: 4,
1096                    content_hash: [34; 32],
1097                },
1098                binding: child_binding.component.clone(),
1099                provisioning_origin: commit_response.registry.provisioning_origin.clone(),
1100                release_set: commit_response.registry.release_set,
1101                status: ComponentLifecycleStatus::Active,
1102                reserved_descendants: 0,
1103                committed_descendants: 1,
1104                encoded_bytes: 8_256,
1105            },
1106            directory: active_directory,
1107            child: ComponentRuntimeStatusResponse {
1108                operation_id: request.operation_id,
1109                binding: ManagedCanisterBinding::ComponentChild(child_binding),
1110                phase: ComponentRuntimePhase::Active,
1111                authority: Some(active_authority),
1112                authority_hash: Some([36; 32]),
1113                activation: Some(ComponentRuntimeActivationEvidence {
1114                    directory_authority_hash: [31; 32],
1115                    activated_at_ns: 33,
1116                }),
1117            },
1118        };
1119
1120        let request_bytes = candid::encode_one(&request).expect("encode child reservation");
1121        let status_bytes =
1122            candid::encode_one(status_request).expect("encode child reservation status");
1123        let creation_bytes =
1124            candid::encode_one(creation_request).expect("encode child creation request");
1125        let install_bytes =
1126            candid::encode_one(install_request).expect("encode child install request");
1127        let response_bytes = candid::encode_one(&response).expect("encode child response");
1128        let commit_request_bytes =
1129            candid::encode_one(commit_request).expect("encode child commit request");
1130        let directory_request_bytes =
1131            candid::encode_one(directory_request).expect("encode child Directory request");
1132        let activation_request_bytes =
1133            candid::encode_one(activation_request).expect("encode child activation request");
1134        let membership_request_bytes =
1135            candid::encode_one(membership_request).expect("encode child membership request");
1136        let commit_response_bytes =
1137            candid::encode_one(&commit_response).expect("encode child commit response");
1138        let directory_response_bytes =
1139            candid::encode_one(&directory_response).expect("encode child Directory response");
1140        let activation_response_bytes =
1141            candid::encode_one(&activation_response).expect("encode child activation response");
1142        let membership_response_bytes =
1143            candid::encode_one(&membership_response).expect("encode child membership response");
1144
1145        assert_eq!(
1146            candid::decode_one::<RootComponentChildAllocationRequest>(&request_bytes)
1147                .expect("decode child reservation"),
1148            request
1149        );
1150        assert_eq!(
1151            candid::decode_one::<RootComponentChildAllocationStatusRequest>(&status_bytes)
1152                .expect("decode child reservation status"),
1153            status_request
1154        );
1155        assert_eq!(
1156            candid::decode_one::<RootComponentChildCreationRequest>(&creation_bytes)
1157                .expect("decode child creation request"),
1158            creation_request
1159        );
1160        assert_eq!(
1161            candid::decode_one::<RootComponentChildInstallRequest>(&install_bytes)
1162                .expect("decode child install request"),
1163            install_request
1164        );
1165        assert_eq!(
1166            candid::decode_one::<RootComponentChildAllocationResponse>(&response_bytes)
1167                .expect("decode child response"),
1168            response
1169        );
1170        assert_eq!(
1171            candid::decode_one::<RootComponentChildCommitRequest>(&commit_request_bytes)
1172                .expect("decode child commit request"),
1173            commit_request
1174        );
1175        assert_eq!(
1176            candid::decode_one::<RootComponentChildDirectoryPreparationRequest>(
1177                &directory_request_bytes
1178            )
1179            .expect("decode child Directory request"),
1180            directory_request
1181        );
1182        assert_eq!(
1183            candid::decode_one::<RootComponentChildCommitResponse>(&commit_response_bytes)
1184                .expect("decode child commit response"),
1185            commit_response
1186        );
1187        assert_eq!(
1188            candid::decode_one::<RootComponentChildDirectoryPreparationResponse>(
1189                &directory_response_bytes
1190            )
1191            .expect("decode child Directory response"),
1192            directory_response
1193        );
1194        assert_eq!(
1195            candid::decode_one::<RootComponentChildRuntimeActivationRequest>(
1196                &activation_request_bytes
1197            )
1198            .expect("decode child activation request"),
1199            activation_request
1200        );
1201        assert_eq!(
1202            candid::decode_one::<RootComponentChildRuntimeActivationResponse>(
1203                &activation_response_bytes
1204            )
1205            .expect("decode child activation response"),
1206            activation_response
1207        );
1208        assert_eq!(
1209            candid::decode_one::<RootComponentChildMembershipActivationRequest>(
1210                &membership_request_bytes
1211            )
1212            .expect("decode child membership request"),
1213            membership_request
1214        );
1215        assert_eq!(
1216            candid::decode_one::<RootComponentChildMembershipActivationResponse>(
1217                &membership_response_bytes
1218            )
1219            .expect("decode child membership response"),
1220            membership_response
1221        );
1222    }
1223
1224    #[test]
1225    fn component_install_request_round_trips_through_candid() {
1226        let request = RootComponentInstallRequest {
1227            operation_id: [10; 32],
1228        };
1229        let bytes = candid::encode_one(request).expect("encode install request");
1230
1231        assert_eq!(
1232            candid::decode_one::<RootComponentInstallRequest>(&bytes)
1233                .expect("decode install request"),
1234            request
1235        );
1236    }
1237
1238    #[test]
1239    fn component_commit_request_round_trips_through_candid() {
1240        let request = RootComponentCommitRequest {
1241            operation_id: [10; 32],
1242        };
1243        let bytes = candid::encode_one(request).expect("encode commit request");
1244
1245        assert_eq!(
1246            candid::decode_one::<RootComponentCommitRequest>(&bytes)
1247                .expect("decode commit request"),
1248            request
1249        );
1250    }
1251
1252    #[test]
1253    fn component_runtime_activation_requests_round_trip_through_candid() {
1254        let root_request = RootComponentRuntimeActivationRequest {
1255            operation_id: [22; 32],
1256        };
1257        let target_request = ComponentRuntimeActivationRequest {
1258            operation_id: root_request.operation_id,
1259            directory_authority_hash: [23; 32],
1260        };
1261        let membership_request = RootComponentMembershipActivationRequest {
1262            operation_id: root_request.operation_id,
1263        };
1264        let root_bytes = candid::encode_one(root_request).expect("encode root activation request");
1265        let target_bytes =
1266            candid::encode_one(target_request).expect("encode target activation request");
1267        let membership_bytes =
1268            candid::encode_one(membership_request).expect("encode membership activation request");
1269
1270        assert_eq!(
1271            candid::decode_one::<RootComponentRuntimeActivationRequest>(&root_bytes)
1272                .expect("decode root activation request"),
1273            root_request
1274        );
1275        assert_eq!(
1276            candid::decode_one::<ComponentRuntimeActivationRequest>(&target_bytes)
1277                .expect("decode target activation request"),
1278            target_request
1279        );
1280        assert_eq!(
1281            candid::decode_one::<RootComponentMembershipActivationRequest>(&membership_bytes)
1282                .expect("decode membership activation request"),
1283            membership_request
1284        );
1285    }
1286}