This commit is contained in:
Eli Peter 2026-06-05 10:16:30 -05:00 committed by GitHub
parent 55247b7fcd
commit 991c84a1eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1464 changed files with 225448 additions and 1985 deletions

View file

@ -1,5 +1,5 @@
use crate::commands::scan::Diag;
use crate::evidence::{Confidence, Evidence};
use crate::evidence::{Confidence, Evidence, VerifyResult, VerifyStatus};
use crate::patterns::{FindingCategory, Severity};
use crate::utils::path::{DEFAULT_UI_MAX_FILE_BYTES, open_repo_text_file};
use serde::Serialize;
@ -26,6 +26,15 @@ pub const VALID_TRIAGE_STATES: &[&str] = &[
"fixed",
];
/// Valid dynamic verification states for findings.
pub const VALID_DYNAMIC_VERIFICATION_STATES: &[&str] = &[
"Confirmed",
"NotConfirmed",
"Inconclusive",
"Unsupported",
"Unverified",
];
/// Check if a string is a valid triage state.
pub fn is_valid_triage_state(s: &str) -> bool {
VALID_TRIAGE_STATES.contains(&s)
@ -38,6 +47,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,
@ -60,6 +73,8 @@ pub struct FindingView {
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<Evidence>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dynamic_verdict: Option<VerifyResult>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guard_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rank_reason: Option<Vec<(String, String)>>,
@ -195,6 +210,7 @@ pub struct FilterValues {
pub languages: Vec<String>,
pub rules: Vec<String>,
pub statuses: Vec<String>,
pub verification_statuses: Vec<String>,
}
/// Collect distinct filter values from a slice of diagnostics.
@ -205,6 +221,7 @@ pub fn collect_filter_values(findings: &[Diag]) -> FilterValues {
let mut languages = BTreeSet::new();
let mut rules = BTreeSet::new();
let mut statuses = BTreeSet::new();
let mut verification_statuses = BTreeSet::new();
for d in findings {
severities.insert(d.severity.as_db_str().to_string());
@ -217,12 +234,20 @@ pub fn collect_filter_values(findings: &[Diag]) -> FilterValues {
}
rules.insert(d.id.clone());
statuses.insert(status_for_diag(d).to_string());
verification_statuses.insert(
dynamic_status_for_diag(d)
.unwrap_or("Unverified")
.to_string(),
);
}
// Always include all valid triage states so the filter dropdown is complete
for s in VALID_TRIAGE_STATES {
statuses.insert(s.to_string());
}
for s in VALID_DYNAMIC_VERIFICATION_STATES {
verification_statuses.insert(s.to_string());
}
FilterValues {
severities: severities.into_iter().collect(),
@ -231,6 +256,7 @@ pub fn collect_filter_values(findings: &[Diag]) -> FilterValues {
languages: languages.into_iter().collect(),
rules: rules.into_iter().collect(),
statuses: statuses.into_iter().collect(),
verification_statuses: verification_statuses.into_iter().collect(),
}
}
@ -263,12 +289,36 @@ fn status_for_diag(d: &Diag) -> &'static str {
}
}
/// Human-readable dynamic status used by API filters and table rows.
pub fn dynamic_status_label(status: VerifyStatus) -> &'static str {
match status {
VerifyStatus::Confirmed => "Confirmed",
VerifyStatus::PartiallyConfirmed => "PartiallyConfirmed",
VerifyStatus::NotConfirmed => "NotConfirmed",
VerifyStatus::Inconclusive => "Inconclusive",
VerifyStatus::Unsupported => "Unsupported",
}
}
/// Dynamic verification status for a diagnostic, when a verdict exists.
pub fn dynamic_status_for_diag(d: &Diag) -> Option<&'static str> {
d.evidence
.as_ref()
.and_then(|ev| ev.dynamic_verdict.as_ref())
.map(|verdict| dynamic_status_label(verdict.status))
}
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,
@ -287,6 +337,10 @@ pub fn finding_from_diag(index: usize, d: &Diag) -> FindingView {
triage_note: String::new(),
code_context: None,
evidence: None,
dynamic_verdict: d
.evidence
.as_ref()
.and_then(|ev| ev.dynamic_verdict.clone()),
guard_kind: None,
rank_reason: None,
sanitizer_status: None,
@ -394,6 +448,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.
@ -704,6 +762,8 @@ pub struct ScannerQuality {
pub symex_verified_rate: f64,
/// Count broken down by symbolic verdict label.
pub symex_breakdown: HashMap<String, usize>,
/// Dynamic verifier verdict counts from the latest scan.
pub dynamic_verification: crate::commands::scan::DynamicVerificationSummary,
}
/// One issue-category bucket (rule-family derived). Broader than OWASP, with
@ -880,6 +940,7 @@ mod tests {
rollup: None,
finding_id: String::new(),
alternative_finding_ids: Vec::new(),
stable_hash: 0,
}
}