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