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