mod support;
use support::contracts::{
load_contracts, make_chunk, primaries, scanner, scanner_with, sufficiency_mask, surfaces,
Primary,
};
use std::collections::BTreeMap;
use keyhog_core::ScanConfig;
const SOURCE_TYPE: &str = "comment-embed";
#[derive(Debug, Clone, Copy)]
enum Comment {
HashLine, SlashSlash, SlashStarBlock, HtmlBlock, SemiLine, DashDashLine, PercentLine, }
impl Comment {
const ALL: &'static [Comment] = &[
Comment::HashLine,
Comment::SlashSlash,
Comment::SlashStarBlock,
Comment::HtmlBlock,
Comment::SemiLine,
Comment::DashDashLine,
Comment::PercentLine,
];
fn label(self) -> &'static str {
match self {
Comment::HashLine => "hash-line",
Comment::SlashSlash => "slash-slash",
Comment::SlashStarBlock => "slash-star-block",
Comment::HtmlBlock => "html-block",
Comment::SemiLine => "semi-line",
Comment::DashDashLine => "dash-dash-line",
Comment::PercentLine => "percent-line",
}
}
fn wrap(self, text: &str) -> String {
let line_prefix = |prefix: &str| {
text.lines()
.map(|l| format!("{prefix} {l}"))
.collect::<Vec<_>>()
.join("\n")
};
match self {
Comment::HashLine => line_prefix("#"),
Comment::SlashSlash => line_prefix("//"),
Comment::SlashStarBlock => format!("/* {text} */"),
Comment::HtmlBlock => format!("<!-- {text} -->"),
Comment::SemiLine => line_prefix(";"),
Comment::DashDashLine => line_prefix("--"),
Comment::PercentLine => line_prefix("%"),
}
}
}
#[test]
fn scan_comments_toggle_is_wired_and_monotone() {
let default_scanner = scanner();
let enabled_scanner = scanner_with(ScanConfig {
scan_comments: true,
..ScanConfig::default()
});
let contracts = load_contracts();
let primaries: Vec<Primary> = primaries(&contracts);
let sufficient = sufficiency_mask(&default_scanner, SOURCE_TYPE, &primaries);
let n_sufficient = sufficient.iter().filter(|b| **b).count();
let mut per_style: BTreeMap<&'static str, (usize, usize, usize)> = BTreeMap::new();
let mut monotonicity_violations: Vec<String> = Vec::new();
let mut flag_effect_cases = 0usize;
for (idx, p) in primaries.iter().enumerate() {
if !sufficient[idx] {
continue; }
for style in Comment::ALL {
let text = style.wrap(&p.text);
let chunk = make_chunk(&text, SOURCE_TYPE, "source.txt");
let default_hit = surfaces(&default_scanner, &chunk, &p.credential);
let enabled_hit = surfaces(&enabled_scanner, &chunk, &p.credential);
let bucket = per_style.entry(style.label()).or_insert((0, 0, 0));
bucket.2 += 1;
if default_hit {
bucket.0 += 1;
}
if enabled_hit {
bucket.1 += 1;
}
if default_hit && !enabled_hit {
monotonicity_violations.push(format!(
"{detector} :: style={style} :: credential {cred:?} surfaced with \
scan_comments=false but VANISHED with scan_comments=true",
detector = p.detector_id,
style = style.label(),
cred = p.credential,
));
}
if enabled_hit && !default_hit {
flag_effect_cases += 1;
}
}
}
let mut summary = format!(
"comment-embed scan_comments toggle: {n_sufficient}/{} primaries fire standalone; \
per-style credential-sufficient hits (default → enabled), informational:\n",
primaries.len(),
);
for (style, (d, e, runs)) in &per_style {
summary.push_str(&format!(" {style:<17} {d:>4} → {e:<4} / {runs}\n"));
}
summary.push_str(&format!(
" flag-effect cases (enabled surfaced what default suppressed): {flag_effect_cases}\n"
));
eprintln!("{summary}");
assert!(
monotonicity_violations.is_empty(),
"scan_comments monotonicity violated ({} cases): enabling --scan-comments REMOVED a \
comment finding it should only ever add, a suppression-ordering bug:\n - {}",
monotonicity_violations.len(),
monotonicity_violations.join("\n - "),
);
assert!(
flag_effect_cases > 0,
"scan_comments had ZERO effect across the entire credential-sufficient corpus in every \
comment style: no credential-sufficient secret that the default scanner suppressed in a \
comment was surfaced by scan_comments=true. The flag is dead-wired (CLAUDE.md WIRING / \
Law 11), either the config no longer reaches the Comment-context penalty, or comment \
context detection broke."
);
}