use crate::contract_lint::{AssertionLint, AssertionLintOutcome, ContractLintReport};
use crate::gate::{
ArtefactRef, Gate, GateKind, GateOutcome, GatePipeline, GateReport, GateVerdict,
};
use crate::types::{Assertion, AssertionCheck};
use std::path::{Path, PathBuf};
pub const VACUOUS_FILTER: &str = "vacuous-filter";
pub const WRONG_POLARITY: &str = "wrong-polarity";
pub const PASSES_ON_BASE: &str = "passes-on-base";
pub const ENV_SENSITIVE: &str = "env-sensitive";
#[derive(Debug, Clone)]
struct CommandCheck {
id: String,
command: String,
}
fn command_checks(contract: &[Assertion]) -> Vec<CommandCheck> {
contract
.iter()
.filter(|a| a.check == AssertionCheck::Command)
.filter_map(|a| {
a.command.as_deref().map(|command| CommandCheck {
id: a.id.clone(),
command: command.to_string(),
})
})
.collect()
}
pub fn contract_gate_reports(
contract: &[Assertion],
lint: Option<&ContractLintReport>,
tree_root: &Path,
) -> Vec<GateReport> {
let mut pipeline = GatePipeline::new();
register_contract_gates(&mut pipeline, contract, lint, tree_root);
pipeline.evaluate()
}
pub fn register_contract_gates(
pipeline: &mut GatePipeline,
contract: &[Assertion],
lint: Option<&ContractLintReport>,
tree_root: &Path,
) {
let checks = command_checks(contract);
if checks.is_empty() {
return;
}
pipeline
.register(Box::new(VacuousFilterGate {
checks: checks.clone(),
repo_root: tree_root.to_path_buf(),
filter_collision: lint.is_some(),
}))
.register(Box::new(WrongPolarityGate {
checks: checks.clone(),
tree_root: tree_root.to_path_buf(),
}));
if let Some(lint) = lint {
pipeline.register(Box::new(PassesOnBaseGate {
results: lint.results.clone(),
}));
}
pipeline.register(Box::new(EnvSensitiveGate { checks }));
}
pub fn failed_gate_names(reports: &[GateReport]) -> Vec<&str> {
reports
.iter()
.filter(|r| !r.outcome.passed())
.map(|r| r.name.as_str())
.collect()
}
pub fn render_gate_verdicts(reports: &[GateReport]) -> String {
render_verdict_block("named contract gates (defect classes):", reports)
}
pub fn render_verdict_block(header: &str, reports: &[GateReport]) -> String {
let mut out = String::from(header);
for report in reports {
let verdict = match report.outcome.verdict {
GateVerdict::Pass => "PASS",
GateVerdict::Fail => "FAIL",
};
out.push_str(&format!("\n- {}: {verdict}", report.name));
if let Some(detail) = &report.outcome.artefact.detail {
for line in detail.lines() {
out.push_str(&format!("\n {line}"));
}
}
}
out
}
fn outcome_from_findings(name: &str, findings: Vec<String>) -> GateOutcome {
let artefact = ArtefactRef::new(format!("contract gate {name}"));
if findings.is_empty() {
GateOutcome::pass(artefact)
} else {
GateOutcome::fail(artefact.with_detail(findings.join("\n")))
}
}
struct VacuousFilterGate {
checks: Vec<CommandCheck>,
repo_root: PathBuf,
filter_collision: bool,
}
impl Gate for VacuousFilterGate {
fn name(&self) -> &str {
VACUOUS_FILTER
}
fn kind(&self) -> GateKind {
GateKind::Deterministic
}
fn evaluate(&self) -> GateOutcome {
let analysis =
vacuous_filter_analysis(&self.checks, &self.repo_root, self.filter_collision);
let score = analysis.score();
let mut outcome = outcome_from_findings(VACUOUS_FILTER, analysis.findings);
if let Some(score) = score {
outcome = outcome.with_score(score.score, score.threshold);
}
outcome
}
}
const VACUOUS_FILTER_SCORE_THRESHOLD: f64 = 1.0;
struct VacuousFilterAnalysis {
findings: Vec<String>,
determined: u64,
undetermined: u64,
}
impl VacuousFilterAnalysis {
fn score(&self) -> Option<crate::gate::GateScore> {
let total = self.determined + self.undetermined;
(total > 0).then(|| crate::gate::GateScore {
score: self.determined as f64 / total as f64,
threshold: VACUOUS_FILTER_SCORE_THRESHOLD,
})
}
}
fn vacuous_filter_analysis(
checks: &[CommandCheck],
repo_root: &Path,
filter_collision: bool,
) -> VacuousFilterAnalysis {
let mut findings = Vec::new();
let mut determined = 0u64;
let mut undetermined = 0u64;
for check in checks {
let words = lex(&check.command);
let segments = segments(&words);
if segments.iter().any(|s| is_test_runner(&s.words)) {
for segment in &segments {
let Some(args) = grep_invocation_args(&segment.words) else {
continue;
};
let Some(patterns) = grep_patterns(args) else {
undetermined += 1;
continue;
};
determined += 1;
if !patterns.iter().any(|p| p.contains("[1-9]")) {
findings.push(format!(
"[{}] test-runner pipeline's grep anchors no nonzero count \
(missing the `[1-9]` guard — zero matching tests still print \
`test result: ok.`): `{}`",
check.id, check.command
));
}
}
}
if !filter_collision {
continue;
}
for segment in &segments {
if let Some(filter) = cargo_test_filter(&segment.words) {
determined += 1;
if let Some(site) = find_test_name_collision(repo_root, &filter) {
findings.push(format!(
"[{}] cargo test filter `{filter}` collides with an existing \
test at {site} — the gate can pass on pre-existing tests with \
zero implementation; the filter must match ONLY the \
not-yet-written tests: `{}`",
check.id, check.command
));
}
}
}
}
VacuousFilterAnalysis {
findings,
determined,
undetermined,
}
}
fn is_test_runner(words: &[Word]) -> bool {
let Some(first) = words.first() else {
return false;
};
match first.text.as_str() {
"cargo" => {
let rest: Vec<&str> = words[1..].iter().map(|w| w.text.as_str()).collect();
rest.first() == Some(&"test")
|| (rest.first() == Some(&"nextest") && rest.get(1) == Some(&"run"))
}
"pytest" | "py.test" => true,
"go" => words.get(1).is_some_and(|w| w.text == "test"),
"npm" | "yarn" | "pnpm" => {
let rest: Vec<&str> = words[1..].iter().map(|w| w.text.as_str()).collect();
rest.first() == Some(&"test")
|| (rest.first() == Some(&"run") && rest.get(1) == Some(&"test"))
}
_ => false,
}
}
fn cargo_test_filter(words: &[Word]) -> Option<String> {
if words.first().map(|w| w.text.as_str()) != Some("cargo") {
return None;
}
if words.get(1).map(|w| w.text.as_str()) != Some("test") {
return None;
}
const VALUE_FLAGS: &[&str] = &[
"-p",
"--package",
"--test",
"--bench",
"--bin",
"--example",
"--features",
"--exclude",
"--config",
"--target",
"--profile",
"-j",
"--jobs",
"--manifest-path",
"--message-format",
"--target-dir",
];
let mut iter = words[2..].iter().peekable();
while let Some(word) = iter.next() {
let text = word.text.as_str();
if text == "--" {
continue;
}
if text.contains('>') || text.contains('<') {
continue;
}
if let Some(flag) = text.strip_prefix('-') {
if !flag.is_empty() && !text.contains('=') && VALUE_FLAGS.contains(&text) {
iter.next();
}
continue;
}
return Some(text.to_string());
}
None
}
fn find_test_name_collision(repo_root: &Path, filter: &str) -> Option<String> {
let mut files = Vec::new();
collect_rs_files(repo_root, &mut files);
for file in files {
let Ok(body) = std::fs::read_to_string(&file) else {
continue;
};
let lines: Vec<&str> = body.lines().collect();
for (i, line) in lines.iter().enumerate() {
let Some(ident) = fn_ident_after(line, "fn ") else {
continue;
};
if !ident.contains(filter) {
continue;
}
let window_start = i.saturating_sub(3);
let is_test = lines[window_start..i].iter().any(|l| {
let l = l.trim_start();
l.starts_with("#[") && l.contains("test")
});
if is_test {
let rel = file.strip_prefix(repo_root).unwrap_or(&file);
return Some(format!("{}:{} (`fn {ident}`)", rel.display(), i + 1));
}
}
}
None
}
fn fn_ident_after<'a>(line: &'a str, needle: &str) -> Option<&'a str> {
let start = line.find(needle)? + needle.len();
let rest = &line[start..];
let end = rest
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.unwrap_or(rest.len());
(end > 0).then(|| &rest[..end])
}
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
let mut entries: Vec<_> = entries.flatten().collect();
entries.sort_by_key(|e| e.file_name());
for entry in entries {
let path = entry.path();
let Ok(meta) = entry.metadata() else {
continue;
};
if meta.is_dir() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name == "target" || name == "node_modules" || name.starts_with('.') {
continue;
}
collect_rs_files(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
struct WrongPolarityGate {
checks: Vec<CommandCheck>,
tree_root: PathBuf,
}
impl Gate for WrongPolarityGate {
fn name(&self) -> &str {
WRONG_POLARITY
}
fn kind(&self) -> GateKind {
GateKind::Deterministic
}
fn evaluate(&self) -> GateOutcome {
outcome_from_findings(
WRONG_POLARITY,
wrong_polarity_findings(&self.checks, &self.tree_root),
)
}
}
fn wrong_polarity_findings(checks: &[CommandCheck], tree_root: &Path) -> Vec<String> {
let mut findings = Vec::new();
for check in checks {
let words = lex(&check.command);
let segs = segments(&words);
for (i, segment) in segs.iter().enumerate() {
let mut negated = false;
let mut seg_words: Vec<Word> = segment.words.clone();
if let Some(first) = seg_words.first() {
if first.text == "!" {
negated = true;
seg_words.remove(0);
} else if let Some(rest) = first.text.strip_prefix('!') {
if !rest.is_empty() {
negated = true;
seg_words[0] = Word {
text: rest.to_string(),
quote: first.quote.clone(),
};
}
}
}
let Some(args) = grep_invocation_args(&seg_words) else {
continue;
};
let or_true = segment.op_after.as_deref() == Some("||")
&& segs
.get(i + 1)
.is_some_and(|next| next.words.len() == 1 && next.words[0].text == "true");
if !negated && !or_true {
continue;
}
let Some(paths) = grep_target_paths(args) else {
continue;
};
for path in paths {
if path.chars().any(|c| "*?[]$~`(".contains(c)) {
continue;
}
let resolved = if Path::new(&path).is_absolute() {
PathBuf::from(&path)
} else {
tree_root.join(&path)
};
if !resolved.exists() {
let form = if negated {
"negated"
} else {
"`|| true`-neutralized"
};
findings.push(format!(
"[{}] {form} grep targets missing path `{path}` — the assertion \
passes because the target is absent, not because the property \
holds: `{}`",
check.id, check.command
));
}
}
}
}
findings
}
struct PassesOnBaseGate {
results: Vec<AssertionLint>,
}
impl Gate for PassesOnBaseGate {
fn name(&self) -> &str {
PASSES_ON_BASE
}
fn kind(&self) -> GateKind {
GateKind::Deterministic
}
fn evaluate(&self) -> GateOutcome {
let findings = self
.results
.iter()
.filter(|r| r.outcome == AssertionLintOutcome::PassedOnBase)
.map(|r| {
format!(
"[{}] already exits zero on the untouched base tree — a \
correctly-scoped \"the work landed\" assertion must FAIL \
before the work lands: `{}`",
r.id, r.command
)
})
.collect();
outcome_from_findings(PASSES_ON_BASE, findings)
}
}
struct EnvSensitiveGate {
checks: Vec<CommandCheck>,
}
impl Gate for EnvSensitiveGate {
fn name(&self) -> &str {
ENV_SENSITIVE
}
fn kind(&self) -> GateKind {
GateKind::Deterministic
}
fn evaluate(&self) -> GateOutcome {
outcome_from_findings(ENV_SENSITIVE, env_sensitive_findings(&self.checks))
}
}
fn env_sensitive_findings(checks: &[CommandCheck]) -> Vec<String> {
const COMPARISONS: &[&str] = &["-lt", "-gt", "-le", "-ge", "-eq", "-ne", "==", "!="];
let mut findings = Vec::new();
for check in checks {
let words = lex(&check.command);
let mut reasons: Vec<&str> = Vec::new();
if words.iter().any(|w| {
w.quote != Quote::Single && (w.text.contains("$HOME") || w.text.contains("${HOME}"))
}) {
reasons.push(
"references $HOME (a per-run scratch HOME makes the verdict environment-dependent)",
);
}
if words
.iter()
.any(|w| w.quote == Quote::None && (w.text.starts_with('~') || w.text.contains("=~/")))
{
reasons.push("references `~` (tilde expands against the runner's HOME, not the tree)");
}
if words.iter().any(|w| {
w.quote != Quote::Single && (w.text.contains("/Users/") || w.text.starts_with("/home/"))
}) {
reasons.push("references an absolute user path (/Users/… or /home/…)");
}
let has_date = words.iter().any(|w| invocation_word(&w.text) == "date");
let has_comparison = words.iter().any(|w| COMPARISONS.contains(&w.text.as_str()));
if has_date && has_comparison {
reasons.push("compares wall-clock `date` output (verdict drifts with the clock)");
}
let segs = segments(&words);
for segment in &segs {
if let Some(first) = segment.words.first() {
let invoked = invocation_word(&first.text);
if invoked == "curl" || invoked == "wget" {
reasons.push("invokes an unproxied network tool (`curl`/`wget`) — the verdict depends on the network, not the tree");
break;
}
}
}
for reason in reasons {
findings.push(format!("[{}] {reason}: `{}`", check.id, check.command));
}
}
findings
}
fn invocation_word(text: &str) -> &str {
text.trim_start_matches("$(")
.trim_start_matches('!')
.trim_matches('`')
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Quote {
None,
Single,
Double,
}
#[derive(Debug, Clone)]
struct Word {
text: String,
quote: Quote,
}
fn lex(command: &str) -> Vec<Word> {
let chars: Vec<char> = command.chars().collect();
let mut words = Vec::new();
let mut text = String::new();
let mut quote = Quote::None;
let mut word_quote = Quote::None;
let mut i = 0;
fn flush(words: &mut Vec<Word>, text: &mut String, word_quote: &mut Quote) {
if !text.is_empty() {
words.push(Word {
text: std::mem::take(text),
quote: std::mem::replace(word_quote, Quote::None),
});
}
}
while i < chars.len() {
let c = chars[i];
match c {
'\'' if quote != Quote::Double => {
if quote == Quote::Single {
quote = Quote::None;
} else {
quote = Quote::Single;
}
i += 1;
}
'"' if quote != Quote::Single => {
if quote == Quote::Double {
quote = Quote::None;
} else {
quote = Quote::Double;
}
i += 1;
}
'\\' if quote != Quote::Single => {
if let Some(next) = chars.get(i + 1) {
if word_quote == Quote::None {
word_quote = quote.clone();
}
text.push(*next);
i += 2;
} else {
i += 1;
}
}
c if c.is_whitespace() && quote == Quote::None => {
flush(&mut words, &mut text, &mut word_quote);
i += 1;
}
'|' if quote == Quote::None => {
flush(&mut words, &mut text, &mut word_quote);
if chars.get(i + 1) == Some(&'|') {
words.push(Word {
text: "||".to_string(),
quote: Quote::None,
});
i += 2;
} else {
words.push(Word {
text: "|".to_string(),
quote: Quote::None,
});
i += 1;
}
}
'&' if quote == Quote::None && chars.get(i + 1) == Some(&'&') => {
flush(&mut words, &mut text, &mut word_quote);
words.push(Word {
text: "&&".to_string(),
quote: Quote::None,
});
i += 2;
}
';' if quote == Quote::None => {
flush(&mut words, &mut text, &mut word_quote);
words.push(Word {
text: ";".to_string(),
quote: Quote::None,
});
i += 1;
}
c => {
if word_quote == Quote::None {
word_quote = quote.clone();
}
text.push(c);
i += 1;
}
}
}
flush(&mut words, &mut text, &mut word_quote);
words
}
struct Segment {
words: Vec<Word>,
op_after: Option<String>,
}
fn segments(words: &[Word]) -> Vec<Segment> {
let mut out = Vec::new();
let mut current: Vec<Word> = Vec::new();
let mut close = |current: &mut Vec<Word>, op: Option<String>| {
if !current.is_empty() {
out.push(Segment {
words: std::mem::take(current),
op_after: op,
});
}
};
for word in words {
match word.text.as_str() {
"|" | "||" | "&&" | ";" if word.quote == Quote::None => {
close(&mut current, Some(word.text.clone()));
}
_ => current.push(word.clone()),
}
}
close(&mut current, None);
out
}
fn grep_invocation_args(words: &[Word]) -> Option<&[Word]> {
let first = words.first()?;
matches!(first.text.as_str(), "grep" | "egrep" | "fgrep").then(|| &words[1..])
}
const GREP_LONG_VALUE_FLAGS: &[&str] = &[
"--context",
"--after-context",
"--before-context",
"--max-count",
"--include",
"--exclude",
"--exclude-dir",
"--label",
"--binary-files",
];
fn grep_patterns(args: &[Word]) -> Option<Vec<String>> {
let mut patterns = Vec::new();
let mut i = 0;
while i < args.len() {
let text = args[i].text.as_str();
if text == "-e" || text == "--regexp" {
patterns.push(args.get(i + 1)?.text.clone());
i += 2;
continue;
}
if let Some(rest) = text.strip_prefix("--regexp=") {
patterns.push(rest.to_string());
i += 1;
continue;
}
if text.starts_with('-') && !text.starts_with("--") && text.len() > 1 {
let flags: Vec<char> = text[1..].chars().collect();
if let Some(pos) = flags.iter().position(|c| *c == 'e') {
if pos + 1 < flags.len() {
patterns.push(flags[pos + 1..].iter().collect());
i += 1;
} else {
patterns.push(args.get(i + 1)?.text.clone());
i += 2;
}
continue;
}
if flags.contains(&'f') {
return None;
}
i += 1;
continue;
}
if text.starts_with("--") {
if GREP_LONG_VALUE_FLAGS.contains(&text) {
i += 2;
} else {
i += 1;
}
continue;
}
patterns.push(text.to_string());
break;
}
(!patterns.is_empty()).then_some(patterns)
}
fn grep_target_paths(args: &[Word]) -> Option<Vec<String>> {
let mut paths = Vec::new();
let mut i = 0;
let mut pattern_consumed = false;
while i < args.len() {
let text = args[i].text.as_str();
if text == "-e" || text == "--regexp" {
pattern_consumed = true;
i += 2;
continue;
}
if text.starts_with("--regexp=") {
pattern_consumed = true;
i += 1;
continue;
}
if text.starts_with('-') && !text.starts_with("--") && text.len() > 1 {
let flags: Vec<char> = text[1..].chars().collect();
if let Some(pos) = flags.iter().position(|c| *c == 'e') {
pattern_consumed = true;
if pos + 1 == flags.len() {
i += 2;
} else {
i += 1;
}
continue;
}
if flags.contains(&'f') {
return None;
}
i += 1;
continue;
}
if text.starts_with("--") {
if GREP_LONG_VALUE_FLAGS.contains(&text) {
i += 2;
} else {
i += 1;
}
continue;
}
if !pattern_consumed {
pattern_consumed = true;
} else {
paths.push(text.to_string());
}
i += 1;
}
Some(paths)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::contract_lint::AssertionLint;
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("crates/engine has a workspace root two levels up")
.to_path_buf()
}
fn check(id: &str, command: &str) -> CommandCheck {
CommandCheck {
id: id.to_string(),
command: command.to_string(),
}
}
fn command_assertion(id: &str, command: &str) -> Assertion {
Assertion {
id: id.to_string(),
statement: format!("statement for {id}"),
check: AssertionCheck::Command,
command: Some(command.to_string()),
negative_control: None,
pty_script: None,
}
}
fn lint_result(id: &str, command: &str, outcome: AssertionLintOutcome) -> AssertionLint {
AssertionLint {
id: id.to_string(),
command: command.to_string(),
outcome,
output_tail: String::new(),
}
}
#[test]
fn contract_gate_vacuous_filter_catches_unanchored_test_result_grep() {
let checks = vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\.'",
)];
let findings = vacuous_filter_analysis(&checks, &workspace_root(), true).findings;
assert_eq!(findings.len(), 1, "{findings:?}");
assert!(findings[0].contains("[a3]"), "{findings:?}");
assert!(findings[0].contains("[1-9]"), "{findings:?}");
}
#[test]
fn contract_gate_vacuous_filter_passes_anchored_nonzero_grep() {
let checks = vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)];
let findings = vacuous_filter_analysis(&checks, &workspace_root(), true).findings;
assert!(findings.is_empty(), "{findings:?}");
}
#[test]
fn contract_gate_vacuous_filter_catches_filter_colliding_with_existing_test() {
let checks = vec![check(
"a3",
"cargo test --workspace approval_lint_ 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)];
let findings = vacuous_filter_analysis(&checks, &workspace_root(), true).findings;
assert_eq!(findings.len(), 1, "{findings:?}");
assert!(findings[0].contains("collides"), "{findings:?}");
assert!(findings[0].contains("approval_lint_"), "{findings:?}");
}
#[test]
fn contract_gate_vacuous_filter_passes_fresh_filter() {
let checks = vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)];
let findings = vacuous_filter_analysis(&checks, &workspace_root(), true).findings;
assert!(findings.is_empty(), "{findings:?}");
}
#[test]
fn contract_gate_vacuous_filter_extracts_filter_past_flags_and_redirects() {
let words = lex("cargo test --workspace approval_lint_ 2>&1 | grep -q x");
let segs = segments(&words);
assert_eq!(
cargo_test_filter(&segs[0].words).as_deref(),
Some("approval_lint_")
);
let words = lex("cargo test --workspace");
let segs = segments(&words);
assert_eq!(cargo_test_filter(&segs[0].words), None);
}
#[test]
fn contract_gate_vacuous_filter_skips_collision_at_final_gate_phase() {
let colliding = vec![check(
"a3",
"cargo test --workspace approval_lint_ 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)];
let findings = vacuous_filter_analysis(&colliding, &workspace_root(), false).findings;
assert!(findings.is_empty(), "{findings:?}");
let unanchored = vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\.'",
)];
let findings = vacuous_filter_analysis(&unanchored, &workspace_root(), false).findings;
assert_eq!(findings.len(), 1, "{findings:?}");
}
#[test]
fn contract_gate_vacuous_filter_gate_score_series_scores_full_coverage() {
let gate = VacuousFilterGate {
checks: vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)],
repo_root: workspace_root(),
filter_collision: true,
};
let outcome = gate.evaluate();
assert!(outcome.passed(), "{outcome:?}");
let score = outcome
.score
.expect("graded surface exists — score attached");
assert_eq!(score.score, 1.0);
assert_eq!(score.threshold, 1.0);
}
#[test]
fn contract_gate_vacuous_filter_gate_score_series_undetermined_grep_lowers_coverage() {
let gate = VacuousFilterGate {
checks: vec![check(
"a3",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qf gate-score-patterns.txt",
)],
repo_root: workspace_root(),
filter_collision: true,
};
let outcome = gate.evaluate();
assert!(outcome.passed(), "{outcome:?}");
let score = outcome
.score
.expect("graded surface exists — score attached");
assert_eq!(score.score, 0.5);
assert_eq!(score.threshold, 1.0);
}
#[test]
fn contract_gate_vacuous_filter_gate_score_series_absent_without_graded_surface() {
let gate = VacuousFilterGate {
checks: vec![
check("a1", "true"),
check("a2", "grep -q marker src/lib.rs"),
],
repo_root: workspace_root(),
filter_collision: true,
};
let outcome = gate.evaluate();
assert!(outcome.passed(), "{outcome:?}");
assert_eq!(outcome.score, None);
}
#[test]
fn contract_gate_vacuous_filter_gate_score_series_other_floor_gates_stay_boolean() {
let contract = vec![command_assertion(
"a1",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -qE 'test result: ok\\. [1-9]'",
)];
let lint = ContractLintReport {
results: vec![lint_result(
"a1",
"cargo test …",
AssertionLintOutcome::FailedOnBase,
)],
tree_clean_at_base: true,
};
let reports = contract_gate_reports(&contract, Some(&lint), &workspace_root());
assert_eq!(reports.len(), 4, "{reports:?}");
for report in reports {
if report.name == VACUOUS_FILTER {
assert!(report.outcome.score.is_some(), "the scored gate scores");
} else {
assert_eq!(
report.outcome.score, None,
"{} stays boolean-only",
report.name
);
}
}
}
#[test]
fn contract_gate_wrong_polarity_catches_negated_grep_on_missing_path() {
let dir = std::env::temp_dir().join(format!("kranz-cg-wp-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let checks = vec![
check("a1", "! grep -q landed-marker no/such/file.txt"),
check("a2", "grep -q landed-marker no/such/file.txt || true"),
];
let findings = wrong_polarity_findings(&checks, &dir);
assert_eq!(findings.len(), 2, "{findings:?}");
assert!(findings[0].contains("[a1]"), "{findings:?}");
assert!(findings[0].contains("negated"), "{findings:?}");
assert!(findings[1].contains("[a2]"), "{findings:?}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn contract_gate_wrong_polarity_passes_examined_or_undeterminable_targets() {
let dir = std::env::temp_dir().join(format!("kranz-cg-wp2-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("present.txt"), "contents\n").unwrap();
let checks = vec![
check("a1", "! grep -q forbidden-marker present.txt"),
check("a2", "grep -q landed-marker no/such/file.txt"),
check("a3", "! grep -q marker no/such/*.txt"),
check("a4", "cargo build 2>&1 | grep -q warning"),
];
let findings = wrong_polarity_findings(&checks, &dir);
assert!(findings.is_empty(), "{findings:?}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn contract_gate_passes_on_base_fails_only_on_base_pass() {
let gate = PassesOnBaseGate {
results: vec![
lint_result("a1", "true", AssertionLintOutcome::PassedOnBase),
lint_result("a2", "false", AssertionLintOutcome::FailedOnBase),
lint_result("a3", "sleep 99", AssertionLintOutcome::CouldNotVerdict),
],
};
let outcome = gate.evaluate();
assert!(!outcome.passed());
let detail = outcome.artefact.detail.expect("fail carries findings");
assert!(detail.contains("[a1]"), "{detail}");
assert!(detail.contains("untouched base tree"), "{detail}");
assert!(!detail.contains("[a2]"), "{detail}");
assert!(!detail.contains("[a3]"), "{detail}");
let clean = PassesOnBaseGate {
results: vec![lint_result(
"a2",
"false",
AssertionLintOutcome::FailedOnBase,
)],
};
assert!(clean.evaluate().passed());
}
#[test]
fn contract_gate_env_sensitive_catches_home_tilde_userpath_date_network() {
let checks = vec![
check("a1", "cat $HOME/.config/tool.toml | grep -q enabled"),
check("a2", "grep -q marker ~/output.txt"),
check("a3", "/Users/alice/bin/tool --check"),
check("a4", "test $(date +%s) -gt 1700000000"),
check("a5", "curl -fsS https://example.com/health | grep -q ok"),
];
let findings = env_sensitive_findings(&checks);
assert_eq!(findings.len(), 5, "{findings:?}");
assert!(findings.iter().any(|f| f.contains("$HOME")), "{findings:?}");
assert!(findings.iter().any(|f| f.contains("~")), "{findings:?}");
assert!(
findings.iter().any(|f| f.contains("absolute user path")),
"{findings:?}"
);
assert!(
findings.iter().any(|f| f.contains("wall-clock")),
"{findings:?}"
);
assert!(
findings.iter().any(|f| f.contains("network tool")),
"{findings:?}"
);
}
#[test]
fn contract_gate_env_sensitive_passes_well_formed_commands() {
let checks = vec![
check("a1", "grep -q '$HOME' src/config.rs"),
check("a2", "grep -q 'curl' docs/api.md"),
check("a3", "date"),
check("a4", "cargo test --workspace"),
check("a5", "test -f .kranz/merge-gates.json"),
];
let findings = env_sensitive_findings(&checks);
assert!(findings.is_empty(), "{findings:?}");
}
#[test]
fn contract_gate_pipeline_names_classes_in_ticket_order() {
let contract = vec![
command_assertion(
"a1",
"cargo test --workspace zz_contract_gate_no_such_filter 2>&1 | grep -q 'test result: ok'",
),
command_assertion("a2", "true"),
];
let lint = ContractLintReport {
results: vec![
lint_result("a1", "cargo test …", AssertionLintOutcome::FailedOnBase),
lint_result("a2", "true", AssertionLintOutcome::PassedOnBase),
],
tree_clean_at_base: true,
};
let reports = contract_gate_reports(&contract, Some(&lint), &workspace_root());
let names: Vec<&str> = reports.iter().map(|r| r.name.as_str()).collect();
assert_eq!(
names,
vec![
VACUOUS_FILTER,
WRONG_POLARITY,
PASSES_ON_BASE,
ENV_SENSITIVE
]
);
assert!(reports.iter().all(|r| r.kind == GateKind::Deterministic));
let failed = failed_gate_names(&reports);
assert_eq!(failed, vec![VACUOUS_FILTER, PASSES_ON_BASE]);
let rendered = render_gate_verdicts(&reports);
assert!(rendered.contains("vacuous-filter: FAIL"), "{rendered}");
assert!(rendered.contains("wrong-polarity: PASS"), "{rendered}");
assert!(rendered.contains("passes-on-base: FAIL"), "{rendered}");
assert!(rendered.contains("env-sensitive: PASS"), "{rendered}");
let reports = contract_gate_reports(&contract, None, &workspace_root());
let names: Vec<&str> = reports.iter().map(|r| r.name.as_str()).collect();
assert_eq!(names, vec![VACUOUS_FILTER, WRONG_POLARITY, ENV_SENSITIVE]);
assert!(contract_gate_reports(&[], None, &workspace_root()).is_empty());
}
#[test]
fn contract_gate_well_formed_merge_gates_pass_all_static_gates() {
let root = workspace_root();
let bytes = std::fs::read(root.join(crate::merge_gate::MERGE_GATES_PATH))
.expect("repo merge-gates.json readable");
let suite = crate::merge_gate::parse_gate_suite(&bytes).expect("suite parses");
let contract: Vec<Assertion> = suite
.gates
.iter()
.enumerate()
.map(|(i, g)| command_assertion(&format!("g{}", i + 1), &g.command))
.collect();
let reports = contract_gate_reports(&contract, None, &root);
assert_eq!(reports.len(), 3, "{reports:?}");
let failed = failed_gate_names(&reports);
assert!(
failed.is_empty(),
"repo merge gates must pass the static contract gates: {failed:?}\n{}",
render_gate_verdicts(&reports)
);
}
#[test]
fn contract_gate_existing_lint_fixtures_pass_static_gates() {
let root = workspace_root();
let contract = vec![
command_assertion("a1", "true"),
command_assertion("a2", "false"),
command_assertion("a3", "sleep 5"),
command_assertion("a6", r#"grep -L '^name = "tokio"' Cargo.lock"#),
command_assertion(
"a7",
"test -z \"$GH_TOKEN\" && env | grep -c hunter2-lint | grep -q '^0$'",
),
];
let reports = contract_gate_reports(&contract, None, &root);
let failed = failed_gate_names(&reports);
assert!(
failed.is_empty(),
"existing lint fixtures must pass the static gates: {failed:?}\n{}",
render_gate_verdicts(&reports)
);
}
}