///|
/// Verification report generator (B-14).
///
/// Generates structured Markdown reports from scenario results,
/// monitor verdicts, and fault injection timelines.
///|
/// ReportEntry records a single event on the verdict timeline.
pub(all) struct ReportEntry {
cycle : Int64
timestamp_ns : Int64
event_type : String
detail : String
verdict : Verdict
} derive(Debug)
///|
/// VerificationReport collects all data needed to render a final report.
pub(all) struct VerificationReport {
title : String
summary_verdict : Verdict
total_cycles : Int64
duration_ns : Int64
monitor_results : Array[MonitorResult]
timeline : Array[ReportEntry]
fault_injections : Array[String]
metadata : Array[(String, String)]
} derive(Debug)
///|
pub fn VerificationReport::new(title : String) -> VerificationReport {
{
title,
summary_verdict: Pass,
total_cycles: 0L,
duration_ns: 0L,
monitor_results: [],
timeline: [],
fault_injections: [],
metadata: [],
}
}
///|
/// Add a timeline entry.
pub fn VerificationReport::add_event(
self : VerificationReport,
cycle : Int64,
event_type : String,
detail : String,
verdict : Verdict,
) -> Unit {
self.timeline.push({ cycle, timestamp_ns: 0L, event_type, detail, verdict })
}
///|
/// Add a fault injection annotation.
pub fn VerificationReport::add_fault(
self : VerificationReport,
description : String,
) -> Unit {
self.fault_injections.push(description)
}
///|
/// Add metadata key-value pair.
pub fn VerificationReport::add_meta(
self : VerificationReport,
key : String,
value : String,
) -> Unit {
self.metadata.push((key, value))
}
///|
/// Render the report as Markdown text.
pub fn VerificationReport::to_markdown(self : VerificationReport) -> String {
let buf = StringBuilder::new()
buf.write_string("# ")
buf.write_string(self.title)
buf.write_string("\n\n")
// Summary
buf.write_string("## Summary\n\n")
buf.write_string("| Field | Value |\n|---|---|\n")
buf.write_string("| Verdict | ")
buf.write_string(@debug.to_string(self.summary_verdict))
buf.write_string(" |\n")
buf.write_string("| Total Cycles | ")
buf.write_string(self.total_cycles.to_string())
buf.write_string(" |\n")
buf.write_string("| Duration (ns) | ")
buf.write_string(self.duration_ns.to_string())
buf.write_string(" |\n")
// Metadata
for entry in self.metadata {
buf.write_string("| ")
buf.write_string(entry.0)
buf.write_string(" | ")
buf.write_string(entry.1)
buf.write_string(" |\n")
}
buf.write_string("\n")
// Monitor Results
if self.monitor_results.length() > 0 {
buf.write_string("## Monitor Results\n\n")
buf.write_string("| Monitor | Verdict |\n|---|---|\n")
for r in self.monitor_results {
buf.write_string("| ")
buf.write_string(r.name)
buf.write_string(" | ")
buf.write_string(@debug.to_string(r.verdict))
buf.write_string(" |\n")
}
buf.write_string("\n")
}
// Fault Injections
if self.fault_injections.length() > 0 {
buf.write_string("## Fault Injections\n\n")
for f in self.fault_injections {
buf.write_string("- ")
buf.write_string(f)
buf.write_string("\n")
}
buf.write_string("\n")
}
// Timeline
if self.timeline.length() > 0 {
buf.write_string("## Verdict Timeline\n\n")
buf.write_string(
"| Cycle | Event | Detail | Verdict |\n|---|---|---|---|\n",
)
for e in self.timeline {
buf.write_string("| ")
buf.write_string(e.cycle.to_string())
buf.write_string(" | ")
buf.write_string(e.event_type)
buf.write_string(" | ")
buf.write_string(e.detail)
buf.write_string(" | ")
buf.write_string(@debug.to_string(e.verdict))
buf.write_string(" |\n")
}
}
buf.to_string()
}
///|
/// Render the report as JSON text.
pub fn VerificationReport::to_json(self : VerificationReport) -> String {
let buf = StringBuilder::new()
buf.write_string("{")
buf.write_string("\"title\":\"")
buf.write_string(escape_json(self.title))
buf.write_string("\",\"verdict\":\"")
buf.write_string(escape_json(@debug.to_string(self.summary_verdict)))
buf.write_string("\",\"total_cycles\":")
buf.write_string(self.total_cycles.to_string())
buf.write_string(",\"duration_ns\":")
buf.write_string(self.duration_ns.to_string())
buf.write_string(",\"monitors\":[")
for i, r in self.monitor_results {
if i > 0 {
buf.write_string(",")
}
buf.write_string("{\"name\":\"")
buf.write_string(escape_json(r.name))
buf.write_string("\",\"verdict\":\"")
buf.write_string(escape_json(@debug.to_string(r.verdict)))
buf.write_string("\"}")
}
buf.write_string("],\"timeline\":[")
for i, e in self.timeline {
if i > 0 {
buf.write_string(",")
}
buf.write_string("{\"cycle\":")
buf.write_string(e.cycle.to_string())
buf.write_string(",\"event\":\"")
buf.write_string(escape_json(e.event_type))
buf.write_string("\",\"detail\":\"")
buf.write_string(escape_json(e.detail))
buf.write_string("\",\"verdict\":\"")
buf.write_string(escape_json(@debug.to_string(e.verdict)))
buf.write_string("\"}")
}
buf.write_string("],\"faults\":[")
for i, f in self.fault_injections {
if i > 0 {
buf.write_string(",")
}
buf.write_string("\"")
buf.write_string(escape_json(f))
buf.write_string("\"")
}
buf.write_string("]}")
buf.to_string()
}
///|
fn escape_json(s : String) -> String {
let buf = StringBuilder::new()
for ch in s.to_array() {
match ch {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
_ => buf.write_char(ch)
}
}
buf.to_string()
}