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) storage: StoreSnapshotStorageMode,
321    pub(crate) memory_id: Option<u8>,
322    pub(crate) stable_key: Option<String>,
323    pub(crate) schema_version: Option<u32>,
324    pub(crate) schema_fingerprint: Option<String>,
325    pub(crate) entity_count: u64,
326}
327
328/// Diagnostic storage mode reported for one store-role snapshot.
329///
330/// This is observability metadata only. It does not participate in allocation
331/// identity, stable-key generation, or durable row/index/schema storage ABI.
332#[derive(CandidType, Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
333pub enum StoreSnapshotStorageMode {
334    #[default]
335    Stable,
336    Heap,
337}
338
339impl StoreSnapshotStorageMode {
340    /// Return the user-facing storage mode label.
341    #[must_use]
342    pub const fn as_str(self) -> &'static str {
343        match self {
344            Self::Stable => "stable",
345            Self::Heap => "heap",
346        }
347    }
348}
349
350#[derive(Clone, Debug, Eq, PartialEq)]
351pub(crate) struct StoreSnapshotAllocationIdentity {
352    memory_id: u8,
353    stable_key: String,
354}
355
356impl StoreSnapshotAllocationIdentity {
357    pub(crate) const fn new(memory_id: u8, stable_key: String) -> Self {
358        Self {
359            memory_id,
360            stable_key,
361        }
362    }
363
364    const fn memory_id(&self) -> u8 {
365        self.memory_id
366    }
367}
368
369#[derive(Clone, Debug, Default, Eq, PartialEq)]
370pub(crate) struct StoreSnapshotSchemaMetadata {
371    schema_version: Option<u32>,
372    schema_fingerprint: Option<String>,
373}
374
375impl StoreSnapshotSchemaMetadata {
376    pub(crate) const fn absent() -> Self {
377        Self {
378            schema_version: None,
379            schema_fingerprint: None,
380        }
381    }
382
383    pub(crate) const fn new(schema_version: u32, schema_fingerprint: String) -> Self {
384        Self {
385            schema_version: Some(schema_version),
386            schema_fingerprint: Some(schema_fingerprint),
387        }
388    }
389
390    const fn schema_version(&self) -> Option<u32> {
391        self.schema_version
392    }
393
394    fn schema_fingerprint(&self) -> Option<String> {
395        self.schema_fingerprint.clone()
396    }
397}
398
399#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
400pub(crate) struct IndexStoreSnapshotStats {
401    entries: u64,
402    user_entries: u64,
403    system_entries: u64,
404    memory_bytes: u64,
405    state: IndexState,
406}
407
408impl IndexStoreSnapshotStats {
409    pub(crate) const fn new(
410        entries: u64,
411        user_entries: u64,
412        system_entries: u64,
413        memory_bytes: u64,
414        state: IndexState,
415    ) -> Self {
416        Self {
417            entries,
418            user_entries,
419            system_entries,
420            memory_bytes,
421            state,
422        }
423    }
424}
425
426impl SchemaStoreSnapshot {
427    /// Construct one schema-store diagnostic row.
428    #[must_use]
429    pub(crate) fn new(
430        path: String,
431        storage: StoreSnapshotStorageMode,
432        allocation: Option<StoreSnapshotAllocationIdentity>,
433        schema_metadata: StoreSnapshotSchemaMetadata,
434        entity_count: u64,
435    ) -> Self {
436        let memory_id = allocation
437            .as_ref()
438            .map(StoreSnapshotAllocationIdentity::memory_id);
439        let stable_key = match allocation {
440            Some(allocation) => Some(allocation.stable_key),
441            None => None,
442        };
443        Self {
444            path,
445            storage,
446            memory_id,
447            stable_key,
448            schema_version: schema_metadata.schema_version(),
449            schema_fingerprint: schema_metadata.schema_fingerprint(),
450            entity_count,
451        }
452    }
453
454    /// Borrow store path.
455    #[must_use]
456    pub const fn path(&self) -> &str {
457        self.path.as_str()
458    }
459
460    /// Return diagnostic storage mode.
461    #[must_use]
462    pub const fn storage(&self) -> StoreSnapshotStorageMode {
463        self.storage
464    }
465
466    /// Return stable-memory manager ID, when generated wiring supplied it.
467    #[must_use]
468    pub const fn memory_id(&self) -> Option<u8> {
469        self.memory_id
470    }
471
472    /// Return durable stable-memory key, when generated wiring supplied it.
473    #[must_use]
474    pub const fn stable_key(&self) -> Option<&str> {
475        match &self.stable_key {
476            Some(value) => Some(value.as_str()),
477            None => None,
478        }
479    }
480
481    /// Return accepted schema/catalog version, when known.
482    #[must_use]
483    pub const fn schema_version(&self) -> Option<u32> {
484        self.schema_version
485    }
486
487    /// Return accepted schema/catalog fingerprint, when known.
488    #[must_use]
489    pub const fn schema_fingerprint(&self) -> Option<&str> {
490        match &self.schema_fingerprint {
491            Some(value) => Some(value.as_str()),
492            None => None,
493        }
494    }
495
496    /// Return number of entity schemas represented in this schema catalog.
497    #[must_use]
498    pub const fn entity_count(&self) -> u64 {
499        self.entity_count
500    }
501}
502
503#[cfg_attr(doc, doc = "DataStoreSnapshot\n\nData-store snapshot row.")]
504#[derive(CandidType, Clone, Debug, Default, Deserialize)]
505pub struct DataStoreSnapshot {
506    pub(crate) path: String,
507    pub(crate) storage: StoreSnapshotStorageMode,
508    pub(crate) memory_id: Option<u8>,
509    pub(crate) stable_key: Option<String>,
510    pub(crate) schema_version: Option<u32>,
511    pub(crate) schema_fingerprint: Option<String>,
512    pub(crate) entries: u64,
513    pub(crate) memory_bytes: u64,
514}
515
516impl DataStoreSnapshot {
517    /// Construct one data-store snapshot row.
518    #[must_use]
519    pub(crate) fn new(
520        path: String,
521        storage: StoreSnapshotStorageMode,
522        allocation: Option<StoreSnapshotAllocationIdentity>,
523        schema_metadata: StoreSnapshotSchemaMetadata,
524        entries: u64,
525        memory_bytes: u64,
526    ) -> Self {
527        let memory_id = allocation
528            .as_ref()
529            .map(StoreSnapshotAllocationIdentity::memory_id);
530        let stable_key = match allocation {
531            Some(allocation) => Some(allocation.stable_key),
532            None => None,
533        };
534        Self {
535            path,
536            storage,
537            memory_id,
538            stable_key,
539            schema_version: schema_metadata.schema_version(),
540            schema_fingerprint: schema_metadata.schema_fingerprint(),
541            entries,
542            memory_bytes,
543        }
544    }
545
546    /// Borrow store path.
547    #[must_use]
548    pub const fn path(&self) -> &str {
549        self.path.as_str()
550    }
551
552    /// Return diagnostic storage mode.
553    #[must_use]
554    pub const fn storage(&self) -> StoreSnapshotStorageMode {
555        self.storage
556    }
557
558    /// Return stable-memory manager ID, when generated wiring supplied it.
559    #[must_use]
560    pub const fn memory_id(&self) -> Option<u8> {
561        self.memory_id
562    }
563
564    /// Return durable stable-memory key, when generated wiring supplied it.
565    #[must_use]
566    pub const fn stable_key(&self) -> Option<&str> {
567        match &self.stable_key {
568            Some(value) => Some(value.as_str()),
569            None => None,
570        }
571    }
572
573    /// Return accepted schema/catalog version, when known.
574    #[must_use]
575    pub const fn schema_version(&self) -> Option<u32> {
576        self.schema_version
577    }
578
579    /// Return accepted schema/catalog fingerprint, when known.
580    #[must_use]
581    pub const fn schema_fingerprint(&self) -> Option<&str> {
582        match &self.schema_fingerprint {
583            Some(value) => Some(value.as_str()),
584            None => None,
585        }
586    }
587
588    /// Return row count.
589    #[must_use]
590    pub const fn entries(&self) -> u64 {
591        self.entries
592    }
593
594    /// Return memory usage in bytes.
595    #[must_use]
596    pub const fn memory_bytes(&self) -> u64 {
597        self.memory_bytes
598    }
599}
600
601#[cfg_attr(doc, doc = "IndexStoreSnapshot\n\nIndex-store snapshot row.")]
602#[derive(CandidType, Clone, Debug, Default, Deserialize)]
603pub struct IndexStoreSnapshot {
604    pub(crate) path: String,
605    pub(crate) storage: StoreSnapshotStorageMode,
606    pub(crate) memory_id: Option<u8>,
607    pub(crate) stable_key: Option<String>,
608    pub(crate) schema_version: Option<u32>,
609    pub(crate) schema_fingerprint: Option<String>,
610    pub(crate) entries: u64,
611    pub(crate) user_entries: u64,
612    pub(crate) system_entries: u64,
613    pub(crate) memory_bytes: u64,
614    pub(crate) state: IndexState,
615}
616
617impl IndexStoreSnapshot {
618    /// Construct one index-store snapshot row.
619    #[must_use]
620    pub(crate) fn new(
621        path: String,
622        storage: StoreSnapshotStorageMode,
623        allocation: Option<StoreSnapshotAllocationIdentity>,
624        schema_metadata: StoreSnapshotSchemaMetadata,
625        stats: IndexStoreSnapshotStats,
626    ) -> Self {
627        let memory_id = allocation
628            .as_ref()
629            .map(StoreSnapshotAllocationIdentity::memory_id);
630        let stable_key = match allocation {
631            Some(allocation) => Some(allocation.stable_key),
632            None => None,
633        };
634        Self {
635            path,
636            storage,
637            memory_id,
638            stable_key,
639            schema_version: schema_metadata.schema_version(),
640            schema_fingerprint: schema_metadata.schema_fingerprint(),
641            entries: stats.entries,
642            user_entries: stats.user_entries,
643            system_entries: stats.system_entries,
644            memory_bytes: stats.memory_bytes,
645            state: stats.state,
646        }
647    }
648
649    /// Borrow store path.
650    #[must_use]
651    pub const fn path(&self) -> &str {
652        self.path.as_str()
653    }
654
655    /// Return diagnostic storage mode.
656    #[must_use]
657    pub const fn storage(&self) -> StoreSnapshotStorageMode {
658        self.storage
659    }
660
661    /// Return stable-memory manager ID, when generated wiring supplied it.
662    #[must_use]
663    pub const fn memory_id(&self) -> Option<u8> {
664        self.memory_id
665    }
666
667    /// Return durable stable-memory key, when generated wiring supplied it.
668    #[must_use]
669    pub const fn stable_key(&self) -> Option<&str> {
670        match &self.stable_key {
671            Some(value) => Some(value.as_str()),
672            None => None,
673        }
674    }
675
676    /// Return accepted schema/catalog version, when known.
677    #[must_use]
678    pub const fn schema_version(&self) -> Option<u32> {
679        self.schema_version
680    }
681
682    /// Return accepted schema/catalog fingerprint, when known.
683    #[must_use]
684    pub const fn schema_fingerprint(&self) -> Option<&str> {
685        match &self.schema_fingerprint {
686            Some(value) => Some(value.as_str()),
687            None => None,
688        }
689    }
690
691    /// Return total entry count.
692    #[must_use]
693    pub const fn entries(&self) -> u64 {
694        self.entries
695    }
696
697    /// Return user-namespace entry count.
698    #[must_use]
699    pub const fn user_entries(&self) -> u64 {
700        self.user_entries
701    }
702
703    /// Return system-namespace entry count.
704    #[must_use]
705    pub const fn system_entries(&self) -> u64 {
706        self.system_entries
707    }
708
709    /// Return memory usage in bytes.
710    #[must_use]
711    pub const fn memory_bytes(&self) -> u64 {
712        self.memory_bytes
713    }
714
715    /// Return the current explicit runtime lifecycle state for this index
716    /// store snapshot.
717    #[must_use]
718    pub const fn state(&self) -> IndexState {
719        self.state
720    }
721}
722
723#[cfg_attr(doc, doc = "EntitySnapshot\n\nPer-entity storage snapshot row.")]
724#[derive(CandidType, Clone, Debug, Default, Deserialize)]
725pub struct EntitySnapshot {
726    pub(crate) store: String,
727
728    pub(crate) path: String,
729
730    pub(crate) entries: u64,
731
732    pub(crate) memory_bytes: u64,
733}
734
735impl EntitySnapshot {
736    /// Construct one entity-storage snapshot row.
737    #[must_use]
738    pub(crate) const fn new(store: String, path: String, entries: u64, memory_bytes: u64) -> Self {
739        Self {
740            store,
741            path,
742            entries,
743            memory_bytes,
744        }
745    }
746
747    /// Borrow store path.
748    #[must_use]
749    pub const fn store(&self) -> &str {
750        self.store.as_str()
751    }
752
753    /// Borrow entity path.
754    #[must_use]
755    pub const fn path(&self) -> &str {
756        self.path.as_str()
757    }
758
759    /// Return row count.
760    #[must_use]
761    pub const fn entries(&self) -> u64 {
762        self.entries
763    }
764
765    /// Return memory usage in bytes.
766    #[must_use]
767    pub const fn memory_bytes(&self) -> u64 {
768        self.memory_bytes
769    }
770}