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
//! KH-GAP-004: Inline `#[cfg(test)]` modules in `src/` violate the
//! Santh folder contract - all micro gates live under `tests/unit/`.
//!
//! ALLOWLIST: a SMALL, explicit set of crate-private modules whose tests are
//! white-box - they assert on private fields / call `pub(crate)` items whose
//! types are deliberately NOT part of the crate's public API. Migrating those
//! tests to an external `tests/` file would force exposing the internals as
//! `pub` purely to satisfy this lint, a worse trade (Law 1 / minimal public
//! surface) than keeping the white-box tests co-located with the code they pin.
//! Every entry is reviewed and must STAY a real offender: a stale entry (file
//! removed, or its tests since migrated) fails this gate loudly, so the
//! exception can never silently outlive the reason it was granted.
use std::path::{Path, PathBuf};
#[path = "inline_gate.rs"]
mod inline_gate;
/// Crate-private modules permitted to keep co-located `#[cfg(test)]` white-box
/// tests. Paths are relative to `src/`. Keep this list SHORT and justified.
const INLINE_TEST_ALLOWLIST: &[&str] = &[
// Hardware-probe tests need the crate-private backend override hook and
// live under a `testing` facade, not as hidden production behavior.
"hw_probe/mod.rs",
// `telemetry.rs` keeps a `#[cfg(test)] #[doc(hidden)] pub mod testing` facade
// (`reset`/`decode_truncation_count`) for integration tests that assert visible
// counters, plus test-only thread-local coverage-gap helpers. Its actual test
// MODULE was migrated to `tests/unit/telemetry_serial.rs`; this facade module
// legitimately remains in-crate (exposing it as production API would leak the
// mutable global telemetry internals), so the general gate allowlists the file.
"telemetry.rs",
// `testing.rs` is the intentionally doc-hidden scanner test facade used by
// external integration targets. Its `#[cfg(test)]` sections are facade
// wiring, not source-local behavioral test suites.
"testing.rs",
// The detector-catalog helper `bundled_detector_ids` is a `#[cfg(test)]`
// `pub(crate)` corpus loader, deliberately not part of the crate's public API,
// so an external `tests/` target cannot reach it. Its co-located tests pin the
// memoized bundled catalog directly, instead of widening the surface to `pub`.
// (The former `validate_rule_detector_ids` rule-file id validator was removed
// by the DET-0 migration, no rule file carries a detector-id list anymore.)
"detector_catalog.rs",
// Sibling `suppression/shape/*` predicate modules with the SAME white-box
// justification as `canonical.rs`: each pins one `pub(crate)` shape predicate
// whose exact single-pass boundary is recall-load-bearing
// `looks_like_url_or_path_segment` (path), `looks_like_english_prose` (prose),
// `looks_like_public_artifact_reference_with_randomness` (public). They are
// crate-internal (not reachable from `tests/unit/`), so co-locating the
// boundary assertions is the same correct trade the shape family already made
// for `canonical.rs` rather than widening the suppression surface to `pub`.
"suppression/shape/path.rs",
"suppression/shape/prose.rs",
"suppression/shape/public.rs",
// The windowed-support tests pin the `pub(crate)` `absolute_offset`
// overflow-to-`None` and `absolute_line` saturation arithmetic that composes
// base+local coordinates for windowed reassembly. The helpers live behind a
// private `mod windowed_support`; the exact overflow/saturation boundary is a
// crate-internal invariant, co-located rather than widening the engine API.
"engine/windowed_support.rs",
// The isolated-entropy floor test is a DEDUP PARITY proof: it pins that the
// private `isolated_bare_entropy_threshold` reproduces the isolated site's
// exact per-band resolution (default→MIXED, ≤high→MIXED, non-finite→MIXED,
// >high→verbatim) after unifying onto the shared override owner. Parity
// proofs over a crate-private helper justify co-location.
"entropy/isolated.rs",
// NOTE: `entropy/plausibility.rs` was removed here, its inline `#[cfg(test)]`
// tests were migrated to `tests/unit/entropy.rs` (they now exercise the
// per-detector entropy-floor resolution through the PUBLIC
// `keyhog_core::detector_spec_by_id`, so no white-box access is needed). The
// gate's anti-staleness check (a stale allowlist entry is a hard failure)
// enforces that this list only names files that STILL hold inline tests.
// The suppression API tests exercise its crate-private typed contexts and
// stage results directly.
"suppression/api.rs",
// `engine/phase2/mark_stats.rs` exposes a `pub(crate)` telemetry facade
// (`record_mark_*`, `take_mark_stats`) over the profile runtime's typed
// counters. Same justification as `telemetry.rs`: keeping the record/read
// seam in-module avoids making the counter plumbing public API.
"engine/phase2/mark_stats.rs",
// `engine/scan_postprocess/fragments.rs` pins the private reassembly floors
// (`REASSEMBLY_MIN_ENTROPY` = 3.0, `REASSEMBLY_MIN_VALUE_LEN` = 16) and proves
// the no-hit reassembly path reuses the SINGLE `reassembly_probe_data` owner
// (a ONE-PLACE guard). Crate-internal, co-located with the owner.
"engine/scan_postprocess/fragments.rs",
];
/// True iff `path` ends with an allowlisted `src/`-relative path (component-wise,
/// so it is exact and cross-platform - never a loose substring match).
fn is_allowlisted(path: &Path) -> bool {
INLINE_TEST_ALLOWLIST
.iter()
.any(|rel| path.ends_with(Path::new(rel)))
}
fn scan_rust_sources(dir: &Path, offenders: &mut Vec<PathBuf>) {
let entries = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read_dir({}) failed: {e}", dir.display()));
for entry in entries {
let entry =
entry.unwrap_or_else(|e| panic!("read_dir({}) entry failed: {e}", dir.display()));
let path = entry.path();
if path.is_dir() {
scan_rust_sources(&path, offenders);
continue;
}
if path.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let content = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("read {} failed: {e}", path.display()));
let has_inline_test = inline_gate::contains_inline_test_module_or_function(&content);
if has_inline_test {
offenders.push(path);
}
}
}
#[test]
fn no_inline_tests_in_src() {
let src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
let mut offenders = Vec::new();
scan_rust_sources(&src_dir, &mut offenders);
offenders.sort();
let rel = |p: &Path| {
p.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(p)
.display()
.to_string()
};
// Disallowed offenders: every inline-test src file that is NOT allowlisted.
let disallowed: Vec<String> = offenders
.iter()
.filter(|p| !is_allowlisted(p))
.map(|p| rel(p))
.collect();
assert!(
disallowed.is_empty(),
"{} scanner/src files still contain #[cfg(test)] - migrate to tests/unit/:\n - {}",
disallowed.len(),
disallowed.join("\n - ")
);
// Stale-allowlist guard: each allowlist entry must still correspond to a real
// inline-test offender. If a file was removed or its tests migrated, the entry
// is dead and must be deleted - otherwise it would silently exempt a future
// file at the same path (Law 9: no evasion; the exception must earn its place
// every run).
for entry in INLINE_TEST_ALLOWLIST {
assert!(
offenders.iter().any(|p| p.ends_with(Path::new(entry))),
"stale INLINE_TEST_ALLOWLIST entry `{entry}`: it no longer contains an inline \
#[cfg(test)] (file moved or tests migrated) - remove it from the allowlist",
);
}
}