use super::config::Thresholds;
pub const PSEUDO_PATHS: &[&str] = &[
"(repo-wide)",
"(degraded)",
"(skipped)",
"(change-set)",
"(diff-summary)",
"(none)",
];
#[must_use]
pub fn is_pseudo_path(path: &str) -> bool {
PSEUDO_PATHS.contains(&path)
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct GateViolation {
pub gate: String,
pub path: String,
pub actual: String,
pub threshold: String,
}
pub fn evaluate_clone_gate(
thresholds: &Thresholds,
db: &crate::facts::FactsDb,
) -> crate::Result<Vec<GateViolation>> {
if !thresholds.gates.disallow_clone_type_1 {
return Ok(Vec::new());
}
let mut stmt = db
.conn()
.prepare(
"SELECT COUNT(DISTINCT clone_group_id) FROM clones \
WHERE similarity = 1.0",
)
.map_err(|e| crate::CodeLoreError::Analysis(format!("prepare clone-gate: {e}")))?;
let count: i64 = stmt
.query_row([], |r| r.get(0))
.map_err(|e| crate::CodeLoreError::Analysis(format!("query clone-gate: {e}")))?;
if count == 0 {
return Ok(Vec::new());
}
Ok(vec![GateViolation {
gate: "disallow_clone_type_1".into(),
path: "(repo-wide)".into(),
actual: count.to_string(),
threshold: "0".into(),
}])
}
pub fn evaluate_architecture_gate(
thresholds: &Thresholds,
db: &crate::facts::FactsDb,
) -> crate::Result<Vec<GateViolation>> {
Ok(evaluate_architecture_gate_measured(thresholds, db)?.0)
}
#[derive(Debug, Clone, Copy)]
pub struct ArchMeasured {
pub cycle_count: u32,
pub propagation_cost: f64,
}
pub fn evaluate_architecture_gate_measured(
thresholds: &Thresholds,
db: &crate::facts::FactsDb,
) -> crate::Result<(Vec<GateViolation>, Option<ArchMeasured>)> {
let g = &thresholds.gates;
if g.max_dependency_cycles.is_none() && g.max_propagation_cost.is_none() {
return Ok((Vec::new(), None));
}
let graph = crate::analyses::import_graph::build_import_graph(db)?;
let m = crate::analyses::import_graph::graph_metrics(&graph);
let mut out = Vec::new();
if let Some(max) = g.max_dependency_cycles
&& m.cycle_count > max
{
out.push(GateViolation {
gate: "max_dependency_cycles".into(),
path: "(repo-wide)".into(),
actual: m.cycle_count.to_string(),
threshold: max.to_string(),
});
}
if let Some(max) = g.max_propagation_cost
&& m.propagation_cost > max
{
out.push(GateViolation {
gate: "max_propagation_cost".into(),
path: "(repo-wide)".into(),
actual: format!("{:.4}", m.propagation_cost),
threshold: format!("{max:.4}"),
});
}
Ok((
out,
Some(ArchMeasured {
cycle_count: m.cycle_count,
propagation_cost: m.propagation_cost,
}),
))
}
#[must_use]
pub fn evaluate_diff_gate(
thresholds: &Thresholds,
new_hotspot_count: u32,
delta_code_health: f64,
base_cycles: u32,
head_cycles: u32,
delta_health_ratio: Option<f64>,
delta_health_verdict: Option<&str>,
) -> Vec<GateViolation> {
let mut out = Vec::new();
let d = &thresholds.diff;
if let Some(max) = d.new_hotspot_max
&& new_hotspot_count > max
{
out.push(GateViolation {
gate: "new_hotspot_max".into(),
path: "(diff-summary)".into(),
actual: new_hotspot_count.to_string(),
threshold: max.to_string(),
});
}
if let Some(min) = d.delta_code_health_min
&& delta_code_health < min
{
out.push(GateViolation {
gate: "delta_code_health_min".into(),
path: "(diff-summary)".into(),
actual: format!("{delta_code_health:+.2}"),
threshold: format!("{min:+.2}"),
});
}
if d.no_new_cycles && head_cycles > base_cycles {
out.push(GateViolation {
gate: "no_new_cycles".into(),
path: "(diff-summary)".into(),
actual: format!("{head_cycles} cycles (base {base_cycles})"),
threshold: format!("≤ {base_cycles}"),
});
}
if let Some(min) = d.delta_health_min
&& let Some(ratio) = delta_health_ratio
&& ratio < min
{
out.push(GateViolation {
gate: "delta_health_min".into(),
path: "(diff-summary)".into(),
actual: format!("{ratio:.1}"),
threshold: format!("\u{2265} {min:.1}"),
});
}
if d.deny_degrading_verdict && delta_health_verdict == Some("degrading") {
out.push(GateViolation {
gate: "deny_degrading_verdict".into(),
path: "(diff-summary)".into(),
actual: "degrading".into(),
threshold: "verdict != degrading".into(),
});
}
out
}
#[must_use]
pub fn verdict_from(measured: bool, violation_count: usize) -> &'static str {
if !measured {
"skipped"
} else if violation_count == 0 {
"passed"
} else {
"failed"
}
}
#[must_use]
pub fn diff_gate_verdict(
base_measured: bool,
head_measured: bool,
violation_count: usize,
) -> &'static str {
verdict_from(base_measured || head_measured, violation_count)
}
#[must_use]
pub fn evaluate_gate_thresholds(
thresholds: &Thresholds,
report: &crate::change_set::ChangeSetReport,
) -> Vec<GateViolation> {
let mut out = Vec::new();
let d = &thresholds.diff;
if let Some(min) = d.delta_code_health_min
&& let (Some(base), Some(projected)) = (
report.health.baseline_median,
report.health.projected_median,
)
{
let delta = projected - base;
if delta < min {
out.push(GateViolation {
gate: "delta_code_health_min".into(),
path: "(change-set)".into(),
actual: format!("{delta:+.2}"),
threshold: format!("{min:+.2}"),
});
}
}
if let Some(min) = d.delta_code_health_min_per_file {
for row in &report.health.deltas {
if let Some(delta) = row.delta
&& delta < min
{
out.push(GateViolation {
gate: "delta_code_health_min_per_file".into(),
path: row.path.clone(),
actual: format!("{delta:+.2}"),
threshold: format!("{min:+.2}"),
});
}
}
}
if let Some(min) = d.new_file_health_min {
for row in &report.health.deltas {
if row.reason.as_deref() == Some(crate::change_set::REASON_NEW_FILE)
&& let Some(score) = row.projected_score
&& score < min
{
out.push(GateViolation {
gate: "new_file_health_min".into(),
path: row.path.clone(),
actual: format!("{score:.1}"),
threshold: format!("{min:.1}"),
});
}
}
}
if d.no_new_cycles {
for path in &report.newly_cyclic_paths {
out.push(GateViolation {
gate: "no_new_cycles".into(),
path: path.clone(),
actual: "newly cyclic".into(),
threshold: "no new cycles".into(),
});
}
}
out
}
#[must_use]
pub fn change_set_gate_verdict(
report: &crate::change_set::ChangeSetReport,
violation_count: usize,
) -> &'static str {
verdict_from(!report.changes.is_empty(), violation_count)
}
#[must_use]
pub fn evaluate_full_tree(
thresholds: &Thresholds,
hotspots: &[crate::analyses::hotspots::HotspotRow],
) -> Vec<GateViolation> {
let mut out = Vec::new();
let g = &thresholds.gates;
for row in hotspots {
if let Some(max) = g.cognitive_max
&& row.cognitive > max
{
out.push(GateViolation {
gate: "cognitive_max".into(),
path: row.path.clone(),
actual: format!("{:.0}", row.cognitive),
threshold: format!("{max:.0}"),
});
}
if let Some(max) = g.hotspot_score_max
&& row.hotspot_score > max
{
out.push(GateViolation {
gate: "hotspot_score_max".into(),
path: row.path.clone(),
actual: format!("{:.2}", row.hotspot_score),
threshold: format!("{max:.2}"),
});
}
}
out
}
#[must_use]
pub fn evaluate_hotspot_anchored_rows(
max: f64,
rows: &[crate::analyses::hotspots::HotspotRow],
) -> Vec<GateViolation> {
let mut out = Vec::new();
for row in rows {
if let Some(score) = row.hotspot_score_anchored
&& score > max
{
out.push(GateViolation {
gate: "hotspot_anchored_max".into(),
path: row.path.clone(),
actual: format!("{score:.2}"),
threshold: format!("{max:.2}"),
});
}
}
out
}
#[must_use]
pub fn evaluate_code_health_gate(
thresholds: &Thresholds,
code_health: &[crate::analyses::code_health::CodeHealthRow],
) -> Vec<GateViolation> {
let mut out = Vec::new();
let Some(min) = thresholds.gates.code_health_min else {
return out;
};
for row in code_health {
if row.score < min {
out.push(GateViolation {
gate: "code_health_min".into(),
path: row.path.clone(),
actual: format!("{:.1}", row.score),
threshold: format!("{min:.1}"),
});
}
}
out
}
#[must_use]
pub fn head_has_scorable_source<R: crate::repo::Repo>(repo: &R, opts: &crate::Options) -> bool {
let Ok(paths) = repo.tracked_paths_at_head() else {
return false;
};
let Ok(filter) = crate::paths_filter::PathsFilter::from_opts(opts) else {
return false;
};
paths.iter().any(|p| {
let rel = std::path::Path::new(p);
!crate::paths_filter::is_git_metadata(rel)
&& !filter.is_excluded(rel, false)
&& crate::complexity::Tier1Language::from_path(p).is_some()
})
}
#[must_use]
pub fn evaluate_corpus_percentile_rows(
max: f64,
rows: &[crate::analyses::code_health::CodeHealthRow],
) -> Vec<GateViolation> {
let mut out = Vec::new();
for row in rows {
if let Some(pct) = row.corpus_percentile
&& pct > max
{
out.push(GateViolation {
gate: "corpus_percentile_max".into(),
path: row.path.clone(),
actual: format!("{pct:.2}"),
threshold: format!("{max:.2}"),
});
}
}
out
}
#[must_use]
pub fn evaluate_effort_exposure_rows(
threshold: f64,
rows: &[crate::analyses::effort_exposure::EffortExposureRow],
) -> Vec<GateViolation> {
evaluate_effort_exposure_rows_exempt(threshold, false, rows)
}
#[must_use]
pub fn evaluate_effort_exposure_rows_exempt(
threshold: f64,
exempt_improving: bool,
rows: &[crate::analyses::effort_exposure::EffortExposureRow],
) -> Vec<GateViolation> {
let red = rows.iter().find(|r| r.band == "red");
let total = red.map_or(0.0, |r| r.churn_share_pct);
let degrading = red.and_then(|r| r.churn_share_degrading_pct);
let (effective, decomposed) = match (exempt_improving, degrading) {
(true, Some(d)) => (d, true),
_ => (total, false),
};
if effective <= threshold {
return Vec::new();
}
let actual = if decomposed {
let improving = red.and_then(|r| r.churn_share_improving_pct).unwrap_or(0.0);
format!("{effective:.2} (red {total:.2}, improving {improving:.2} exempt)")
} else {
format!("{effective:.2}")
};
vec![GateViolation {
gate: "max_red_effort_pct".into(),
path: "(repo-wide)".into(),
actual,
threshold: format!("{threshold:.2}"),
}]
}
pub fn evaluate_effort_exposure_gate(
thresholds: &Thresholds,
db: &crate::facts::FactsDb,
opts: &crate::Options,
) -> crate::Result<Vec<GateViolation>> {
let Some(threshold) = thresholds.gates.max_red_effort_pct else {
return Ok(Vec::new());
};
let rows = crate::analyses::effort_exposure::run_effort_exposure(db, opts)?;
Ok(evaluate_effort_exposure_rows(threshold, &rows))
}
#[must_use]
pub fn evaluate_familiarity_rows(
threshold: f64,
rows: &[crate::analyses::code_familiarity::CodeFamiliarityRow],
) -> Vec<GateViolation> {
let Some(row) = rows.first() else {
return Vec::new();
};
if row.familiarity_pct < threshold {
vec![GateViolation {
gate: "code_familiarity_min".into(),
path: "(repo-wide)".into(),
actual: format!("{:.2}", row.familiarity_pct),
threshold: format!("{threshold:.2}"),
}]
} else {
Vec::new()
}
}
pub fn evaluate_familiarity_gate(
thresholds: &Thresholds,
db: &crate::facts::FactsDb,
opts: &crate::Options,
) -> crate::Result<Vec<GateViolation>> {
let Some(threshold) = thresholds.gates.code_familiarity_min else {
return Ok(Vec::new());
};
let rows = crate::analyses::code_familiarity::run_code_familiarity(db, opts)?;
Ok(evaluate_familiarity_rows(threshold, &rows))
}
#[must_use]
pub fn evaluate_finding_overlap_rows(
threshold: u32,
rows: &[crate::analyses::finding_hotspot_overlap::FindingHotspotOverlapRow],
) -> Vec<GateViolation> {
let act_now_count = rows.iter().filter(|r| r.priority == "act-now").count();
if act_now_count as u64 > u64::from(threshold) {
vec![GateViolation {
gate: "max_findings_in_hot_files".into(),
path: "(repo-wide)".into(),
actual: act_now_count.to_string(),
threshold: threshold.to_string(),
}]
} else {
Vec::new()
}
}
#[must_use]
pub fn evaluate_new_code_rows(
cfg: &super::config::NewCodeGates,
scope: &crate::analyses::new_code::NewCodeScope,
) -> Vec<GateViolation> {
let mut out = Vec::new();
if let Some(floor) = cfg.born_health_min {
for (path, score) in &scope.born {
if *score < floor {
out.push(GateViolation {
gate: "born_health_min".into(),
path: path.clone(),
actual: format!("{score:.1} (born in window)"),
threshold: format!("{floor:.1}"),
});
}
}
}
if cfg.touched_no_degradation {
for (path, net) in &scope.touched {
if *net < -f64::EPSILON {
out.push(GateViolation {
gate: "touched_no_degradation".into(),
path: path.clone(),
actual: format!("net {net:+.1} over {}d", cfg.window_days),
threshold: "\u{2265} 0".into(),
});
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn make_row(
path: &str,
cognitive: f64,
cognitive_health: f64,
hotspot: f64,
) -> crate::analyses::hotspots::HotspotRow {
crate::analyses::hotspots::HotspotRow {
path: path.to_string(),
revisions: 1,
cognitive,
cognitive_health,
hotspot_score: hotspot,
mi: None,
mi_rank: None,
ai_pct: None,
hotspot_score_anchored: None,
}
}
#[test]
fn cognitive_max_flags_offending_file() {
let mut t = Thresholds::default();
t.gates.cognitive_max = Some(30.0);
let rows = vec![
make_row("a.rs", 40.0, 80.0, 1.0),
make_row("b.rs", 20.0, 90.0, 1.0),
];
let v = evaluate_full_tree(&t, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].path, "a.rs");
assert_eq!(v[0].gate, "cognitive_max");
}
fn make_ch_row(path: &str, score: f64) -> crate::analyses::code_health::CodeHealthRow {
crate::analyses::code_health::CodeHealthRow {
path: path.to_string(),
cognitive: 0.0,
score,
structural_risk: 0.0,
percentile: 0.0,
band: "green".to_string(),
corpus_percentile: None,
beyond_corpus: false,
corpus_percentile_ci_low: None,
corpus_percentile_ci_high: None,
}
}
#[test]
fn code_health_min_flags_offending_file() {
let mut t = Thresholds::default();
t.gates.code_health_min = Some(70.0);
let rows = vec![make_ch_row("a.rs", 50.0), make_ch_row("b.rs", 85.0)];
let v = evaluate_code_health_gate(&t, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].path, "a.rs");
assert_eq!(v[0].gate, "code_health_min");
}
#[test]
fn code_health_gate_vacuous_when_unconfigured() {
let t = Thresholds::default();
let rows = vec![make_ch_row("a.rs", 0.0)];
assert!(evaluate_code_health_gate(&t, &rows).is_empty());
}
fn make_corpus_row(
path: &str,
corpus_percentile: Option<f64>,
) -> crate::analyses::code_health::CodeHealthRow {
crate::analyses::code_health::CodeHealthRow {
path: path.to_string(),
cognitive: 0.0,
score: 100.0,
structural_risk: 0.0,
percentile: 0.0,
band: "green".to_string(),
corpus_percentile,
beyond_corpus: false,
corpus_percentile_ci_low: None,
corpus_percentile_ci_high: None,
}
}
#[test]
fn corpus_percentile_max_flags_file_above_ceiling() {
let rows = vec![
make_corpus_row("hot.rs", Some(0.95)),
make_corpus_row("edge.rs", Some(0.90)),
make_corpus_row("cool.rs", Some(0.10)),
make_corpus_row("unknown.rs", None),
];
let v = evaluate_corpus_percentile_rows(0.90, &rows);
assert_eq!(v.len(), 1, "only the strictly-above file violates: {v:?}");
assert_eq!(v[0].path, "hot.rs");
assert_eq!(v[0].gate, "corpus_percentile_max");
assert_eq!(v[0].actual, "0.95");
assert_eq!(v[0].threshold, "0.90");
}
#[test]
fn corpus_percentile_max_boundary_is_strictly_greater() {
let rows = vec![make_corpus_row("edge.rs", Some(0.90))];
assert!(evaluate_corpus_percentile_rows(0.90, &rows).is_empty());
}
#[test]
fn corpus_percentile_max_ignores_none_rows() {
let rows = vec![make_corpus_row("unknown.rs", None)];
assert!(evaluate_corpus_percentile_rows(0.0, &rows).is_empty());
}
fn make_anchored_row(
path: &str,
anchored: Option<f64>,
) -> crate::analyses::hotspots::HotspotRow {
crate::analyses::hotspots::HotspotRow {
path: path.into(),
revisions: 1,
cognitive: 0.0,
cognitive_health: 100.0,
hotspot_score: 0.0,
mi: None,
mi_rank: None,
ai_pct: None,
hotspot_score_anchored: anchored,
}
}
#[test]
fn hotspot_anchored_max_flags_file_above_ceiling() {
let rows = vec![
make_anchored_row("hot.rs", Some(9.50)),
make_anchored_row("edge.rs", Some(9.00)),
make_anchored_row("cool.rs", Some(1.00)),
make_anchored_row("uncovered.rs", None),
];
let v = evaluate_hotspot_anchored_rows(9.0, &rows);
assert_eq!(v.len(), 1, "only the strictly-above file violates: {v:?}");
assert_eq!(v[0].path, "hot.rs");
assert_eq!(v[0].gate, "hotspot_anchored_max");
assert_eq!(v[0].actual, "9.50");
assert_eq!(v[0].threshold, "9.00");
}
#[test]
fn hotspot_anchored_max_boundary_is_strictly_greater() {
let rows = vec![make_anchored_row("edge.rs", Some(9.0))];
assert!(evaluate_hotspot_anchored_rows(9.0, &rows).is_empty());
}
#[test]
fn hotspot_anchored_max_ignores_none_rows() {
let rows = vec![make_anchored_row("uncovered.rs", None)];
assert!(evaluate_hotspot_anchored_rows(0.0, &rows).is_empty());
}
#[test]
fn empty_thresholds_never_violates() {
let t = Thresholds::default();
let rows = vec![make_row("a.rs", 9999.0, 0.0, 99.0)];
let v = evaluate_full_tree(&t, &rows);
assert!(v.is_empty());
}
#[test]
fn diff_gate_vacuous_when_unconfigured() {
let t = Thresholds::default();
let v = evaluate_diff_gate(&t, 999, -100.0, 0, 5, None, None);
assert!(
v.is_empty(),
"no [diff] gates ⇒ no violations regardless of inputs"
);
}
#[test]
fn diff_gate_new_hotspot_max_flags_excess() {
let mut t = Thresholds::default();
t.diff.new_hotspot_max = Some(2);
let v = evaluate_diff_gate(&t, 5, 0.0, 0, 0, None, None);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "new_hotspot_max");
assert_eq!(v[0].actual, "5");
assert_eq!(v[0].threshold, "2");
}
#[test]
fn diff_gate_new_hotspot_max_boundary_passes() {
let mut t = Thresholds::default();
t.diff.new_hotspot_max = Some(3);
let v = evaluate_diff_gate(&t, 3, 0.0, 0, 0, None, None);
assert!(v.is_empty());
}
#[test]
fn diff_gate_delta_health_min_flags_drop() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(-5.0);
let v = evaluate_diff_gate(&t, 0, -10.0, 0, 0, None, None);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "delta_code_health_min");
assert_eq!(v[0].actual, "-10.00");
assert_eq!(v[0].threshold, "-5.00");
}
#[test]
fn diff_gate_delta_health_min_improvement_passes() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(-5.0);
let v = evaluate_diff_gate(&t, 0, 3.0, 0, 0, None, None);
assert!(v.is_empty());
}
#[test]
fn diff_gate_both_violations_surface_independently() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(0.0);
t.diff.new_hotspot_max = Some(0);
let v = evaluate_diff_gate(&t, 2, -1.0, 0, 0, None, None);
assert_eq!(v.len(), 2);
let gates: Vec<&str> = v.iter().map(|g| g.gate.as_str()).collect();
assert!(gates.contains(&"new_hotspot_max"));
assert!(gates.contains(&"delta_code_health_min"));
}
#[test]
fn diff_gate_no_new_cycles_flags_a_new_loop() {
let mut t = Thresholds::default();
t.diff.no_new_cycles = true;
let v = evaluate_diff_gate(&t, 0, 0.0, 1, 2, None, None);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "no_new_cycles");
assert!(evaluate_diff_gate(&t, 0, 0.0, 2, 2, None, None).is_empty());
assert!(evaluate_diff_gate(&t, 0, 0.0, 3, 1, None, None).is_empty());
}
#[test]
fn delta_health_min_gate_fires_below_floor() {
let t = Thresholds::from_text("[diff]\ndelta_health_min = 50.0\n").unwrap();
let v = evaluate_diff_gate(&t, 0, 0.0, 0, 0, Some(42.0), Some("indeterminate"));
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "delta_health_min");
}
#[test]
fn delta_health_min_gate_passes_at_floor_and_skips_no_code_change() {
let t = Thresholds::from_text("[diff]\ndelta_health_min = 50.0\n").unwrap();
assert!(evaluate_diff_gate(&t, 0, 0.0, 0, 0, Some(50.0), Some("indeterminate")).is_empty());
assert!(evaluate_diff_gate(&t, 0, 0.0, 0, 0, None, Some("no-code-change")).is_empty());
}
#[test]
fn deny_degrading_verdict_gate() {
let t = Thresholds::from_text("[diff]\ndeny_degrading_verdict = true\n").unwrap();
let v = evaluate_diff_gate(&t, 0, 0.0, 0, 0, Some(10.0), Some("degrading"));
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "deny_degrading_verdict");
assert!(evaluate_diff_gate(&t, 0, 0.0, 0, 0, Some(60.0), Some("indeterminate")).is_empty());
}
#[test]
fn diff_gate_verdict_skipped_when_neither_side_measured() {
assert_eq!(diff_gate_verdict(false, false, 0), "skipped");
}
#[test]
fn diff_gate_verdict_passes_when_measured_and_clean() {
assert_eq!(diff_gate_verdict(true, true, 0), "passed");
}
#[test]
fn diff_gate_verdict_fails_when_measured_with_violations() {
assert_eq!(diff_gate_verdict(true, true, 3), "failed");
}
#[test]
fn diff_gate_verdict_treats_one_sided_measurement_as_measured() {
assert_eq!(diff_gate_verdict(true, false, 0), "passed");
assert_eq!(diff_gate_verdict(false, true, 1), "failed");
}
fn make_gate_delta(path: &str, delta: Option<f64>) -> crate::change_set::FileDelta {
crate::change_set::FileDelta {
path: path.to_string(),
kind: "modified".to_string(),
baseline_score: delta.map(|_| 50.0),
projected_score: delta.map(|d| 50.0 + d),
delta,
baseline_band: None,
projected_band: None,
reason: delta.map_or_else(|| Some("deleted at gate time".to_string()), |_| None),
}
}
fn derive_gate_report_changes(
deltas: &[crate::change_set::FileDelta],
newly_cyclic: &[String],
) -> Vec<crate::repo::WorktreeChange> {
let mut changes: Vec<crate::repo::WorktreeChange> = deltas
.iter()
.map(|d| crate::repo::WorktreeChange {
path: d.path.clone(),
kind: match d.kind.as_str() {
"added" => crate::repo::WorktreeChangeKind::Added,
"deleted" => crate::repo::WorktreeChangeKind::Deleted,
_ => crate::repo::WorktreeChangeKind::Modified,
},
rename_from: None,
})
.collect();
for path in newly_cyclic {
if !changes.iter().any(|c| &c.path == path) {
changes.push(crate::repo::WorktreeChange {
path: path.clone(),
kind: crate::repo::WorktreeChangeKind::Modified,
rename_from: None,
});
}
}
changes
}
fn make_gate_report(
deltas: Vec<crate::change_set::FileDelta>,
baseline_median: Option<f64>,
projected_median: Option<f64>,
newly_cyclic: Vec<String>,
) -> crate::change_set::ChangeSetReport {
let changes = derive_gate_report_changes(&deltas, &newly_cyclic);
crate::change_set::ChangeSetReport {
head_sha: "0123456789abcdef0123456789abcdef01234567".to_string(),
merge_in_progress: false,
changes,
health: crate::change_set::HealthProjection {
deltas,
baseline_median,
projected_median,
},
base_cyclic_paths: Vec::new(),
newly_cyclic_paths: newly_cyclic,
coupling_absences: Vec::new(),
findings: Vec::new(),
}
}
#[test]
fn gate_thresholds_vacuous_when_unconfigured() {
let t = Thresholds::default();
let report = make_gate_report(
vec![make_gate_delta("a.rs", Some(-50.0))],
Some(90.0),
Some(10.0),
vec!["b.rs".to_string()],
);
assert!(
evaluate_gate_thresholds(&t, &report).is_empty(),
"no [diff] gates ⇒ no violations regardless of the report"
);
}
#[test]
fn gate_median_floor_fires_below_min() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(-5.0);
let report = make_gate_report(Vec::new(), Some(60.0), Some(50.0), Vec::new());
let v = evaluate_gate_thresholds(&t, &report);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "delta_code_health_min");
assert_eq!(v[0].path, "(change-set)");
assert_eq!(v[0].actual, "-10.00");
assert_eq!(v[0].threshold, "-5.00");
}
#[test]
fn gate_median_floor_passes_at_boundary() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(-5.0);
let report = make_gate_report(Vec::new(), Some(60.0), Some(55.0), Vec::new());
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
}
#[test]
fn gate_median_floor_skipped_when_median_absent() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min = Some(0.0);
let no_base = make_gate_report(Vec::new(), None, Some(50.0), Vec::new());
assert!(evaluate_gate_thresholds(&t, &no_base).is_empty());
let no_projected = make_gate_report(Vec::new(), Some(50.0), None, Vec::new());
assert!(evaluate_gate_thresholds(&t, &no_projected).is_empty());
}
#[test]
fn gate_per_file_floor_flags_each_offending_file() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min_per_file = Some(0.0);
let report = make_gate_report(
vec![
make_gate_delta("a.rs", Some(-2.5)),
make_gate_delta("b.rs", Some(-0.1)),
make_gate_delta("c.rs", Some(1.0)),
make_gate_delta("d.rs", None),
],
Some(50.0),
Some(50.0),
Vec::new(),
);
let v = evaluate_gate_thresholds(&t, &report);
assert_eq!(v.len(), 2, "exactly the two below-floor files: {v:?}");
assert!(v.iter().all(|x| x.gate == "delta_code_health_min_per_file"));
assert_eq!(v[0].path, "a.rs");
assert_eq!(v[0].actual, "-2.50");
assert_eq!(v[0].threshold, "+0.00");
assert_eq!(v[1].path, "b.rs");
assert_eq!(change_set_gate_verdict(&report, v.len()), "failed");
}
#[test]
fn gate_per_file_floor_passes_at_boundary() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min_per_file = Some(-1.0);
let report = make_gate_report(
vec![make_gate_delta("a.rs", Some(-1.0))],
Some(50.0),
Some(50.0),
Vec::new(),
);
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
}
#[test]
fn gate_no_new_cycles_names_each_newly_cyclic_path() {
let mut t = Thresholds::default();
t.diff.no_new_cycles = true;
let report = make_gate_report(
Vec::new(),
Some(50.0),
Some(50.0),
vec!["src/a.rs".to_string(), "src/b.rs".to_string()],
);
let v = evaluate_gate_thresholds(&t, &report);
assert_eq!(v.len(), 2, "one violation per newly cyclic path: {v:?}");
assert!(v.iter().all(|x| x.gate == "no_new_cycles"));
assert_eq!(v[0].path, "src/a.rs");
assert_eq!(v[1].path, "src/b.rs");
assert_eq!(change_set_gate_verdict(&report, v.len()), "failed");
let clean = make_gate_report(Vec::new(), Some(50.0), Some(50.0), Vec::new());
assert!(evaluate_gate_thresholds(&t, &clean).is_empty());
}
#[test]
fn gate_ignores_diff_only_keys() {
let mut t = Thresholds::default();
t.diff.new_hotspot_max = Some(0);
t.diff.delta_health_min = Some(100.0);
t.diff.deny_degrading_verdict = true;
let report = make_gate_report(
vec![make_gate_delta("a.rs", Some(-50.0))],
Some(90.0),
Some(10.0),
Vec::new(),
);
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
}
fn make_added_delta(path: &str, projected_score: f64) -> crate::change_set::FileDelta {
crate::change_set::FileDelta {
path: path.to_string(),
kind: "added".to_string(),
baseline_score: None,
projected_score: Some(projected_score),
delta: None,
baseline_band: None,
projected_band: Some("red".to_string()),
reason: Some(crate::change_set::REASON_NEW_FILE.to_string()),
}
}
#[test]
fn gate_new_file_floor_flags_low_added_file() {
let mut t = Thresholds::default();
t.diff.new_file_health_min = Some(50.0);
let report = make_gate_report(
vec![
make_added_delta("src/god_class.rs", 20.0), make_added_delta("src/tidy.rs", 80.0), make_gate_delta("src/edited.rs", Some(-40.0)), make_gate_delta("src/gone.rs", None), ],
Some(60.0),
Some(59.0),
Vec::new(),
);
let v = evaluate_gate_thresholds(&t, &report);
assert_eq!(
v.len(),
1,
"only the below-floor added file violates: {v:?}"
);
assert_eq!(v[0].gate, "new_file_health_min");
assert_eq!(v[0].path, "src/god_class.rs");
assert_eq!(v[0].actual, "20.0");
assert_eq!(v[0].threshold, "50.0");
assert_eq!(change_set_gate_verdict(&report, v.len()), "failed");
}
#[test]
fn gate_new_file_floor_absent_key_is_byte_identical_noop() {
let t = Thresholds::default();
let report = make_gate_report(
vec![make_added_delta("src/god_class.rs", 1.0)],
Some(60.0),
Some(60.0),
Vec::new(),
);
assert!(
evaluate_gate_thresholds(&t, &report).is_empty(),
"no new_file_health_min ⇒ no new-file gate at all"
);
}
#[test]
fn gate_new_file_floor_passes_at_boundary() {
let mut t = Thresholds::default();
t.diff.new_file_health_min = Some(50.0);
let report = make_gate_report(
vec![make_added_delta("src/edge.rs", 50.0)],
Some(50.0),
Some(50.0),
Vec::new(),
);
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
}
#[test]
fn gate_new_file_floor_never_fires_on_deleted_file() {
let mut t = Thresholds::default();
t.diff.new_file_health_min = Some(100.0);
let report = make_gate_report(
vec![make_gate_delta("src/gone.rs", None)],
Some(50.0),
Some(50.0),
Vec::new(),
);
assert!(
evaluate_gate_thresholds(&t, &report).is_empty(),
"deleted files never trip the new-file floor"
);
}
#[test]
fn gate_per_file_floor_skipped_on_empty_change_set() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min_per_file = Some(0.0);
let report = make_gate_report(Vec::new(), None, None, Vec::new());
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
assert_eq!(change_set_gate_verdict(&report, 0), "skipped");
}
#[test]
fn gate_per_file_floor_passes_on_measured_clean_change_set() {
let mut t = Thresholds::default();
t.diff.delta_code_health_min_per_file = Some(-5.0);
let report = make_gate_report(
vec![make_gate_delta("a.rs", Some(1.0))],
Some(50.0),
Some(51.0),
Vec::new(),
);
let v = evaluate_gate_thresholds(&t, &report);
assert!(v.is_empty());
assert_eq!(change_set_gate_verdict(&report, v.len()), "passed");
}
#[test]
fn gate_new_file_floor_skipped_on_empty_change_set() {
let mut t = Thresholds::default();
t.diff.new_file_health_min = Some(50.0);
let report = make_gate_report(Vec::new(), None, None, Vec::new());
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
assert_eq!(change_set_gate_verdict(&report, 0), "skipped");
}
#[test]
fn gate_new_file_floor_passes_on_measured_clean_change_set() {
let mut t = Thresholds::default();
t.diff.new_file_health_min = Some(50.0);
let report = make_gate_report(
vec![make_added_delta("src/tidy.rs", 80.0)],
Some(60.0),
Some(60.0),
Vec::new(),
);
let v = evaluate_gate_thresholds(&t, &report);
assert!(v.is_empty());
assert_eq!(change_set_gate_verdict(&report, v.len()), "passed");
}
#[test]
fn gate_no_new_cycles_skipped_on_empty_change_set() {
let mut t = Thresholds::default();
t.diff.no_new_cycles = true;
let report = make_gate_report(Vec::new(), None, None, Vec::new());
assert!(evaluate_gate_thresholds(&t, &report).is_empty());
assert_eq!(change_set_gate_verdict(&report, 0), "skipped");
}
#[test]
fn gate_no_new_cycles_passes_on_measured_change_set_with_no_new_cycles() {
let mut t = Thresholds::default();
t.diff.no_new_cycles = true;
let report = make_gate_report(
vec![make_gate_delta("a.rs", Some(1.0))],
Some(50.0),
Some(51.0),
Vec::new(),
);
let v = evaluate_gate_thresholds(&t, &report);
assert!(v.is_empty());
assert_eq!(change_set_gate_verdict(&report, v.len()), "passed");
}
fn make_effort_row(
band: &str,
churn_share_pct: f64,
) -> crate::analyses::effort_exposure::EffortExposureRow {
crate::analyses::effort_exposure::EffortExposureRow {
band: band.to_string(),
files: 1,
loc_share_pct: 0.0,
commit_share_pct: 0.0,
churn_share_pct,
commit_share_ci_low: 0.0,
commit_share_ci_high: 0.0,
churn_share_improving_pct: None,
churn_share_degrading_pct: None,
}
}
fn make_decomposed_red_row(
churn_share_pct: f64,
improving: f64,
degrading: f64,
) -> crate::analyses::effort_exposure::EffortExposureRow {
crate::analyses::effort_exposure::EffortExposureRow {
band: "red".to_string(),
files: 1,
loc_share_pct: 0.0,
commit_share_pct: 0.0,
churn_share_pct,
commit_share_ci_low: 0.0,
commit_share_ci_high: 0.0,
churn_share_improving_pct: Some(improving),
churn_share_degrading_pct: Some(degrading),
}
}
#[test]
fn effort_exposure_gate_vacuous_when_unconfigured() {
let rows = vec![make_effort_row("red", 99.0)];
let v = evaluate_effort_exposure_rows(f64::MAX, &rows);
assert!(v.is_empty());
let t = Thresholds::default();
assert!(t.is_empty(), "default thresholds must be empty");
let t = Thresholds::from_text("[gates]\nmax_red_effort_pct = 30.0\n").unwrap();
assert!(
!t.is_empty(),
"max_red_effort_pct alone makes thresholds non-empty"
);
}
#[test]
fn effort_exposure_rows_fails_when_red_churn_exceeds_threshold() {
let rows = vec![make_effort_row("red", 50.0), make_effort_row("green", 50.0)];
let v = evaluate_effort_exposure_rows(30.0, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "max_red_effort_pct");
assert_eq!(v[0].actual, "50.00");
assert_eq!(v[0].threshold, "30.00");
assert_eq!(v[0].path, "(repo-wide)");
}
#[test]
fn effort_exposure_rows_passes_at_boundary() {
let rows = vec![make_effort_row("red", 30.0)];
assert!(evaluate_effort_exposure_rows(30.0, &rows).is_empty());
}
#[test]
fn effort_exposure_rows_passes_when_no_red_band() {
let rows = vec![make_effort_row("green", 100.0)];
assert!(evaluate_effort_exposure_rows(0.001, &rows).is_empty());
assert!(evaluate_effort_exposure_rows(0.0, &rows).is_empty());
}
#[test]
fn effort_exposure_rows_passes_at_threshold_100() {
let rows = vec![make_effort_row("red", 100.0)];
assert!(evaluate_effort_exposure_rows(100.0, &rows).is_empty());
}
#[test]
fn exempt_off_is_byte_identical_to_plain_evaluator() {
let rows = vec![make_decomposed_red_row(50.0, 40.0, 10.0)];
let plain = evaluate_effort_exposure_rows(30.0, &rows);
let exempt_off = evaluate_effort_exposure_rows_exempt(30.0, false, &rows);
assert_eq!(format!("{plain:?}"), format!("{exempt_off:?}"));
assert_eq!(exempt_off.len(), 1);
assert_eq!(exempt_off[0].actual, "50.00");
}
#[test]
fn exempt_on_gates_only_the_degrading_share() {
let rows = vec![make_decomposed_red_row(50.0, 40.0, 10.0)];
assert!(
evaluate_effort_exposure_rows_exempt(30.0, true, &rows).is_empty(),
"degrading share 10 % ≤ ceiling 30 % must pass under the exemption"
);
}
#[test]
fn exempt_on_violation_discloses_all_three_numbers() {
let rows = vec![make_decomposed_red_row(50.0, 10.0, 40.0)];
let v = evaluate_effort_exposure_rows_exempt(30.0, true, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "max_red_effort_pct");
assert_eq!(v[0].threshold, "30.00");
assert_eq!(v[0].actual, "40.00 (red 50.00, improving 10.00 exempt)");
}
#[test]
fn exempt_on_without_decomposition_falls_back_to_total() {
let rows = vec![make_effort_row("red", 50.0)];
let v = evaluate_effort_exposure_rows_exempt(30.0, true, &rows);
assert_eq!(v.len(), 1);
assert_eq!(
v[0].actual, "50.00",
"no unearned exemption without a split"
);
}
fn make_familiarity_row(
familiarity_pct: f64,
) -> crate::analyses::code_familiarity::CodeFamiliarityRow {
crate::analyses::code_familiarity::CodeFamiliarityRow {
scope: "repo".into(),
familiarity_pct,
active_authors: 1,
total_authors: 1,
islands_pct: 0.0,
verdict: (if familiarity_pct >= 70.0 {
"good"
} else {
"risky"
})
.into(),
}
}
#[test]
fn familiarity_gate_fires_when_score_below_threshold() {
let rows = vec![make_familiarity_row(60.0)];
let v = evaluate_familiarity_rows(70.0, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "code_familiarity_min");
assert_eq!(v[0].actual, "60.00");
assert_eq!(v[0].threshold, "70.00");
assert_eq!(v[0].path, "(repo-wide)");
}
#[test]
fn familiarity_gate_passes_at_boundary() {
let rows = vec![make_familiarity_row(70.0)];
assert!(evaluate_familiarity_rows(70.0, &rows).is_empty());
}
#[test]
fn familiarity_gate_vacuous_when_rows_empty() {
assert!(evaluate_familiarity_rows(70.0, &[]).is_empty());
}
#[cfg(feature = "test-support")]
#[test]
fn familiarity_gate_integration_passes_at_threshold_0() {
use crate::Options;
use crate::facts::FactsDb;
use crate::repo::GixRepo;
let delivery = crate::test_support::delivery_repo::build();
let repo = GixRepo::open(delivery.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: delivery.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let thresholds =
Thresholds::from_text("[gates]\ncode_familiarity_min = 0.0\n").expect("parse");
let v = evaluate_familiarity_gate(&thresholds, &db, &opts).expect("evaluate gate");
assert!(
v.is_empty(),
"threshold 0.0 must pass on delivery_repo: {v:?}"
);
}
fn make_overlap_row(
path: &str,
priority: &str,
) -> crate::analyses::finding_hotspot_overlap::FindingHotspotOverlapRow {
crate::analyses::finding_hotspot_overlap::FindingHotspotOverlapRow {
path: path.to_string(),
findings: 1,
engines: "semgrep".to_string(),
worst_level: "warning".to_string(),
hotspot_score: 5.0,
revs_percentile: 0.9,
health_band: "red".to_string(),
priority: priority.to_string(),
}
}
#[test]
fn finding_overlap_gate_fires_when_act_now_count_exceeds_threshold() {
let rows = vec![make_overlap_row("src/main.rs", "act-now")];
let v = evaluate_finding_overlap_rows(0, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "max_findings_in_hot_files");
assert_eq!(v[0].path, "(repo-wide)");
assert_eq!(v[0].actual, "1");
assert_eq!(v[0].threshold, "0");
}
#[test]
fn finding_overlap_gate_passes_when_act_now_count_at_threshold() {
let rows = vec![
make_overlap_row("src/main.rs", "act-now"),
make_overlap_row("src/lib.rs", "act-now"),
];
assert!(evaluate_finding_overlap_rows(2, &rows).is_empty());
}
#[test]
fn finding_overlap_gate_ignores_non_act_now_rows() {
let rows = vec![
make_overlap_row("src/main.rs", "plan"),
make_overlap_row("src/lib.rs", "note"),
];
let v = evaluate_finding_overlap_rows(0, &rows);
assert!(v.is_empty(), "plan/note rows must not trigger the gate");
}
#[test]
fn finding_overlap_gate_measured_value_is_act_now_count() {
let rows = vec![
make_overlap_row("a.rs", "act-now"),
make_overlap_row("b.rs", "act-now"),
make_overlap_row("c.rs", "plan"),
];
let v = evaluate_finding_overlap_rows(0, &rows);
assert_eq!(v.len(), 1);
assert_eq!(v[0].actual, "2", "actual must reflect only act-now count");
}
#[test]
fn empty_code_health_rows_with_threshold_yields_no_lib_violations() {
let t = Thresholds::from_text("[gates]\ncode_health_min = 50.0\n").unwrap();
let v = evaluate_code_health_gate(&t, &[]);
assert!(v.is_empty(), "no violations from empty rows: {v:?}");
}
#[cfg(feature = "test-support")]
#[test]
fn effort_exposure_gate_integration_passes_at_threshold_100() {
use crate::Options;
use crate::facts::FactsDb;
use crate::repo::GixRepo;
let bio = crate::test_support::biomarker_repo::build();
let repo = GixRepo::open(bio.dir.path()).expect("open repo");
let db = FactsDb::new_in_memory().expect("db");
let opts = Options {
repo_path: bio.dir.path().to_path_buf(),
min_revs: 1,
..Options::default()
};
db.ingest(&repo, &opts).expect("ingest");
let thresholds =
Thresholds::from_text("[gates]\nmax_red_effort_pct = 100.0\n").expect("parse");
let v = evaluate_effort_exposure_gate(&thresholds, &db, &opts).expect("evaluate gate");
assert!(v.is_empty(), "threshold 100 must pass: {v:?}");
}
use crate::analyses::new_code::NewCodeScope;
use crate::quality_gates::NewCodeGates;
fn nc_cfg() -> NewCodeGates {
NewCodeGates {
window_days: 90,
born_health_min: Some(60.0),
touched_no_degradation: true,
}
}
#[test]
fn new_code_born_below_floor_violates_with_born_message() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![("src/fresh.rs".into(), 41.2)],
touched: vec![],
};
let v = evaluate_new_code_rows(&nc_cfg(), &scope);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "born_health_min");
assert_eq!(v[0].path, "src/fresh.rs");
assert!(
v[0].actual.contains("born in window") && v[0].actual.contains("41.2"),
"born violation discloses the band + score: {}",
v[0].actual
);
assert_eq!(v[0].threshold, "60.0");
}
#[test]
fn new_code_born_at_or_above_floor_passes() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![("at.rs".into(), 60.0), ("above.rs".into(), 80.0)],
touched: vec![],
};
assert!(evaluate_new_code_rows(&nc_cfg(), &scope).is_empty());
}
#[test]
fn new_code_born_band_skipped_when_floor_absent() {
let cfg = NewCodeGates {
born_health_min: None,
..nc_cfg()
};
let scope = NewCodeScope {
window_start_present: true,
born: vec![("low.rs".into(), 3.0)],
touched: vec![],
};
assert!(evaluate_new_code_rows(&cfg, &scope).is_empty());
}
#[test]
fn new_code_touched_net_negative_violates_with_touched_message() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("src/legacy.rs".into(), -30.0)],
};
let v = evaluate_new_code_rows(&nc_cfg(), &scope);
assert_eq!(v.len(), 1);
assert_eq!(v[0].gate, "touched_no_degradation");
assert_eq!(v[0].path, "src/legacy.rs");
assert!(
v[0].actual.contains("net -30.0") && v[0].actual.contains("90d"),
"touched violation discloses net movement + window: {}",
v[0].actual
);
}
#[test]
fn new_code_touched_net_zero_passes_typo_fix() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("mono.rs".into(), 0.0)],
};
assert!(evaluate_new_code_rows(&nc_cfg(), &scope).is_empty());
}
#[test]
fn new_code_touched_net_positive_passes() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("improved.rs".into(), 42.0)],
};
assert!(evaluate_new_code_rows(&nc_cfg(), &scope).is_empty());
}
#[test]
fn new_code_touched_epsilon_boundary() {
let inside = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("noise.rs".into(), -f64::EPSILON * 0.5)],
};
assert!(
evaluate_new_code_rows(&nc_cfg(), &inside).is_empty(),
"a sub-epsilon negative is float noise and passes"
);
let real = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("real.rs".into(), -1.0)],
};
assert_eq!(
evaluate_new_code_rows(&nc_cfg(), &real).len(),
1,
"a real net degradation fails"
);
}
#[test]
fn new_code_touched_band_skipped_when_off() {
let cfg = NewCodeGates {
touched_no_degradation: false,
..nc_cfg()
};
let scope = NewCodeScope {
window_start_present: true,
born: vec![],
touched: vec![("rotting.rs".into(), -99.0)],
};
assert!(evaluate_new_code_rows(&cfg, &scope).is_empty());
}
#[test]
fn new_code_both_bands_flag_independently() {
let scope = NewCodeScope {
window_start_present: true,
born: vec![("new_bad.rs".into(), 20.0), ("new_ok.rs".into(), 90.0)],
touched: vec![
("touched_bad.rs".into(), -12.0),
("touched_ok.rs".into(), 5.0),
],
};
let v = evaluate_new_code_rows(&nc_cfg(), &scope);
assert_eq!(v.len(), 2);
assert!(
v.iter()
.any(|x| x.gate == "born_health_min" && x.path == "new_bad.rs")
);
assert!(
v.iter()
.any(|x| x.gate == "touched_no_degradation" && x.path == "touched_bad.rs")
);
}
#[test]
fn new_code_shallow_history_scope_yields_no_violations() {
let scope = NewCodeScope::default();
assert!(!scope.window_start_present);
assert!(evaluate_new_code_rows(&nc_cfg(), &scope).is_empty());
}
}