1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//! Category enforcement finding data.
use std::fmt;
use std::path::Path;
/// Optional source location for an enforcement finding.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FindingLocation {
/// Source file containing the finding.
pub file: String,
/// One-based source line.
pub line: usize,
}
impl FindingLocation {
/// Build a source location from a path and one-based line.
///
/// The path is converted to a display string so that findings are
/// self-contained and do not borrow from the filesystem walk. This
/// makes them cheap to clone into reports and serialize to CI logs.
#[inline]
pub fn new(path: &Path, line: usize) -> Self {
Self {
file: path.display().to_string(),
line,
}
}
}
/// Category gate failure with an actionable remediation.
///
/// The category gate enforces the A/B/C taxonomy: Category A ops must be
/// zero-overhead compositions, Category B patterns are forbidden entirely,
/// and Category C ops must declare honest per-backend availability. Every
/// variant carries enough context to produce a `Fix:`-prefixed message.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CategoryFinding {
/// Standard message-based finding.
Message {
/// Operation ID associated with the finding.
op_id: String,
/// Enforcement pass that emitted this finding.
pass: &'static str,
/// Human-readable failure text.
message: String,
/// Optional source location.
location: Option<FindingLocation>,
/// Severity level if explicitly classified.
severity: Option<String>,
/// Whether this finding was produced while falling back to text scan
/// because the source file could not be parsed.
parse_error: bool,
},
/// Category A AST violation with a structured kind and mandatory location.
CategoryAViolation {
/// Operation ID associated with the finding.
op_id: String,
/// Kind of violation (e.g. "Vec::new", "dyn Trait").
kind: String,
/// Source location of the violation.
location: FindingLocation,
/// Human-readable failure text.
message: String,
},
}
impl CategoryFinding {
/// Emit an operation-scoped category finding.
///
/// Use this constructor when the violation is tied to a specific op
/// rather than a source file or a global pattern. The resulting finding
/// has no location and no severity until one is attached with the
/// `with_*` builder methods.
#[inline]
pub fn op(op_id: &str, pass: &'static str, message: impl Into<String>) -> Self {
Self::Message {
op_id: op_id.to_string(),
pass,
message: message.into(),
location: None,
severity: None,
parse_error: false,
}
}
/// Emit a source-scoped Category B finding.
///
/// Category B findings are typically discovered by scanning source files
/// for forbidden patterns (dynamic dispatch, runtime registries, etc.).
/// The path and line anchor the finding so that IDE integrations can
/// jump straight to the offending construct.
#[inline]
pub fn located(
pass: &'static str,
path: &Path,
line: usize,
message: impl Into<String>,
) -> Self {
Self::Message {
op_id: "CategoryB".to_string(),
pass,
message: message.into(),
location: Some(FindingLocation::new(path, line)),
severity: None,
parse_error: false,
}
}
/// Emit a Category A AST violation.
///
/// Category A requires zero-overhead composition. If an op's source
/// contains a construct that cannot be lowered to exactly the hand-written
/// baseline (e.g. a heap allocation, a virtual call, or a hidden wrapper),
/// this constructor produces a structured violation with a mandatory
/// source location.
#[inline]
pub fn category_a_violation(
op_id: &str,
kind: impl Into<String>,
location: FindingLocation,
) -> Self {
let kind = kind.into();
let message = format!(
"contains forbidden {kind} at {}:{}. Fix: remove the construct to maintain Category A zero-overhead guarantee.",
location.file, location.line
);
Self::CategoryAViolation {
op_id: op_id.to_string(),
kind,
location,
message,
}
}
/// Operation ID associated with this finding.
///
/// For `Message` variants this is the explicit `op_id`. For
/// `CategoryAViolation` variants it is also the explicit `op_id`.
/// Use this to filter findings by operation when rendering per-op
/// reports.
#[inline]
pub fn op_id(&self) -> &str {
match self {
Self::Message { op_id, .. } => op_id,
Self::CategoryAViolation { op_id, .. } => op_id,
}
}
/// Enforcement pass that emitted this finding.
///
/// The pass name is a stable snake_case identifier used by CI dashboards
/// to route findings to the right owner (e.g. `category_a_zero_overhead`
/// goes to the op author, `category_b_tripwire` goes to the architecture
/// gate maintainer).
#[inline]
pub fn pass(&self) -> &'static str {
match self {
Self::Message { pass, .. } => pass,
Self::CategoryAViolation { .. } => "category_a_zero_overhead",
}
}
/// Human-readable failure text.
///
/// This is the string that appears in CLI output and CI logs. It always
/// includes a `Fix:` prefix when produced by the standard constructors.
#[inline]
pub fn message(&self) -> &str {
match self {
Self::Message { message, .. } => message,
Self::CategoryAViolation { message, .. } => message,
}
}
/// Source location if available.
///
/// Returns `Some` for located findings and `CategoryAViolation`
/// variants. Returns `None` for plain `Message` findings that were
/// emitted without a file path.
#[inline]
pub fn location(&self) -> Option<&FindingLocation> {
match self {
Self::Message { location, .. } => location.as_ref(),
Self::CategoryAViolation { location, .. } => Some(location),
}
}
/// Severity level if explicitly classified.
///
/// The category gate does not use severity levels — every finding is a
/// hard failure. This field exists for compatibility with downstream
/// report formats that may layer additional classification on top.
#[inline]
pub fn severity(&self) -> Option<&str> {
match self {
Self::Message { severity, .. } => severity.as_deref(),
Self::CategoryAViolation { .. } => None,
}
}
/// Whether this finding was produced while bypassing the AST visitor
/// because of a parse error.
///
/// Parse-error fallback is a safety net: if a source file cannot be
/// parsed, the gate falls back to a text scan. A `parse_error` flag
/// tells the consumer that the finding may be a false positive caused
/// by macro-generated or syntactically unusual code.
#[inline]
pub fn parse_error(&self) -> bool {
match self {
Self::Message { parse_error, .. } => *parse_error,
Self::CategoryAViolation { .. } => false,
}
}
/// Set the severity level.
///
/// This is a builder-style setter for report generators that need to
/// annotate a finding after construction. It has no effect on
/// `CategoryAViolation` variants because those are always hard failures.
#[inline]
pub fn with_severity(mut self, severity: impl Into<String>) -> Self {
match &mut self {
Self::Message { severity: s, .. } => {
*s = Some(severity.into());
}
Self::CategoryAViolation { .. } => {}
}
self
}
/// Mark whether this finding was produced while bypassing the AST visitor
/// because of a parse error.
///
/// Use this to flag findings that came from the fallback text scanner.
/// It has no effect on `CategoryAViolation` variants.
#[inline]
pub fn with_parse_error(mut self, parse_error: bool) -> Self {
match &mut self {
Self::Message { parse_error: p, .. } => {
*p = parse_error;
}
Self::CategoryAViolation { .. } => {}
}
self
}
}
impl fmt::Display for CategoryFinding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Message {
op_id,
location: Some(location),
message,
..
} => {
write!(
f,
"{} declares Category B pattern at {}:{}: {}",
op_id, location.file, location.line, message
)
}
Self::Message {
op_id,
location: None,
message,
..
} => {
write!(f, "Op {} {}", op_id, message)
}
Self::CategoryAViolation {
op_id,
kind,
location,
message,
} => {
write!(
f,
"Op {} declares Category A violation ({}) at {}:{}: {}",
op_id, kind, location.file, location.line, message
)
}
}
}
}