ic-query 0.27.5

Internet Computer query library for NNS, SNS, ICRC, system canisters, and public network metadata
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
//! Module: cache::model
//!
//! Responsibility: define shared cache-validation and local inventory report contracts.
//! Does not own: filesystem traversal, cache-family validation, or CLI output.
//! Boundary: names family validation outcomes and exposes generic header, age,
//! recovery-policy, and lock evidence without performing family-specific validation.

use serde::Serialize;
use std::{fmt, path::PathBuf};

/// Current serialized schema version for cache-status reports.
pub const CACHE_STATUS_REPORT_SCHEMA_VERSION: u32 = 1;

///
/// CacheValidationStatus
///
/// Semantic validation result for an existing family-specific cache.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheValidationStatus {
    /// The cache passed its family-specific schema, identity, and completeness checks.
    #[serde(rename = "ok")]
    Valid,
    /// The cache exists but failed family-specific validation.
    Invalid,
}

impl CacheValidationStatus {
    /// Return the stable serialized status label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Valid => "ok",
            Self::Invalid => "invalid",
        }
    }
}

impl fmt::Display for CacheValidationStatus {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

///
/// CacheRefreshAttemptStatus
///
/// Lifecycle state for a complete-cache refresh attempt.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheRefreshAttemptStatus {
    /// A refresh started or published intermediate collection progress.
    Running,
    /// A refresh exhausted its source and published a complete cache.
    Complete,
    /// A refresh terminated without replacing the complete cache.
    Failed,
}

impl CacheRefreshAttemptStatus {
    /// Return the stable serialized lifecycle label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Running => "running",
            Self::Complete => "complete",
            Self::Failed => "failed",
        }
    }

    #[cfg(any(feature = "host", test))]
    pub(crate) fn from_label(label: &str) -> Option<Self> {
        match label {
            "running" => Some(Self::Running),
            "complete" => Some(Self::Complete),
            "failed" => Some(Self::Failed),
            _ => None,
        }
    }
}

impl fmt::Display for CacheRefreshAttemptStatus {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(self.as_str())
    }
}

///
/// CacheHeaderStatus
///
/// Generic header-integrity classification for one complete cache file.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheHeaderStatus {
    /// The generic cache header is readable.
    Readable,
    /// The generic cache header cannot be read or parsed.
    Invalid,
}

impl CacheHeaderStatus {
    /// Return the stable serialized status label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Readable => "readable",
            Self::Invalid => "invalid",
        }
    }
}

///
/// CacheAgeStatus
///
/// Caller-relative age classification for one complete cache file.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheAgeStatus {
    /// The cache is within its registered family stale threshold.
    Fresh,
    /// The cache is older than its registered family stale threshold.
    Stale,
    /// The cache has a readable age but no registered family stale threshold.
    Unmanaged,
    /// The cache age cannot be calculated from its generic timestamp evidence.
    Unknown,
}

impl CacheAgeStatus {
    /// Return the stable serialized status label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Fresh => "fresh",
            Self::Stale => "stale",
            Self::Unmanaged => "unmanaged",
            Self::Unknown => "unknown",
        }
    }
}

///
/// CacheRecoveryPolicy
///
/// Owner policy for replacing recoverable invalid content at a canonical cache path.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheRecoveryPolicy {
    /// An ordinary owner read-through replaces recoverable invalid content.
    Automatic,
    /// Recovery requires an explicitly selected refresh operation.
    Explicit,
    /// Ordinary read-through creates a missing cache but does not replace invalid content.
    MissingOnly,
    /// The path does not identify a current canonical cache owner.
    Unknown,
}

impl CacheRecoveryPolicy {
    /// Return the stable serialized policy label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Automatic => "automatic",
            Self::Explicit => "explicit",
            Self::MissingOnly => "missing_only",
            Self::Unknown => "unknown",
        }
    }
}

///
/// CacheRefreshLockStatus
///
/// Generic age or validity classification for one refresh lock.
///

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CacheRefreshLockStatus {
    /// The lock is within the stale threshold recorded by its owner.
    Active,
    /// The lock is older than the stale threshold recorded by its owner.
    Stale,
    /// The lock is unreadable, malformed, or future-dated.
    Invalid,
}

impl CacheRefreshLockStatus {
    /// Return the stable serialized status label.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Active => "active",
            Self::Stale => "stale",
            Self::Invalid => "invalid",
        }
    }
}

///
/// CacheStatusRequest
///
/// Local cache-root inspection request with a caller-supplied observation time.
///

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CacheStatusRequest {
    /// User-level cache root to inspect.
    pub cache_root: PathBuf,
    /// Observation time used to calculate cache ages.
    pub now_unix_secs: u64,
}

impl CacheStatusRequest {
    /// Construct a local cache-status request.
    #[must_use]
    pub fn new(cache_root: impl Into<PathBuf>, now_unix_secs: u64) -> Self {
        Self {
            cache_root: cache_root.into(),
            now_unix_secs,
        }
    }
}

///
/// CacheStatusReport
///
/// Bounded local inventory of known complete caches and refresh locks.
///

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CacheStatusReport {
    /// Cache-status report schema version.
    pub schema_version: u32,
    /// Inspected user-level cache root.
    pub cache_root: String,
    /// UTC timestamp at which local inspection was requested.
    pub inspected_at: String,
    /// Whether the cache root existed.
    pub cache_root_found: bool,
    /// Maximum number of cache and refresh-lock files inspected in one report.
    pub scan_limit: usize,
    /// Whether additional cache or refresh-lock candidates existed beyond the scan limit.
    pub truncated: bool,
    /// Whether the generic inventory performed family-specific semantic validation.
    pub family_validation_performed: bool,
    /// Number of cache rows returned.
    pub cache_count: usize,
    /// Number of caches with readable generic headers.
    pub readable_header_count: usize,
    /// Number of caches with unreadable or malformed generic headers.
    pub invalid_header_count: usize,
    /// Number of caches fresh under an explicit family policy.
    pub fresh_count: usize,
    /// Number of caches stale under an explicit family policy.
    pub stale_count: usize,
    /// Number of readable caches whose family has no registered age policy.
    pub unmanaged_age_count: usize,
    /// Number of caches whose age cannot be calculated from generic evidence.
    pub unknown_age_count: usize,
    /// Sum of filesystem sizes for returned cache files.
    pub total_size_bytes: u64,
    /// Canonically path-ordered cache rows.
    pub caches: Vec<CacheStatusRow>,
    /// Number of refresh-lock rows returned.
    pub refresh_lock_count: usize,
    /// Number of locks still active under their recorded stale policy.
    pub active_refresh_lock_count: usize,
    /// Number of locks older than their recorded stale policy.
    pub stale_refresh_lock_count: usize,
    /// Number of unreadable, malformed, or future-dated locks.
    pub invalid_refresh_lock_count: usize,
    /// Sum of filesystem sizes for returned refresh-lock files.
    pub refresh_lock_size_bytes: u64,
    /// Canonically path-ordered refresh-lock rows.
    pub refresh_locks: Vec<CacheRefreshLockStatusRow>,
}

///
/// CacheStatusRow
///
/// Generic local metadata and caller-relative age for one complete cache file.
///

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CacheStatusRow {
    /// Stable component label inferred from cache identity or canonical path.
    pub component: String,
    /// Absolute cache-file path.
    pub cache_path: String,
    /// Cache-root-relative path.
    pub relative_path: String,
    /// Generic cache-header integrity without family-specific semantic validation.
    pub header_status: CacheHeaderStatus,
    /// Caller-relative age classification kept separate from header integrity.
    pub age_status: CacheAgeStatus,
    /// Owner policy for recovering invalid content at this canonical path.
    pub recovery_policy: CacheRecoveryPolicy,
    /// Serialized cache schema version when readable.
    pub schema_version: Option<u32>,
    /// Serialized network identity when present.
    pub network: Option<String>,
    /// Cache collection timestamp when readable.
    pub fetched_at: Option<String>,
    /// Caller-relative age when the timestamp is valid and not in the future.
    pub age_seconds: Option<u64>,
    /// Family age threshold when one is explicitly defined.
    pub stale_after_seconds: Option<u64>,
    /// Filesystem size of this cache file.
    pub size_bytes: u64,
    /// Generic header or timestamp inspection error; family-specific validation is separate.
    pub inspection_error: Option<String>,
}

///
/// CacheRefreshLockStatusRow
///
/// Local identity, ownership, age, and stale policy for one refresh lock.
///

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CacheRefreshLockStatusRow {
    /// Stable component label inferred from the recorded cache target.
    pub component: String,
    /// Absolute refresh-lock path.
    pub refresh_lock_path: String,
    /// Cache-root-relative refresh-lock path.
    pub relative_path: String,
    /// Generic lock age or validity classification.
    pub status: CacheRefreshLockStatus,
    /// Serialized refresh-lock schema version when readable.
    pub schema_version: Option<u32>,
    /// Serialized network identity when readable.
    pub network: Option<String>,
    /// Operating-system process id recorded by the lock owner when readable.
    pub pid: Option<u32>,
    /// Raw Unix-millisecond acquisition time when readable.
    pub started_at_unix_ms: Option<u64>,
    /// UTC acquisition timestamp when readable.
    pub started_at: Option<String>,
    /// Caller-relative lock age when the timestamp is not in the future.
    pub age_seconds: Option<u64>,
    /// Stale threshold recorded by the lock owner.
    pub stale_after_seconds: Option<u64>,
    /// Cache target recorded by the lock owner when readable.
    pub target_path: Option<String>,
    /// Filesystem size of this refresh-lock file.
    pub size_bytes: u64,
    /// Lock parse, shape, or timestamp error.
    pub error: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn typed_statuses_keep_stable_json_labels() {
        for (status, expected) in [
            (CacheValidationStatus::Valid, "ok"),
            (CacheValidationStatus::Invalid, "invalid"),
        ] {
            assert_eq!(status.as_str(), expected);
            assert_eq!(status.to_string(), expected);
            assert_eq!(
                serde_json::to_value(status).expect("serialize cache validation status"),
                serde_json::json!(expected)
            );
        }
        for (status, expected) in [
            (CacheRefreshAttemptStatus::Running, "running"),
            (CacheRefreshAttemptStatus::Complete, "complete"),
            (CacheRefreshAttemptStatus::Failed, "failed"),
        ] {
            assert_eq!(status.as_str(), expected);
            assert_eq!(status.to_string(), expected);
            assert_eq!(
                CacheRefreshAttemptStatus::from_label(expected),
                Some(status)
            );
            assert_eq!(
                serde_json::to_value(status).expect("serialize refresh-attempt status"),
                serde_json::json!(expected)
            );
        }
        assert_eq!(CacheRefreshAttemptStatus::from_label("unknown"), None);
        for (status, expected) in [
            (CacheHeaderStatus::Readable, "readable"),
            (CacheHeaderStatus::Invalid, "invalid"),
        ] {
            assert_eq!(status.as_str(), expected);
            assert_eq!(
                serde_json::to_value(status).expect("serialize cache header status"),
                serde_json::json!(expected)
            );
        }
        for (status, expected) in [
            (CacheAgeStatus::Fresh, "fresh"),
            (CacheAgeStatus::Stale, "stale"),
            (CacheAgeStatus::Unmanaged, "unmanaged"),
            (CacheAgeStatus::Unknown, "unknown"),
        ] {
            assert_eq!(status.as_str(), expected);
            assert_eq!(
                serde_json::to_value(status).expect("serialize cache age status"),
                serde_json::json!(expected)
            );
        }
        for (policy, expected) in [
            (CacheRecoveryPolicy::Automatic, "automatic"),
            (CacheRecoveryPolicy::Explicit, "explicit"),
            (CacheRecoveryPolicy::MissingOnly, "missing_only"),
            (CacheRecoveryPolicy::Unknown, "unknown"),
        ] {
            assert_eq!(policy.as_str(), expected);
            assert_eq!(
                serde_json::to_value(policy).expect("serialize cache recovery policy"),
                serde_json::json!(expected)
            );
        }
        for (status, expected) in [
            (CacheRefreshLockStatus::Active, "active"),
            (CacheRefreshLockStatus::Stale, "stale"),
            (CacheRefreshLockStatus::Invalid, "invalid"),
        ] {
            assert_eq!(status.as_str(), expected);
            assert_eq!(
                serde_json::to_value(status).expect("serialize refresh-lock status"),
                serde_json::json!(expected)
            );
        }
    }
}