Skip to main content

weavatrix_scan/report/
mod.rs

1use std::path::PathBuf;
2
3mod compact;
4mod scan;
5
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct FileIdentity {
9    pub file_system: u64,
10    pub file: u64,
11}
12
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
15pub struct FileVersion {
16    #[cfg_attr(feature = "serde", serde(default))]
17    pub modified_ns: Option<u128>,
18    #[cfg_attr(feature = "serde", serde(default))]
19    pub changed_ns: Option<u128>,
20    #[cfg_attr(feature = "serde", serde(default))]
21    pub identity: Option<FileIdentity>,
22}
23
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ScannedFile {
27    #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
28    pub absolute: PathBuf,
29    pub relative: String,
30    pub bytes: u64,
31    pub content_hash: Option<String>,
32    /// Whole-content validation fingerprint used only for strict cache reuse.
33    #[cfg_attr(feature = "serde", serde(default))]
34    pub content_fingerprint: Option<String>,
35    #[cfg_attr(feature = "serde", serde(default))]
36    pub version: FileVersion,
37    #[cfg_attr(feature = "serde", serde(default))]
38    pub binary_checked: bool,
39}
40
41/// Selected-file evidence without a duplicated absolute path allocation.
42///
43/// Join `relative` to [`CompactScanReport::root`] only when an absolute path
44/// is needed.
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct CompactScannedFile {
48    pub relative: Box<str>,
49    pub bytes: u64,
50    /// Allocated only when content inspection was requested.
51    #[cfg_attr(feature = "serde", serde(default))]
52    pub content: Option<Box<CompactContentEvidence>>,
53}
54
55/// Optional rich evidence kept out of metadata-only compact entries.
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct CompactContentEvidence {
59    pub content_hash: Option<Box<str>>,
60    /// Whole-content validation fingerprint used only for strict cache reuse.
61    #[cfg_attr(feature = "serde", serde(default))]
62    pub content_fingerprint: Option<Box<str>>,
63    #[cfg_attr(feature = "serde", serde(default))]
64    pub version: FileVersion,
65    #[cfg_attr(feature = "serde", serde(default))]
66    pub binary_checked: bool,
67}
68
69impl CompactScannedFile {
70    /// Returns the strong content hash when content hashing was requested.
71    #[must_use]
72    pub fn content_hash(&self) -> Option<&str> {
73        self.content
74            .as_deref()
75            .and_then(|content| content.content_hash.as_deref())
76    }
77}
78
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
81pub struct ScanCacheStats {
82    pub reused_hashes: u64,
83    pub content_reads: u64,
84    /// Whole-file fingerprint reads used to validate cached SHA-256 values.
85    #[cfg_attr(feature = "serde", serde(default))]
86    pub fingerprint_reads: u64,
87}
88
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
91pub enum SkipKind {
92    Binary,
93    FileSystemBoundary,
94    Extension,
95    Ignored,
96    IoError,
97    MaxDepth,
98    Oversized,
99    PathEscape,
100    StandardDirectory,
101    Hidden,
102    Override,
103    Symlink,
104    SymlinkLoop,
105    ScanLimit,
106    ConcurrentModification,
107}
108
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct SkippedEntry {
112    pub relative: String,
113    pub kind: SkipKind,
114    pub detail: Option<String>,
115}
116
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct ScanWarning {
120    pub relative: Option<String>,
121    pub message: String,
122}
123
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
126pub enum IgnoreSourceKind {
127    GitGlobal,
128    GitExclude,
129    GitIgnore,
130    DotIgnore,
131    Custom,
132    Explicit,
133    Override,
134}
135
136#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct IgnoreSourceEvidence {
139    pub kind: IgnoreSourceKind,
140    pub location: String,
141    pub content_hash: String,
142}
143
144#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum ScanTermination {
147    MaxEntries,
148    MaxTotalBytes,
149    Timeout,
150    Cancelled,
151}
152
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct ScanReport {
156    #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
157    pub root: PathBuf,
158    pub files: Vec<ScannedFile>,
159    pub skipped: Vec<SkippedEntry>,
160    pub warnings: Vec<ScanWarning>,
161    /// Every ignore input that participated in path selection.
162    #[cfg_attr(feature = "serde", serde(default))]
163    pub ignore_sources: Vec<IgnoreSourceEvidence>,
164    pub revision: String,
165    /// False when local I/O or ignore-rule errors made evidence partial.
166    #[cfg_attr(feature = "serde", serde(default = "default_complete"))]
167    pub complete: bool,
168    /// Why a bounded scan stopped before exhausting the tree.
169    #[cfg_attr(feature = "serde", serde(default))]
170    pub termination: Option<ScanTermination>,
171    /// False when selection depended on host-level configuration.
172    #[cfg_attr(feature = "serde", serde(default = "default_portable"))]
173    pub portable: bool,
174    /// Evidence of content work reused from an older persistent report.
175    #[cfg_attr(feature = "serde", serde(default))]
176    pub cache: ScanCacheStats,
177    #[cfg_attr(feature = "serde", serde(skip, default = "default_record_skipped"))]
178    record_skipped: bool,
179}
180
181/// Memory-efficient deterministic manifest retaining the root path once.
182///
183/// This report preserves scanner selection, hashes, revision, warnings and
184/// typed skip evidence while avoiding one absolute `PathBuf` per selected
185/// file.
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct CompactScanReport {
189    #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
190    pub root: PathBuf,
191    pub files: Vec<CompactScannedFile>,
192    pub skipped: Vec<SkippedEntry>,
193    pub warnings: Vec<ScanWarning>,
194    #[cfg_attr(feature = "serde", serde(default))]
195    pub ignore_sources: Vec<IgnoreSourceEvidence>,
196    pub revision: String,
197    #[cfg_attr(feature = "serde", serde(default = "default_complete"))]
198    pub complete: bool,
199    #[cfg_attr(feature = "serde", serde(default))]
200    pub termination: Option<ScanTermination>,
201    #[cfg_attr(feature = "serde", serde(default = "default_portable"))]
202    pub portable: bool,
203    #[cfg_attr(feature = "serde", serde(default))]
204    pub cache: ScanCacheStats,
205}
206
207#[cfg(feature = "serde")]
208const fn default_complete() -> bool {
209    true
210}
211
212#[cfg(feature = "serde")]
213const fn default_record_skipped() -> bool {
214    true
215}
216
217#[cfg(feature = "serde")]
218const fn default_portable() -> bool {
219    true
220}