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    dto::{fleet_registry::FleetRegistryVersion, root_store::RootStoreBootstrapRequest},
9    ids::{
10        CanisterRole, ComponentInstanceId, ComponentSpecId, ComponentTopologyDigest,
11        FleetSubnetRootReleaseSet,
12    },
13};
14use candid::{CandidType, Principal};
15use serde::{Deserialize, Serialize};
16
17///
18/// RootComponentRegistryPreparationRequest
19///
20/// Exact authority required before an empty root-local Component Registry may be prepared.
21///
22
23#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
24pub struct RootComponentRegistryPreparationRequest {
25    pub store_bootstrap: RootStoreBootstrapRequest,
26    pub expected_fleet_registry: FleetRegistryVersion,
27}
28
29///
30/// RootComponentRegistryStatusResponse
31///
32/// Compact durable Component Registry authority and current allocation counters.
33///
34
35#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
36pub struct RootComponentRegistryStatusResponse {
37    pub fleet_subnet_root: Principal,
38    pub prepared_against_registry: FleetRegistryVersion,
39    pub release_set: FleetSubnetRootReleaseSet,
40    pub component_topology_digest: ComponentTopologyDigest,
41    pub next_allocation_sequence: u64,
42    pub reserved_component_instances: u32,
43    pub committed_component_instances: u32,
44    pub managed_descendants: u32,
45    pub encoded_bytes: u64,
46}
47
48///
49/// RootComponentAllocationRequest
50///
51/// Controller command naming one idempotent top-level Component reservation intent.
52///
53
54#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
55pub struct RootComponentAllocationRequest {
56    pub operation_id: [u8; 32],
57    pub component_spec: ComponentSpecId,
58}
59
60///
61/// RootComponentAllocationStatusRequest
62///
63/// Read-only lookup key for one durable top-level Component allocation operation.
64///
65
66#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
67pub struct RootComponentAllocationStatusRequest {
68    pub operation_id: [u8; 32],
69}
70
71///
72/// ComponentProvisioningOrigin
73///
74/// Authenticated causal authority retained with one top-level Component allocation.
75///
76
77#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
78pub enum ComponentProvisioningOrigin {
79    FleetAdministrator { caller: Principal },
80}
81
82///
83/// RootComponentAllocationPhase
84///
85/// Durable root-local progress of one top-level Component allocation operation.
86///
87
88#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
89pub enum RootComponentAllocationPhase {
90    Reserved,
91}
92
93///
94/// RootComponentAllocationResponse
95///
96/// Durable identity reservation returned identically for exact operation retry.
97///
98
99#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
100pub struct RootComponentAllocationResponse {
101    pub operation_id: [u8; 32],
102    pub allocation_sequence: u64,
103    pub component: ComponentInstanceId,
104    pub component_spec: ComponentSpecId,
105    pub spec_hash: [u8; 32],
106    pub role: CanisterRole,
107    pub provisioning_origin: ComponentProvisioningOrigin,
108    pub release_set: FleetSubnetRootReleaseSet,
109    pub phase: RootComponentAllocationPhase,
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::{
116        dto::root_store::RootStoreBootstrapRequest,
117        ids::{
118            AppId, CanonicalNetworkId, FleetCoordinatorBinding, FleetId, FleetKey,
119            FleetRegistryAuthority, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
120        },
121    };
122
123    #[test]
124    fn component_registry_contracts_round_trip_through_candid() {
125        let authority = FleetRegistryAuthority {
126            binding: FleetCoordinatorBinding {
127                fleet: crate::ids::FleetBinding {
128                    fleet: FleetKey {
129                        canonical_network_id: CanonicalNetworkId::public_ic(),
130                        fleet_id: FleetId::from_generated_bytes([1; 32]),
131                    },
132                    app: AppId::from("toko"),
133                },
134                coordinator_subnet: SubnetId::from_principal(Principal::from_slice(&[2; 29])),
135                coordinator: Principal::from_slice(&[3; 29]),
136            },
137            epoch: 1,
138        };
139        let request = RootComponentRegistryPreparationRequest {
140            store_bootstrap: RootStoreBootstrapRequest {
141                manifest_payload_size_bytes: 128,
142            },
143            expected_fleet_registry: FleetRegistryVersion {
144                authority,
145                revision: 4,
146                content_hash: [5; 32],
147            },
148        };
149        let response = RootComponentRegistryStatusResponse {
150            fleet_subnet_root: Principal::from_slice(&[6; 29]),
151            prepared_against_registry: request.expected_fleet_registry.clone(),
152            release_set: FleetSubnetRootReleaseSet {
153                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
154                    [7; 32],
155                )),
156                manifest_digest: ReleaseSetDigest::from_bytes([8; 32]),
157            },
158            component_topology_digest: ComponentTopologyDigest::from_bytes([9; 32]),
159            next_allocation_sequence: 1,
160            reserved_component_instances: 0,
161            committed_component_instances: 0,
162            managed_descendants: 0,
163            encoded_bytes: 0,
164        };
165        let allocation = RootComponentAllocationResponse {
166            operation_id: [10; 32],
167            allocation_sequence: 1,
168            component: ComponentInstanceId::from_generated_bytes([11; 32]),
169            component_spec: "projects".parse().expect("Component Spec ID"),
170            spec_hash: [12; 32],
171            role: CanisterRole::new("project_hub"),
172            provisioning_origin: ComponentProvisioningOrigin::FleetAdministrator {
173                caller: Principal::from_slice(&[13; 29]),
174            },
175            release_set: response.release_set,
176            phase: RootComponentAllocationPhase::Reserved,
177        };
178
179        let request_bytes = candid::encode_one(&request).expect("encode request");
180        let response_bytes = candid::encode_one(&response).expect("encode response");
181        let allocation_bytes = candid::encode_one(&allocation).expect("encode allocation");
182
183        assert_eq!(
184            candid::decode_one::<RootComponentRegistryPreparationRequest>(&request_bytes)
185                .expect("decode request"),
186            request
187        );
188        assert_eq!(
189            candid::decode_one::<RootComponentRegistryStatusResponse>(&response_bytes)
190                .expect("decode response"),
191            response
192        );
193        assert_eq!(
194            candid::decode_one::<RootComponentAllocationResponse>(&allocation_bytes)
195                .expect("decode allocation"),
196            allocation
197        );
198    }
199}