ic_query/cache/model/mod.rs
1//! Module: cache::model
2//!
3//! Responsibility: define shared cache-validation and local inventory report contracts.
4//! Does not own: filesystem traversal, cache-family validation, or CLI output.
5//! Boundary: names family validation outcomes and exposes generic header, age,
6//! recovery-policy, and lock evidence without performing family-specific validation.
7
8use serde::{Deserialize as SerdeDeserialize, Serialize};
9use std::{fmt, path::PathBuf};
10
11/// Current serialized schema version for cache-status reports.
12pub const CACHE_STATUS_REPORT_SCHEMA_VERSION: u32 = 1;
13
14///
15/// CacheValidationStatus
16///
17/// Semantic validation result for an existing family-specific cache.
18///
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum CacheValidationStatus {
23 /// The cache passed its family-specific schema, identity, and completeness checks.
24 #[serde(rename = "ok")]
25 Valid,
26 /// The cache exists but failed family-specific validation.
27 Invalid,
28}
29
30impl CacheValidationStatus {
31 /// Return the stable serialized status label.
32 #[must_use]
33 pub const fn as_str(self) -> &'static str {
34 match self {
35 Self::Valid => "ok",
36 Self::Invalid => "invalid",
37 }
38 }
39}
40
41impl fmt::Display for CacheValidationStatus {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 formatter.write_str(self.as_str())
44 }
45}
46
47///
48/// CacheRefreshAttemptStatus
49///
50/// Lifecycle state for a complete-cache refresh attempt.
51///
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
54#[serde(rename_all = "snake_case")]
55pub enum CacheRefreshAttemptStatus {
56 /// A refresh started or published intermediate collection progress.
57 Running,
58 /// A refresh exhausted its source and published a complete cache.
59 Complete,
60 /// A refresh terminated without replacing the complete cache.
61 Failed,
62}
63
64impl CacheRefreshAttemptStatus {
65 /// Return the stable serialized lifecycle label.
66 #[must_use]
67 pub const fn as_str(self) -> &'static str {
68 match self {
69 Self::Running => "running",
70 Self::Complete => "complete",
71 Self::Failed => "failed",
72 }
73 }
74}
75
76impl fmt::Display for CacheRefreshAttemptStatus {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter.write_str(self.as_str())
79 }
80}
81
82///
83/// CacheHeaderStatus
84///
85/// Generic header-integrity classification for one complete cache file.
86///
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
89#[serde(rename_all = "snake_case")]
90pub enum CacheHeaderStatus {
91 /// The generic cache header is readable.
92 Readable,
93 /// The generic cache header cannot be read or parsed.
94 Invalid,
95}
96
97impl CacheHeaderStatus {
98 /// Return the stable serialized status label.
99 #[must_use]
100 pub const fn as_str(self) -> &'static str {
101 match self {
102 Self::Readable => "readable",
103 Self::Invalid => "invalid",
104 }
105 }
106}
107
108///
109/// CacheAgeStatus
110///
111/// Caller-relative age classification for one complete cache file.
112///
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "snake_case")]
116pub enum CacheAgeStatus {
117 /// The cache is within its registered family stale threshold.
118 Fresh,
119 /// The cache is older than its registered family stale threshold.
120 Stale,
121 /// The cache has a readable age but no registered family stale threshold.
122 Unmanaged,
123 /// The cache age cannot be calculated from its generic timestamp evidence.
124 Unknown,
125}
126
127impl CacheAgeStatus {
128 /// Return the stable serialized status label.
129 #[must_use]
130 pub const fn as_str(self) -> &'static str {
131 match self {
132 Self::Fresh => "fresh",
133 Self::Stale => "stale",
134 Self::Unmanaged => "unmanaged",
135 Self::Unknown => "unknown",
136 }
137 }
138}
139
140///
141/// CacheRecoveryPolicy
142///
143/// Owner policy for replacing recoverable invalid content at a canonical cache path.
144///
145
146#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
147#[serde(rename_all = "snake_case")]
148pub enum CacheRecoveryPolicy {
149 /// An ordinary owner read-through replaces recoverable invalid content.
150 Automatic,
151 /// Recovery requires an explicitly selected refresh operation.
152 Explicit,
153 /// Ordinary read-through creates a missing cache but does not replace invalid content.
154 MissingOnly,
155 /// The path does not identify a current canonical cache owner.
156 Unknown,
157}
158
159impl CacheRecoveryPolicy {
160 /// Return the stable serialized policy label.
161 #[must_use]
162 pub const fn as_str(self) -> &'static str {
163 match self {
164 Self::Automatic => "automatic",
165 Self::Explicit => "explicit",
166 Self::MissingOnly => "missing_only",
167 Self::Unknown => "unknown",
168 }
169 }
170}
171
172///
173/// CacheRefreshLockStatus
174///
175/// Observed kernel exclusion or invalid coordination-file classification.
176///
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
179#[serde(rename_all = "snake_case")]
180pub enum CacheRefreshLockStatus {
181 /// Another open descriptor holds exclusive refresh ownership.
182 Active,
183 /// The persistent coordination file is currently unlocked.
184 Available,
185 /// The coordination file is invalid.
186 Invalid,
187}
188
189impl CacheRefreshLockStatus {
190 /// Return the stable serialized status label.
191 #[must_use]
192 pub const fn as_str(self) -> &'static str {
193 match self {
194 Self::Active => "active",
195 Self::Available => "available",
196 Self::Invalid => "invalid",
197 }
198 }
199}
200
201///
202/// CacheStatusRequest
203///
204/// Local cache-root inspection request with a caller-supplied observation time.
205///
206
207#[derive(Clone, Debug, Eq, PartialEq)]
208pub struct CacheStatusRequest {
209 /// User-level cache root to inspect.
210 pub cache_root: PathBuf,
211 /// Observation time used to calculate cache ages.
212 pub now_unix_secs: u64,
213}
214
215impl CacheStatusRequest {
216 /// Construct a local cache-status request.
217 #[must_use]
218 pub fn new(cache_root: impl Into<PathBuf>, now_unix_secs: u64) -> Self {
219 Self {
220 cache_root: cache_root.into(),
221 now_unix_secs,
222 }
223 }
224}
225
226///
227/// CacheStatusReport
228///
229/// Bounded local inventory of known complete caches and refresh locks.
230///
231
232#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
233pub struct CacheStatusReport {
234 /// Cache-status report schema version.
235 pub schema_version: u32,
236 /// Inspected user-level cache root.
237 pub cache_root: String,
238 /// UTC timestamp at which local inspection was requested.
239 pub inspected_at: String,
240 /// Whether the cache root existed.
241 pub cache_root_found: bool,
242 /// Maximum number of cache and refresh-lock files inspected in one report.
243 pub scan_limit: usize,
244 /// Whether additional cache or refresh-lock candidates existed beyond the scan limit.
245 pub truncated: bool,
246 /// Whether the generic inventory performed family-specific semantic validation.
247 pub family_validation_performed: bool,
248 /// Number of caches with readable generic headers.
249 pub readable_header_count: usize,
250 /// Number of caches with unreadable or malformed generic headers.
251 pub invalid_header_count: usize,
252 /// Number of caches fresh under an explicit family policy.
253 pub fresh_count: usize,
254 /// Number of caches stale under an explicit family policy.
255 pub stale_count: usize,
256 /// Number of readable caches whose family has no registered age policy.
257 pub unmanaged_age_count: usize,
258 /// Number of caches whose age cannot be calculated from generic evidence.
259 pub unknown_age_count: usize,
260 /// Sum of filesystem sizes for returned cache files.
261 pub total_size_bytes: u64,
262 /// Canonically path-ordered cache rows.
263 pub caches: Vec<CacheStatusRow>,
264 /// Number of coordination files held by a refreshing process.
265 pub active_refresh_lock_count: usize,
266 /// Number of currently unlocked coordination files.
267 pub available_refresh_lock_count: usize,
268 /// Number of invalid coordination files.
269 pub invalid_refresh_lock_count: usize,
270 /// Sum of filesystem sizes for returned refresh-lock files.
271 pub refresh_lock_size_bytes: u64,
272 /// Canonically path-ordered refresh-lock rows.
273 pub refresh_locks: Vec<CacheRefreshLockStatusRow>,
274}
275
276///
277/// CacheStatusRow
278///
279/// Generic local metadata and caller-relative age for one complete cache file.
280///
281
282#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
283pub struct CacheStatusRow {
284 /// Stable component label inferred from cache identity or canonical path.
285 pub component: String,
286 /// Absolute cache-file path.
287 pub cache_path: String,
288 /// Cache-root-relative path.
289 pub relative_path: String,
290 /// Generic cache-header integrity without family-specific semantic validation.
291 pub header_status: CacheHeaderStatus,
292 /// Caller-relative age classification kept separate from header integrity.
293 pub age_status: CacheAgeStatus,
294 /// Owner policy for recovering invalid content at this canonical path.
295 pub recovery_policy: CacheRecoveryPolicy,
296 /// Serialized cache schema version when readable.
297 pub schema_version: Option<u32>,
298 /// Serialized network identity when present.
299 pub network: Option<String>,
300 /// Cache collection timestamp when readable.
301 pub fetched_at: Option<String>,
302 /// Caller-relative age when the timestamp is valid and not in the future.
303 pub age_seconds: Option<u64>,
304 /// Family age threshold when one is explicitly defined.
305 pub stale_after_seconds: Option<u64>,
306 /// Filesystem size of this cache file.
307 pub size_bytes: u64,
308 /// Generic header or timestamp inspection error; family-specific validation is separate.
309 pub inspection_error: Option<String>,
310}
311
312///
313/// CacheRefreshLockStatusRow
314///
315/// Local path and observed kernel exclusion for one persistent coordination file.
316///
317
318#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
319pub struct CacheRefreshLockStatusRow {
320 /// Stable component label inferred from the coordination-file path.
321 pub component: String,
322 /// Absolute refresh-lock path.
323 pub refresh_lock_path: String,
324 /// Cache-root-relative refresh-lock path.
325 pub relative_path: String,
326 /// Observed kernel exclusion or invalid sidecar classification.
327 pub status: CacheRefreshLockStatus,
328 /// Filesystem size of this refresh-lock file.
329 pub size_bytes: u64,
330 /// Coordination-file admission failure.
331 pub error: Option<String>,
332}
333
334#[cfg(test)]
335mod tests;
336
337///
338/// SnapshotRefreshAttemptStatus
339///
340/// Report-safe lifecycle evidence projected after a family validates its attempt sidecar.
341///
342
343#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
344pub struct SnapshotRefreshAttemptStatus {
345 /// Current lifecycle status.
346 pub status: CacheRefreshAttemptStatus,
347 /// Attempt start timestamp.
348 pub started_at: String,
349 /// Latest attempt update timestamp.
350 pub updated_at: String,
351 /// Requested page size.
352 pub page_size: u32,
353 /// Successfully retained pages.
354 pub pages_fetched: u32,
355 /// Successfully retained rows.
356 pub rows_fetched: usize,
357 /// Latest exclusive cursor.
358 pub last_cursor: Option<String>,
359 /// Terminal failure evidence.
360 pub last_error: Option<String>,
361}