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///
127/// FleetHostCanisterActivationEvidence
128///
129
130#[derive(Clone, Debug, Eq, PartialEq)]
131pub struct FleetHostCanisterActivationEvidence {
132    pub principal: Principal,
133    pub activation_evidence_hash: Option<[u8; 32]>,
134}
135
136///
137/// FleetActivationHostRecord
138///
139
140#[derive(Clone, Debug, Eq, PartialEq)]
141pub struct FleetActivationHostRecord {
142    pub identity: FleetActivationIdentity,
143    pub cascade_manifest: Option<Vec<FleetCascadeManifestEntry>>,
144    pub credential: Option<FleetCredentialGenerationRef>,
145    pub credential_manifest: Option<FleetCredentialManifest>,
146    pub canisters: Vec<FleetHostCanisterActivationEvidence>,
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::ids::{AppId, CanonicalNetworkId, FleetId, FleetKey, ReleaseBuildNonce};
153
154    fn fleet_binding() -> FleetBinding {
155        FleetBinding {
156            fleet: FleetKey {
157                network: CanonicalNetworkId::public_ic(),
158                fleet_id: FleetId::from_generated_bytes([7; 32]),
159            },
160            app: AppId::from("toko"),
161        }
162    }
163
164    #[test]
165    fn current_root_install_identity_roundtrips_with_text_ids() {
166        let input = CurrentRootInstallIdentity {
167            fleet: fleet_binding(),
168            install_id: [8; 32],
169            release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
170                [9; 32],
171            )),
172            expected_module_hash: Some([10; 32]),
173        };
174
175        let bytes = candid::encode_one(&input).expect("encode current root install identity");
176        let decoded: CurrentRootInstallIdentity =
177            candid::decode_one(&bytes).expect("decode current root install identity");
178
179        assert_eq!(decoded, input);
180    }
181
182    #[test]
183    fn fleet_activation_status_roundtrips_as_named_candid_shapes() {
184        let input = FleetActivationStatusResponse {
185            phase: FleetActivationPhase::Prepared,
186            identity: FleetActivationIdentity {
187                fleet: fleet_binding(),
188                operation_id: [11; 32],
189                release_build_id: ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes(
190                    [12; 32],
191                )),
192            },
193            cascade: Some(FleetCascadeActivationEvidence::Source {
194                cascade_manifest_hash: [13; 32],
195            }),
196            cascade_manifest: Some(vec![FleetCascadeManifestEntry {
197                principal: Principal::from_slice(&[14; 29]),
198                state_snapshot_hash: [15; 32],
199                topology_snapshot_hash: [16; 32],
200            }]),
201            credential: None,
202            credential_manifest: None,
203            activated_at_ns: None,
204        };
205
206        let bytes = candid::encode_one(&input).expect("encode activation status");
207        let decoded: FleetActivationStatusResponse =
208            candid::decode_one(&bytes).expect("decode activation status");
209
210        assert_eq!(decoded, input);
211    }
212}