[pitboss] phase 08: M6.5 — Patch-validation / fix-validation CI mode

This commit is contained in:
pitboss 2026-05-12 14:14:13 -04:00
parent 25e8b0eb0e
commit 118cafa535
13 changed files with 1067 additions and 4 deletions

View file

@ -266,7 +266,13 @@ impl JobManager {
// Prepare the final state outside the lock.
let (status, diags, error_str) = match result {
Ok(diags) => {
Ok(mut diags) => {
// Compute stable_hash for every finding (§M6.5 cross-commit identity).
// The CLI handler does this in commands/scan.rs::handle, but the
// server scan path bypasses handle, so do it here.
for d in &mut diags {
d.stable_hash = scan::compute_stable_hash(d);
}
log_collector.info(format!("Scan completed: {} findings", diags.len()), None);
(JobStatus::Completed, Some(Arc::new(diags)), None)
}

View file

@ -38,6 +38,10 @@ pub struct FindingView {
pub fingerprint: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub portable_fingerprint: String,
/// Blake3-derived stable cross-commit identity hash (M6.5). Zero when not
/// yet computed (server-side scans always compute it post-analysis).
#[serde(skip_serializing_if = "crate::server::models::is_zero_u64")]
pub stable_hash: u64,
pub path: String,
pub line: usize,
pub col: usize,
@ -263,12 +267,17 @@ fn status_for_diag(d: &Diag) -> &'static str {
}
}
pub(crate) fn is_zero_u64(v: &u64) -> bool {
*v == 0
}
/// Convert a Diag to a FindingView at a given index.
pub fn finding_from_diag(index: usize, d: &Diag) -> FindingView {
FindingView {
index,
fingerprint: compute_fingerprint(d),
portable_fingerprint: String::new(), // set by caller with scan_root
stable_hash: d.stable_hash,
path: d.path.clone(),
line: d.line,
col: d.col,
@ -394,6 +403,10 @@ pub struct CompareResponse {
pub fixed_findings: Vec<ComparedFinding>,
pub changed_findings: Vec<ChangedFinding>,
pub unchanged_findings: Vec<ComparedFinding>,
/// Verdict-level diff entries (M6.5). Populated when findings in both
/// scans carry `stable_hash` values.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub verdict_diff: Vec<crate::baseline::VerdictDiffEntry>,
}
/// Minimal scan metadata for comparison headers.

View file

@ -459,6 +459,11 @@ async fn compare_scans(
severity_delta,
};
// Build verdict diff from left (baseline) → right (current) using stable_hash.
let left_baseline = crate::baseline::diags_to_baseline_entries(&left_findings);
let verdict_diff_result =
crate::baseline::compute_verdict_diff(&left_baseline, &right_findings);
Ok(Json(CompareResponse {
left_scan: left_info,
right_scan: right_info,
@ -467,6 +472,7 @@ async fn compare_scans(
fixed_findings,
changed_findings,
unchanged_findings,
verdict_diff: verdict_diff_result.entries,
}))
}