///|
/// Aggregate validation counters for dashboards and CI gates.
pub struct ValidationSummary {
diagnostics : Int
errors : Int
warnings : Int
unknown_changes : Int
duplicate_ids : Int
width_mismatches : Int
monotonic_timestamps : Bool
score : Int
} derive(Debug, Eq)
///|
/// Count diagnostics and structural risks in one trace.
pub fn validation_summary(trace : Trace) -> ValidationSummary {
let diagnostics = deep_validate(trace)
let (errors, warnings) = diagnostic_counts(diagnostics)
let mut unknown_changes = 0
let mut width_mismatches = 0
for change in trace.changes {
match find_signal(trace, change.id) {
None => unknown_changes += 1
Some(signal) =>
if change.value.width() > 0 && change.value.width() > signal.width {
width_mismatches += 1
}
}
}
let duplicate_ids = duplicate_signal_ids(trace).length()
let monotonic = timestamps_monotonic(trace)
let penalty = errors * 20 +
warnings * 5 +
unknown_changes * 10 +
width_mismatches * 10
let monotonic_penalty = if monotonic { 0 } else { 20 }
let score = Int::max(0, Int::min(100, 100 - penalty - monotonic_penalty))
{
diagnostics: diagnostics.length(),
errors,
warnings,
unknown_changes,
duplicate_ids,
width_mismatches,
monotonic_timestamps: monotonic,
score,
}
}
///|
/// Return whether timestamps are nondecreasing in source order.
pub fn timestamps_monotonic(trace : Trace) -> Bool {
let mut previous : Int64? = None
for change in trace.changes {
match previous {
Some(value) if change.time < value => return false
_ => ()
}
previous = Some(change.time)
}
true
}
///|
/// Return a compact validation line suitable for CI output.
pub fn validation_summary_line(summary : ValidationSummary) -> String {
"score=" +
summary.score.to_string() +
" diagnostics=" +
summary.diagnostics.to_string() +
" errors=" +
summary.errors.to_string() +
" warnings=" +
summary.warnings.to_string() +
" unknown_changes=" +
summary.unknown_changes.to_string() +
" width_mismatches=" +
summary.width_mismatches.to_string() +
" monotonic=" +
summary.monotonic_timestamps.to_string()
}
///|
/// Render validation counters as JSON.
pub fn validation_summary_json(summary : ValidationSummary) -> String {
"{\"diagnostics\":" +
summary.diagnostics.to_string() +
",\"errors\":" +
summary.errors.to_string() +
",\"warnings\":" +
summary.warnings.to_string() +
",\"unknown_changes\":" +
summary.unknown_changes.to_string() +
",\"duplicate_ids\":" +
summary.duplicate_ids.to_string() +
",\"width_mismatches\":" +
summary.width_mismatches.to_string() +
",\"monotonic_timestamps\":" +
summary.monotonic_timestamps.to_string() +
",\"score\":" +
summary.score.to_string() +
"}"
}
///|
/// Return declaration identifiers that are repeated in a malformed trace.
pub fn duplicate_signal_ids(trace : Trace) -> Array[String] {
let result : Array[String] = []
for index in 0.. Array[String] {
let result : Array[String] = []
for change in trace.changes {
if !trace.has_signal(change.id) && !result.contains(change.id) {
result.push(change.id)
}
}
result
}
///|
/// Return a compact structural gate result.
pub fn structural_gate(trace : Trace) -> Bool {
let summary = validation_summary(trace)
summary.errors == 0 &&
summary.unknown_changes == 0 &&
summary.width_mismatches == 0 &&
summary.monotonic_timestamps
}
///|
/// Return whether a trace is safe for bounded analysis.
pub fn analysis_safe(trace : Trace) -> Bool {
structural_gate(trace) &&
trace.end_time >= 0L &&
trace.signals.length() <= 100000
}
///|
/// Render a one-line gate explanation.
pub fn structural_gate_report(trace : Trace) -> String {
let summary = validation_summary(trace)
if structural_gate(trace) {
"pass " + validation_summary_line(summary)
} else {
"fail " + validation_summary_line(summary)
}
}
///|
/// Return all declarations with values wider than their declared width.
pub fn width_mismatch_ids(trace : Trace) -> Array[String] {
let result : Array[String] = []
for change in trace.changes {
match find_signal(trace, change.id) {
Some(signal) if change.value.width() > signal.width &&
!result.contains(change.id) => result.push(change.id)
_ => ()
}
}
result
}