Skip to main content

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}