ic_query/sns/report/model/reports/canisters.rs
1//! Module: sns::report::model::reports::canisters
2//!
3//! Responsibility: SNS Root canister inventory and health report DTOs.
4//! Does not own: Root transport, SNS lookup, report assembly, or rendering.
5//! Boundary: preserves native canister roles, status, module hashes, and typed gaps.
6
7use super::invocation::{SnsCanisterCallType, SnsCanisterMethod};
8use serde::Serialize;
9
10///
11/// SnsCanisterRole
12///
13/// Native role assigned to a canister by the SNS Root interface.
14///
15
16#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
17#[serde(rename_all = "snake_case")]
18pub enum SnsCanisterRole {
19 /// SNS Root canister.
20 Root,
21 /// SNS Governance canister.
22 Governance,
23 /// SNS ledger canister.
24 Ledger,
25 /// SNS decentralization swap canister.
26 Swap,
27 /// SNS ledger index canister.
28 Index,
29 /// SNS ledger archive canister.
30 Archive,
31 /// Dapp canister registered with SNS Root.
32 Dapp,
33 /// SNS extension canister registered with SNS Root.
34 Extension,
35}
36
37impl SnsCanisterRole {
38 /// Return the native lowercase role label used in text reports.
39 #[must_use]
40 pub const fn as_str(self) -> &'static str {
41 match self {
42 Self::Root => "root",
43 Self::Governance => "governance",
44 Self::Ledger => "ledger",
45 Self::Swap => "swap",
46 Self::Index => "index",
47 Self::Archive => "archive",
48 Self::Dapp => "dapp",
49 Self::Extension => "extension",
50 }
51 }
52}
53
54///
55/// SnsCanisterStatus
56///
57/// Native running state returned by SNS Root for one canister.
58///
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
61#[serde(rename_all = "snake_case")]
62pub enum SnsCanisterStatus {
63 /// The canister is running.
64 Running,
65 /// The canister is stopping.
66 Stopping,
67 /// The canister is stopped.
68 Stopped,
69}
70
71impl SnsCanisterStatus {
72 /// Return the native lowercase canister-status label.
73 #[must_use]
74 pub const fn as_str(self) -> &'static str {
75 match self {
76 Self::Running => "running",
77 Self::Stopping => "stopping",
78 Self::Stopped => "stopped",
79 }
80 }
81}
82
83///
84/// SnsCanisterCycleBalanceStatus
85///
86/// Factual availability and zero/nonzero classification of one reported cycle balance.
87///
88
89#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
90#[serde(rename_all = "snake_case")]
91pub enum SnsCanisterCycleBalanceStatus {
92 /// Root reported an exact zero cycle balance.
93 ReportedZero,
94 /// Root reported a positive cycle balance.
95 ReportedNonzero,
96 /// Root did not provide operational health for this inventory row.
97 Unavailable,
98}
99
100impl SnsCanisterCycleBalanceStatus {
101 /// Return the stable lowercase cycle-balance observation label.
102 #[must_use]
103 pub const fn as_str(self) -> &'static str {
104 match self {
105 Self::ReportedZero => "reported_zero",
106 Self::ReportedNonzero => "reported_nonzero",
107 Self::Unavailable => "unavailable",
108 }
109 }
110}
111
112///
113/// SnsCanisterGapKind
114///
115/// Typed reason that Root inventory and health evidence could not be joined.
116///
117
118#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
119#[serde(rename_all = "snake_case")]
120pub enum SnsCanisterGapKind {
121 /// The inventory response omitted a canister id for a native singleton role.
122 InventoryCanisterIdMissing,
123 /// The health response omitted the summary for an inventory canister.
124 SummaryMissing,
125 /// A health summary omitted its canister id.
126 SummaryCanisterIdMissing,
127 /// A singleton health summary identified a different canister than inventory.
128 SummaryCanisterIdMismatch,
129 /// A health summary identified a canister absent from inventory.
130 SummaryNotInInventory,
131 /// More than one health summary identified the same inventory canister and role.
132 DuplicateSummary,
133 /// A matched health summary omitted canister status.
134 StatusMissing,
135 /// The current Root health response does not expose this native role.
136 HealthUnsupported,
137}
138
139impl SnsCanisterGapKind {
140 /// Return the stable lowercase gap label used in text reports.
141 #[must_use]
142 pub const fn as_str(self) -> &'static str {
143 match self {
144 Self::InventoryCanisterIdMissing => "inventory_canister_id_missing",
145 Self::SummaryMissing => "summary_missing",
146 Self::SummaryCanisterIdMissing => "summary_canister_id_missing",
147 Self::SummaryCanisterIdMismatch => "summary_canister_id_mismatch",
148 Self::SummaryNotInInventory => "summary_not_in_inventory",
149 Self::DuplicateSummary => "duplicate_summary",
150 Self::StatusMissing => "status_missing",
151 Self::HealthUnsupported => "health_unsupported",
152 }
153 }
154}
155
156///
157/// SnsCanisterGap
158///
159/// One explicit inventory or health relation gap returned by SNS Root.
160///
161
162#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
163pub struct SnsCanisterGap {
164 /// Typed gap classification.
165 pub kind: SnsCanisterGapKind,
166 /// Native SNS canister role involved in the gap.
167 pub role: SnsCanisterRole,
168 /// Canister id supplied by the inventory response, when available.
169 pub inventory_canister_id: Option<String>,
170 /// Canister id supplied by the health summary, when available.
171 pub summary_canister_id: Option<String>,
172}
173
174///
175/// SnsCanisterHealthQueryGap
176///
177/// Failed Root health ingress retained after the inventory query succeeded.
178///
179
180#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
181pub struct SnsCanisterHealthQueryGap {
182 /// Native Root health method that failed.
183 pub method: SnsCanisterMethod,
184 /// Transport, encoding, or decoding failure retained for diagnostics.
185 pub reason: String,
186}
187
188///
189/// SnsCanisterRow
190///
191/// One canister in the authoritative SNS Root inventory.
192///
193
194#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
195pub struct SnsCanisterRow {
196 /// Native SNS canister role.
197 pub role: SnsCanisterRole,
198 /// Canonical canister principal text.
199 pub canister_id: String,
200 /// Native canister running state when Root returned health evidence.
201 pub status: Option<SnsCanisterStatus>,
202 /// Running Wasm module hash as lowercase hexadecimal text.
203 pub module_hash_hex: Option<String>,
204 /// Raw cycle balance as unsigned decimal text.
205 pub cycles: Option<String>,
206 /// Factual classification of the returned cycle balance or its absence.
207 pub cycle_balance_status: SnsCanisterCycleBalanceStatus,
208 /// Raw memory size in bytes as unsigned decimal text.
209 pub memory_size: Option<String>,
210 /// Raw idle cycles burned per day as unsigned decimal text.
211 pub idle_cycles_burned_per_day: Option<String>,
212 /// Canonical controller principals returned by Root.
213 pub controllers: Vec<String>,
214}
215
216///
217/// SnsCanisterReport
218///
219/// Joined SNS Root inventory and operational-health report.
220///
221
222#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
223pub struct SnsCanisterReport {
224 /// Report schema version.
225 pub schema_version: u32,
226 /// Requested IC network identity.
227 pub network: String,
228 /// Mainnet SNS-W canister used to resolve the SNS.
229 pub sns_wasm_canister_id: String,
230 /// Collection timestamp in UTC.
231 pub fetched_at: String,
232 /// IC API endpoint used for SNS-W and Root calls.
233 pub source_endpoint: String,
234 /// Collector identity recorded by the source request.
235 pub fetched_by: String,
236 /// SNS-W list id assigned to this deployed SNS.
237 pub id: usize,
238 /// SNS name resolved during discovery.
239 pub name: String,
240 /// Root canister queried for inventory and health.
241 pub root_canister_id: String,
242 /// Root query method used as the inventory authority.
243 pub inventory_method: SnsCanisterMethod,
244 /// Root ingress method used for operational health.
245 pub health_method: SnsCanisterMethod,
246 /// Transport kind used for the health call.
247 pub health_call_type: SnsCanisterCallType,
248 /// Value sent in the Root health request; always false for this read-only report.
249 pub health_update_canister_list: bool,
250 /// Whether all joined values represent one authoritative point-in-time snapshot.
251 pub point_in_time_guaranteed: bool,
252 /// Number of canisters in the Root inventory response.
253 pub canister_count: usize,
254 /// Number of inventory canisters with returned operational status.
255 pub health_status_count: usize,
256 /// Number of inventory canisters for which Root reported exactly zero cycles.
257 pub reported_zero_cycles_count: usize,
258 /// Number of inventory canisters for which cycle evidence was unavailable.
259 pub cycles_unavailable_count: usize,
260 /// Number of explicit inventory or health relation gaps.
261 pub gap_count: usize,
262 /// Root health ingress failure retained after successful inventory collection.
263 pub health_query_gap: Option<SnsCanisterHealthQueryGap>,
264 /// Canonically ordered inventory rows.
265 pub canisters: Vec<SnsCanisterRow>,
266 /// Canonically ordered typed relation gaps.
267 pub gaps: Vec<SnsCanisterGap>,
268}