Skip to main content

canic_core/dto/
fleet_activation.rs

1//! Module: dto::fleet_activation
2//!
3//! Responsibility: carry Fleet activation identity and evidence across host/runtime boundaries.
4//! Does not own: validation, persistence, hashing, activation policy, or recovery transitions.
5//! Boundary: authoritative owners validate these passive shapes before storing or acting on them.
6
7use crate::ids::{FleetBinding, FleetKey, ReleaseBuildId};
8use candid::{CandidType, Principal};
9use serde::Deserialize;
10
11/// Maximum managed Canisters represented by one Fleet activation.
12pub const MAX_FLEET_ACTIVATION_CANISTERS: usize = 4_096;
13/// Maximum issuer entries represented by one Fleet credential manifest.
14pub const MAX_FLEET_CREDENTIAL_MANIFEST_ENTRIES: usize = 64;
15/// Maximum canonical host activation-record size.
16pub const MAX_FLEET_ACTIVATION_HOST_RECORD_BYTES: usize = 2_097_152;
17
18///
19/// CurrentRootInstallIdentity
20///
21
22/// Exact current-only identity accepted by a fresh or reinstalled Fleet root.
23#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
24pub struct CurrentRootInstallIdentity {
25    pub fleet: FleetBinding,
26    pub install_id: [u8; 32],
27    pub release_build_id: ReleaseBuildId,
28    pub expected_module_hash: Option<[u8; 32]>,
29}
30
31///
32/// FleetCascadeManifestEntry
33///
34
35#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
36pub struct FleetCascadeManifestEntry {
37    pub principal: Principal,
38    pub state_snapshot_hash: [u8; 32],
39    pub topology_snapshot_hash: [u8; 32],
40}
41
42///
43/// FleetCascadeActivationEvidence
44///
45
46#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
47pub enum FleetCascadeActivationEvidence {
48    Source {
49        cascade_manifest_hash: [u8; 32],
50    },
51    Applied {
52        state_snapshot_hash: [u8; 32],
53        topology_snapshot_hash: [u8; 32],
54    },
55}
56
57///
58/// FleetCredentialGenerationRef
59///
60
61#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
62pub struct FleetCredentialGenerationRef {
63    pub generation: u64,
64    pub manifest_hash: [u8; 32],
65}
66
67///
68/// FleetCredentialManifestEntry
69///
70
71#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
72pub struct FleetCredentialManifestEntry {
73    pub root_issuer: Principal,
74    pub subject_canister: Principal,
75    pub not_before_ns: u64,
76    pub expires_at_ns: u64,
77    pub key_identity_hash: [u8; 32],
78    pub cert_hash: [u8; 32],
79    pub proof_hash: [u8; 32],
80    pub bundle_hash: [u8; 32],
81}
82
83///
84/// FleetCredentialManifest
85///
86
87#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
88pub struct FleetCredentialManifest {
89    pub fleet: FleetKey,
90    pub activation_id: [u8; 32],
91    pub generation: u64,
92    pub root_policy_set_hash: [u8; 32],
93    pub renewal_template_set_hash: [u8; 32],
94    pub entries: Vec<FleetCredentialManifestEntry>,
95}
96
97///
98/// FleetActivationIdentity
99///
100
101#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
102pub struct FleetActivationIdentity {
103    pub fleet: FleetBinding,
104    pub operation_id: [u8; 32],
105    pub release_build_id: ReleaseBuildId,
106}
107
108///
109/// FleetActivationPhase
110///
111
112#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
113pub enum FleetActivationPhase {
114    Prepared,
115    Active,
116}
117
118///
119/// FleetActivationStatusResponse
120///
121
122#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
123pub struct FleetActivationStatusResponse {
124    pub phase: FleetActivationPhase,
125    pub identity: FleetActivationIdentity,
126    pub cascade: Option<FleetCascadeActivationEvidence>,
127    pub cascade_manifest: Option<Vec<FleetCascadeManifestEntry>>,
128    pub credential: Option<FleetCredentialGenerationRef>,
129    pub credential_manifest: Option<FleetCredentialManifest>,
130    pub activated_at_ns: Option<u64>,
131}
132
133#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
134pub struct FleetActivationResumeRequest {
135    pub operation_id: [u8; 32],
136    pub credential: FleetCredentialGenerationRef,
137}
138
139#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
140pub struct FleetCredentialGenerationRequest {
141    pub operation_id: [u8; 32],
142    pub credential: FleetCredentialGenerationRef,
143}
144
145#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
146pub struct FleetActivationRequest {
147    pub operation_id: [u8; 32],
148    pub credential: FleetCredentialGenerationRef,
149    pub activation_evidence_hash: [u8; 32],
150}
151
152///
153/// FleetHostCanisterActivationEvidence
154///
155
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct FleetHostCanisterActivationEvidence {
158    pub principal: Principal,
159    pub activation_evidence_hash: Option<[u8; 32]>,
160}
161
162///
163/// FleetActivationHostRecord
164///
165
166#[derive(Clone, Debug, Eq, PartialEq)]
167pub struct FleetActivationHostRecord {
168    pub identity: FleetActivationIdentity,
169    pub cascade_manifest: Option<Vec<FleetCascadeManifestEntry>>,
170    pub credential: Option<FleetCredentialGenerationRef>,
171    pub credential_manifest: Option<FleetCredentialManifest>,
172    pub canisters: Vec<FleetHostCanisterActivationEvidence>,
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::ids::{AppId, CanonicalNetworkId, FleetId, FleetKey, ReleaseBuildNonce};
179
180    fn fleet_binding() -> FleetBinding {
181        FleetBinding {
182            fleet: FleetKey {
183                network: CanonicalNetworkId::public_ic(),
184                fleet_id: FleetId::from_generated_bytes([7; 32]),
185            },
186            app: AppId::from("toko"),
187        }
188    }
189
190    #[test]
191    fn current_root_install_identity_roundtrips_with_text_ids() {
192        let input = CurrentRootInstallIdentity {
193            fleet: fleet_binding(),
194            install_id: [8; 32],
195            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
196                [9; 32],
197            )),
198            expected_module_hash: Some([10; 32]),
199        };
200
201        let bytes = candid::encode_one(&input).expect("encode current root install identity");
202        let decoded: CurrentRootInstallIdentity =
203            candid::decode_one(&bytes).expect("decode current root install identity");
204
205        assert_eq!(decoded, input);
206    }
207
208    #[test]
209    fn fleet_activation_status_roundtrips_as_named_candid_shapes() {
210        let input = FleetActivationStatusResponse {
211            phase: FleetActivationPhase::Prepared,
212            identity: FleetActivationIdentity {
213                fleet: fleet_binding(),
214                operation_id: [11; 32],
215                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
216                    [12; 32],
217                )),
218            },
219            cascade: Some(FleetCascadeActivationEvidence::Source {
220                cascade_manifest_hash: [13; 32],
221            }),
222            cascade_manifest: Some(vec![FleetCascadeManifestEntry {
223                principal: Principal::from_slice(&[14; 29]),
224                state_snapshot_hash: [15; 32],
225                topology_snapshot_hash: [16; 32],
226            }]),
227            credential: None,
228            credential_manifest: None,
229            activated_at_ns: None,
230        };
231
232        let bytes = candid::encode_one(&input).expect("encode activation status");
233        let decoded: FleetActivationStatusResponse =
234            candid::decode_one(&bytes).expect("decode activation status");
235
236        assert_eq!(decoded, input);
237    }
238}