Skip to main content

icydb_core/db/diagnostics/
model.rs

1//! Module: diagnostics::model
2//! Responsibility: diagnostics report DTO contracts and simple accessors.
3//! Does not own: store traversal, integrity scanning, or execution trace policy.
4//! Boundary: report assembly modules construct these DTOs; public callers read them.
5
6use crate::db::index::IndexState;
7use candid::CandidType;
8use serde::Deserialize;
9
10#[cfg_attr(doc, doc = "StorageReport\n\nLive storage snapshot payload.")]
11#[derive(CandidType, Clone, Debug, Default, Deserialize)]
12pub struct StorageReport {
13    pub(crate) storage_data: Vec<DataStoreSnapshot>,
14    pub(crate) storage_index: Vec<IndexStoreSnapshot>,
15    pub(crate) schema_storage: Vec<SchemaStoreSnapshot>,
16    pub(crate) entity_storage: Vec<EntitySnapshot>,
17    pub(crate) corrupted_keys: u64,
18    pub(crate) corrupted_entries: u64,
19}
20
21#[cfg_attr(
22    doc,
23    doc = "IntegrityTotals\n\nAggregated integrity-scan counters across all stores."
24)]
25#[derive(CandidType, Clone, Debug, Default, Deserialize)]
26pub struct IntegrityTotals {
27    pub(crate) data_rows_scanned: u64,
28    pub(crate) index_entries_scanned: u64,
29    pub(crate) corrupted_data_keys: u64,
30    pub(crate) corrupted_data_rows: u64,
31    pub(crate) corrupted_index_keys: u64,
32    pub(crate) corrupted_index_entries: u64,
33    pub(crate) missing_index_entries: u64,
34    pub(crate) divergent_index_entries: u64,
35    pub(crate) orphan_index_references: u64,
36    pub(crate) misuse_findings: u64,
37}
38
39impl IntegrityTotals {
40    pub(super) const fn add_store_snapshot(&mut self, store: &IntegrityStoreSnapshot) {
41        self.data_rows_scanned = self
42            .data_rows_scanned
43            .saturating_add(store.data_rows_scanned);
44        self.index_entries_scanned = self
45            .index_entries_scanned
46            .saturating_add(store.index_entries_scanned);
47        self.corrupted_data_keys = self
48            .corrupted_data_keys
49            .saturating_add(store.corrupted_data_keys);
50        self.corrupted_data_rows = self
51            .corrupted_data_rows
52            .saturating_add(store.corrupted_data_rows);
53        self.corrupted_index_keys = self
54            .corrupted_index_keys
55            .saturating_add(store.corrupted_index_keys);
56        self.corrupted_index_entries = self
57            .corrupted_index_entries
58            .saturating_add(store.corrupted_index_entries);
59        self.missing_index_entries = self
60            .missing_index_entries
61            .saturating_add(store.missing_index_entries);
62        self.divergent_index_entries = self
63            .divergent_index_entries
64            .saturating_add(store.divergent_index_entries);
65        self.orphan_index_references = self
66            .orphan_index_references
67            .saturating_add(store.orphan_index_references);
68        self.misuse_findings = self.misuse_findings.saturating_add(store.misuse_findings);
69    }
70
71    /// Return total number of data rows scanned.
72    #[must_use]
73    pub const fn data_rows_scanned(&self) -> u64 {
74        self.data_rows_scanned
75    }
76
77    /// Return total number of index entries scanned.
78    #[must_use]
79    pub const fn index_entries_scanned(&self) -> u64 {
80        self.index_entries_scanned
81    }
82
83    /// Return total number of corrupted data-key findings.
84    #[must_use]
85    pub const fn corrupted_data_keys(&self) -> u64 {
86        self.corrupted_data_keys
87    }
88
89    /// Return total number of corrupted data-row findings.
90    #[must_use]
91    pub const fn corrupted_data_rows(&self) -> u64 {
92        self.corrupted_data_rows
93    }
94
95    /// Return total number of corrupted index-key findings.
96    #[must_use]
97    pub const fn corrupted_index_keys(&self) -> u64 {
98        self.corrupted_index_keys
99    }
100
101    /// Return total number of corrupted index-entry findings.
102    #[must_use]
103    pub const fn corrupted_index_entries(&self) -> u64 {
104        self.corrupted_index_entries
105    }
106
107    /// Return total number of missing index-entry findings.
108    #[must_use]
109    pub const fn missing_index_entries(&self) -> u64 {
110        self.missing_index_entries
111    }
112
113    /// Return total number of divergent index-entry findings.
114    #[must_use]
115    pub const fn divergent_index_entries(&self) -> u64 {
116        self.divergent_index_entries
117    }
118
119    /// Return total number of orphan index-reference findings.
120    #[must_use]
121    pub const fn orphan_index_references(&self) -> u64 {
122        self.orphan_index_references
123    }
124
125    /// Return total number of misuse findings.
126    #[must_use]
127    pub const fn misuse_findings(&self) -> u64 {
128        self.misuse_findings
129    }
130}
131
132#[cfg_attr(
133    doc,
134    doc = "IntegrityStoreSnapshot\n\nPer-store integrity findings and scan counters."
135)]
136#[derive(CandidType, Clone, Debug, Default, Deserialize)]
137pub struct IntegrityStoreSnapshot {
138    pub(crate) path: String,
139    pub(crate) data_rows_scanned: u64,
140    pub(crate) index_entries_scanned: u64,
141    pub(crate) corrupted_data_keys: u64,
142    pub(crate) corrupted_data_rows: u64,
143    pub(crate) corrupted_index_keys: u64,
144    pub(crate) corrupted_index_entries: u64,
145    pub(crate) missing_index_entries: u64,
146    pub(crate) divergent_index_entries: u64,
147    pub(crate) orphan_index_references: u64,
148    pub(crate) misuse_findings: u64,
149}
150
151impl IntegrityStoreSnapshot {
152    /// Construct one empty store-level integrity snapshot.
153    #[must_use]
154    pub(crate) fn new(path: String) -> Self {
155        Self {
156            path,
157            ..Self::default()
158        }
159    }
160
161    /// Borrow store path.
162    #[must_use]
163    pub const fn path(&self) -> &str {
164        self.path.as_str()
165    }
166
167    /// Return number of scanned data rows.
168    #[must_use]
169    pub const fn data_rows_scanned(&self) -> u64 {
170        self.data_rows_scanned
171    }
172
173    /// Return number of scanned index entries.
174    #[must_use]
175    pub const fn index_entries_scanned(&self) -> u64 {
176        self.index_entries_scanned
177    }
178
179    /// Return number of corrupted data-key findings.
180    #[must_use]
181    pub const fn corrupted_data_keys(&self) -> u64 {
182        self.corrupted_data_keys
183    }
184
185    /// Return number of corrupted data-row findings.
186    #[must_use]
187    pub const fn corrupted_data_rows(&self) -> u64 {
188        self.corrupted_data_rows
189    }
190
191    /// Return number of corrupted index-key findings.
192    #[must_use]
193    pub const fn corrupted_index_keys(&self) -> u64 {
194        self.corrupted_index_keys
195    }
196
197    /// Return number of corrupted index-entry findings.
198    #[must_use]
199    pub const fn corrupted_index_entries(&self) -> u64 {
200        self.corrupted_index_entries
201    }
202
203    /// Return number of missing index-entry findings.
204    #[must_use]
205    pub const fn missing_index_entries(&self) -> u64 {
206        self.missing_index_entries
207    }
208
209    /// Return number of divergent index-entry findings.
210    #[must_use]
211    pub const fn divergent_index_entries(&self) -> u64 {
212        self.divergent_index_entries
213    }
214
215    /// Return number of orphan index-reference findings.
216    #[must_use]
217    pub const fn orphan_index_references(&self) -> u64 {
218        self.orphan_index_references
219    }
220
221    /// Return number of misuse findings.
222    #[must_use]
223    pub const fn misuse_findings(&self) -> u64 {
224        self.misuse_findings
225    }
226}
227
228#[cfg_attr(
229    doc,
230    doc = "IntegrityReport\n\nFull integrity-scan output across all registered stores."
231)]
232#[derive(CandidType, Clone, Debug, Default, Deserialize)]
233pub struct IntegrityReport {
234    pub(crate) stores: Vec<IntegrityStoreSnapshot>,
235    pub(crate) totals: IntegrityTotals,
236}
237
238impl IntegrityReport {
239    /// Construct one integrity report payload.
240    #[must_use]
241    pub(crate) const fn new(stores: Vec<IntegrityStoreSnapshot>, totals: IntegrityTotals) -> Self {
242        Self { stores, totals }
243    }
244
245    /// Borrow per-store integrity snapshots.
246    #[must_use]
247    pub const fn stores(&self) -> &[IntegrityStoreSnapshot] {
248        self.stores.as_slice()
249    }
250
251    /// Borrow aggregated integrity totals.
252    #[must_use]
253    pub const fn totals(&self) -> &IntegrityTotals {
254        &self.totals
255    }
256}
257
258impl StorageReport {
259    /// Construct one storage report payload.
260    #[must_use]
261    pub(crate) const fn new(
262        storage_data: Vec<DataStoreSnapshot>,
263        storage_index: Vec<IndexStoreSnapshot>,
264        schema_storage: Vec<SchemaStoreSnapshot>,
265        entity_storage: Vec<EntitySnapshot>,
266        corrupted_keys: u64,
267        corrupted_entries: u64,
268    ) -> Self {
269        Self {
270            storage_data,
271            storage_index,
272            schema_storage,
273            entity_storage,
274            corrupted_keys,
275            corrupted_entries,
276        }
277    }
278
279    /// Borrow data-store snapshots.
280    #[must_use]
281    pub const fn storage_data(&self) -> &[DataStoreSnapshot] {
282        self.storage_data.as_slice()
283    }
284
285    /// Borrow index-store snapshots.
286    #[must_use]
287    pub const fn storage_index(&self) -> &[IndexStoreSnapshot] {
288        self.storage_index.as_slice()
289    }
290
291    /// Borrow schema-store snapshots.
292    #[must_use]
293    pub const fn schema_storage(&self) -> &[SchemaStoreSnapshot] {
294        self.schema_storage.as_slice()
295    }
296
297    /// Borrow entity-level storage snapshots.
298    #[must_use]
299    pub const fn entity_storage(&self) -> &[EntitySnapshot] {
300        self.entity_storage.as_slice()
301    }
302
303    /// Return count of corrupted decoded data keys.
304    #[must_use]
305    pub const fn corrupted_keys(&self) -> u64 {
306        self.corrupted_keys
307    }
308
309    /// Return count of corrupted index entries.
310    #[must_use]
311    pub const fn corrupted_entries(&self) -> u64 {
312        self.corrupted_entries
313    }
314}
315
316#[cfg_attr(doc, doc = "SchemaStoreSnapshot\n\nSchema-store diagnostic row.")]
317#[derive(CandidType, Clone, Debug, Default, Deserialize)]
318pub struct SchemaStoreSnapshot {
319    pub(crate) path: String,
320    pub(crate) memory_id: Option<u8>,
321    pub(crate) stable_key: Option<String>,
322    pub(crate) schema_version: Option<u32>,
323    pub(crate) schema_fingerprint: Option<String>,
324    pub(crate) entity_count: u64,
325}
326
327#[derive(Clone, Debug, Eq, PartialEq)]
328pub(crate) struct StoreSnapshotAllocationIdentity {
329    memory_id: u8,
330    stable_key: String,
331}
332
333impl StoreSnapshotAllocationIdentity {
334    pub(crate) const fn new(memory_id: u8, stable_key: String) -> Self {
335        Self {
336            memory_id,
337            stable_key,
338        }
339    }
340
341    const fn memory_id(&self) -> u8 {
342        self.memory_id
343    }
344}
345
346#[derive(Clone, Debug, Default, Eq, PartialEq)]
347pub(crate) struct StoreSnapshotSchemaMetadata {
348    schema_version: Option<u32>,
349    schema_fingerprint: Option<String>,
350}
351
352impl StoreSnapshotSchemaMetadata {
353    pub(crate) const fn absent() -> Self {
354        Self {
355            schema_version: None,
356            schema_fingerprint: None,
357        }
358    }
359
360    pub(crate) const fn new(schema_version: u32, schema_fingerprint: String) -> Self {
361        Self {
362            schema_version: Some(schema_version),
363            schema_fingerprint: Some(schema_fingerprint),
364        }
365    }
366
367    const fn schema_version(&self) -> Option<u32> {
368        self.schema_version
369    }
370
371    fn schema_fingerprint(&self) -> Option<String> {
372        self.schema_fingerprint.clone()
373    }
374}
375
376#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
377pub(crate) struct IndexStoreSnapshotStats {
378    entries: u64,
379    user_entries: u64,
380    system_entries: u64,
381    memory_bytes: u64,
382    state: IndexState,
383}
384
385impl IndexStoreSnapshotStats {
386    pub(crate) const fn new(
387        entries: u64,
388        user_entries: u64,
389        system_entries: u64,
390        memory_bytes: u64,
391        state: IndexState,
392    ) -> Self {
393        Self {
394            entries,
395            user_entries,
396            system_entries,
397            memory_bytes,
398            state,
399        }
400    }
401}
402
403impl SchemaStoreSnapshot {
404    /// Construct one schema-store diagnostic row.
405    #[must_use]
406    pub(crate) fn new(
407        path: String,
408        allocation: Option<StoreSnapshotAllocationIdentity>,
409        schema_metadata: StoreSnapshotSchemaMetadata,
410        entity_count: u64,
411    ) -> Self {
412        let memory_id = allocation
413            .as_ref()
414            .map(StoreSnapshotAllocationIdentity::memory_id);
415        let stable_key = match allocation {
416            Some(allocation) => Some(allocation.stable_key),
417            None => None,
418        };
419        Self {
420            path,
421            memory_id,
422            stable_key,
423            schema_version: schema_metadata.schema_version(),
424            schema_fingerprint: schema_metadata.schema_fingerprint(),
425            entity_count,
426        }
427    }
428
429    /// Borrow store path.
430    #[must_use]
431    pub const fn path(&self) -> &str {
432        self.path.as_str()
433    }
434
435    /// Return stable-memory manager ID, when generated wiring supplied it.
436    #[must_use]
437    pub const fn memory_id(&self) -> Option<u8> {
438        self.memory_id
439    }
440
441    /// Return durable stable-memory key, when generated wiring supplied it.
442    #[must_use]
443    pub const fn stable_key(&self) -> Option<&str> {
444        match &self.stable_key {
445            Some(value) => Some(value.as_str()),
446            None => None,
447        }
448    }
449
450    /// Return accepted schema/catalog version, when known.
451    #[must_use]
452    pub const fn schema_version(&self) -> Option<u32> {
453        self.schema_version
454    }
455
456    /// Return accepted schema/catalog fingerprint, when known.
457    #[must_use]
458    pub const fn schema_fingerprint(&self) -> Option<&str> {
459        match &self.schema_fingerprint {
460            Some(value) => Some(value.as_str()),
461            None => None,
462        }
463    }
464
465    /// Return number of entity schemas represented in this schema catalog.
466    #[must_use]
467    pub const fn entity_count(&self) -> u64 {
468        self.entity_count
469    }
470}
471
472#[cfg_attr(doc, doc = "DataStoreSnapshot\n\nData-store snapshot row.")]
473#[derive(CandidType, Clone, Debug, Default, Deserialize)]
474pub struct DataStoreSnapshot {
475    pub(crate) path: String,
476    pub(crate) memory_id: Option<u8>,
477    pub(crate) stable_key: Option<String>,
478    pub(crate) schema_version: Option<u32>,
479    pub(crate) schema_fingerprint: Option<String>,
480    pub(crate) entries: u64,
481    pub(crate) memory_bytes: u64,
482}
483
484impl DataStoreSnapshot {
485    /// Construct one data-store snapshot row.
486    #[must_use]
487    pub(crate) fn new(
488        path: String,
489        allocation: Option<StoreSnapshotAllocationIdentity>,
490        schema_metadata: StoreSnapshotSchemaMetadata,
491        entries: u64,
492        memory_bytes: u64,
493    ) -> Self {
494        let memory_id = allocation
495            .as_ref()
496            .map(StoreSnapshotAllocationIdentity::memory_id);
497        let stable_key = match allocation {
498            Some(allocation) => Some(allocation.stable_key),
499            None => None,
500        };
501        Self {
502            path,
503            memory_id,
504            stable_key,
505            schema_version: schema_metadata.schema_version(),
506            schema_fingerprint: schema_metadata.schema_fingerprint(),
507            entries,
508            memory_bytes,
509        }
510    }
511
512    /// Borrow store path.
513    #[must_use]
514    pub const fn path(&self) -> &str {
515        self.path.as_str()
516    }
517
518    /// Return stable-memory manager ID, when generated wiring supplied it.
519    #[must_use]
520    pub const fn memory_id(&self) -> Option<u8> {
521        self.memory_id
522    }
523
524    /// Return durable stable-memory key, when generated wiring supplied it.
525    #[must_use]
526    pub const fn stable_key(&self) -> Option<&str> {
527        match &self.stable_key {
528            Some(value) => Some(value.as_str()),
529            None => None,
530        }
531    }
532
533    /// Return accepted schema/catalog version, when known.
534    #[must_use]
535    pub const fn schema_version(&self) -> Option<u32> {
536        self.schema_version
537    }
538
539    /// Return accepted schema/catalog fingerprint, when known.
540    #[must_use]
541    pub const fn schema_fingerprint(&self) -> Option<&str> {
542        match &self.schema_fingerprint {
543            Some(value) => Some(value.as_str()),
544            None => None,
545        }
546    }
547
548    /// Return row count.
549    #[must_use]
550    pub const fn entries(&self) -> u64 {
551        self.entries
552    }
553
554    /// Return memory usage in bytes.
555    #[must_use]
556    pub const fn memory_bytes(&self) -> u64 {
557        self.memory_bytes
558    }
559}
560
561#[cfg_attr(doc, doc = "IndexStoreSnapshot\n\nIndex-store snapshot row.")]
562#[derive(CandidType, Clone, Debug, Default, Deserialize)]
563pub struct IndexStoreSnapshot {
564    pub(crate) path: String,
565    pub(crate) memory_id: Option<u8>,
566    pub(crate) stable_key: Option<String>,
567    pub(crate) schema_version: Option<u32>,
568    pub(crate) schema_fingerprint: Option<String>,
569    pub(crate) entries: u64,
570    pub(crate) user_entries: u64,
571    pub(crate) system_entries: u64,
572    pub(crate) memory_bytes: u64,
573    pub(crate) state: IndexState,
574}
575
576impl IndexStoreSnapshot {
577    /// Construct one index-store snapshot row.
578    #[must_use]
579    pub(crate) fn new(
580        path: String,
581        allocation: Option<StoreSnapshotAllocationIdentity>,
582        schema_metadata: StoreSnapshotSchemaMetadata,
583        stats: IndexStoreSnapshotStats,
584    ) -> Self {
585        let memory_id = allocation
586            .as_ref()
587            .map(StoreSnapshotAllocationIdentity::memory_id);
588        let stable_key = match allocation {
589            Some(allocation) => Some(allocation.stable_key),
590            None => None,
591        };
592        Self {
593            path,
594            memory_id,
595            stable_key,
596            schema_version: schema_metadata.schema_version(),
597            schema_fingerprint: schema_metadata.schema_fingerprint(),
598            entries: stats.entries,
599            user_entries: stats.user_entries,
600            system_entries: stats.system_entries,
601            memory_bytes: stats.memory_bytes,
602            state: stats.state,
603        }
604    }
605
606    /// Borrow store path.
607    #[must_use]
608    pub const fn path(&self) -> &str {
609        self.path.as_str()
610    }
611
612    /// Return stable-memory manager ID, when generated wiring supplied it.
613    #[must_use]
614    pub const fn memory_id(&self) -> Option<u8> {
615        self.memory_id
616    }
617
618    /// Return durable stable-memory key, when generated wiring supplied it.
619    #[must_use]
620    pub const fn stable_key(&self) -> Option<&str> {
621        match &self.stable_key {
622            Some(value) => Some(value.as_str()),
623            None => None,
624        }
625    }
626
627    /// Return accepted schema/catalog version, when known.
628    #[must_use]
629    pub const fn schema_version(&self) -> Option<u32> {
630        self.schema_version
631    }
632
633    /// Return accepted schema/catalog fingerprint, when known.
634    #[must_use]
635    pub const fn schema_fingerprint(&self) -> Option<&str> {
636        match &self.schema_fingerprint {
637            Some(value) => Some(value.as_str()),
638            None => None,
639        }
640    }
641
642    /// Return total entry count.
643    #[must_use]
644    pub const fn entries(&self) -> u64 {
645        self.entries
646    }
647
648    /// Return user-namespace entry count.
649    #[must_use]
650    pub const fn user_entries(&self) -> u64 {
651        self.user_entries
652    }
653
654    /// Return system-namespace entry count.
655    #[must_use]
656    pub const fn system_entries(&self) -> u64 {
657        self.system_entries
658    }
659
660    /// Return memory usage in bytes.
661    #[must_use]
662    pub const fn memory_bytes(&self) -> u64 {
663        self.memory_bytes
664    }
665
666    /// Return the current explicit runtime lifecycle state for this index
667    /// store snapshot.
668    #[must_use]
669    pub const fn state(&self) -> IndexState {
670        self.state
671    }
672}
673
674#[cfg_attr(doc, doc = "EntitySnapshot\n\nPer-entity storage snapshot row.")]
675#[derive(CandidType, Clone, Debug, Default, Deserialize)]
676pub struct EntitySnapshot {
677    pub(crate) store: String,
678
679    pub(crate) path: String,
680
681    pub(crate) entries: u64,
682
683    pub(crate) memory_bytes: u64,
684}
685
686impl EntitySnapshot {
687    /// Construct one entity-storage snapshot row.
688    #[must_use]
689    pub(crate) const fn new(store: String, path: String, entries: u64, memory_bytes: u64) -> Self {
690        Self {
691            store,
692            path,
693            entries,
694            memory_bytes,
695        }
696    }
697
698    /// Borrow store path.
699    #[must_use]
700    pub const fn store(&self) -> &str {
701        self.store.as_str()
702    }
703
704    /// Borrow entity path.
705    #[must_use]
706    pub const fn path(&self) -> &str {
707        self.path.as_str()
708    }
709
710    /// Return row count.
711    #[must_use]
712    pub const fn entries(&self) -> u64 {
713        self.entries
714    }
715
716    /// Return memory usage in bytes.
717    #[must_use]
718    pub const fn memory_bytes(&self) -> u64 {
719        self.memory_bytes
720    }
721}