///|
/// Stages of the reusable waveform analysis workflow.
pub enum WorkflowStage {
InputValidation
IndexBuild
WindowSegmentation
CoverageAnalysis
AssertionAnalysis
ProtocolAnalysis
ReportAssembly
} derive(Debug, Eq)
///|
/// Status of one workflow stage.
pub enum WorkflowStageStatus {
StagePending
StageRunning
StagePassed
StageSkipped
StageFailed
} derive(Debug, Eq)
///|
/// Configuration for a repeatable analysis workflow.
pub struct WorkflowOptions {
window_width : Int64
coverage_signal_ids : Array[String]
assertions : Array[WaveformAssertion]
protocol_channels : Array[ProtocolChannel]
stop_on_failure : Bool
} derive(Debug)
///|
/// Result of one workflow stage.
pub struct WorkflowStageResult {
stage : WorkflowStage
status : WorkflowStageStatus
duration_units : Int64
rows : Int
message : String
} derive(Debug)
///|
/// Complete workflow output, retaining reusable intermediate products.
pub struct AnalysisWorkflow {
trace : Trace
options : WorkflowOptions
stages : Array[WorkflowStageResult]
index : TraceIndex?
windows : Array[TraceWindow]
coverage : CoverageReport?
assertions : AssertionSummary?
protocol_stats : Array[ProtocolStats]
passed : Bool
} derive(Debug)
///|
/// List workflow stages.
pub fn workflow_stage_catalog() -> Array[WorkflowStage] {
[
InputValidation,
IndexBuild,
WindowSegmentation,
CoverageAnalysis,
AssertionAnalysis,
ProtocolAnalysis,
ReportAssembly,
]
}
///|
/// List workflow statuses.
pub fn workflow_status_catalog() -> Array[WorkflowStageStatus] {
[StagePending, StageRunning, StagePassed, StageSkipped, StageFailed]
}
///|
/// Create default options for a production trace review.
pub fn default_workflow_options() -> WorkflowOptions {
{
window_width: 100L,
coverage_signal_ids: [],
assertions: [],
protocol_channels: [],
stop_on_failure: false,
}
}
///|
/// Set analysis window width.
pub fn WorkflowOptions::with_window_width(
self : WorkflowOptions,
width : Int64,
) -> WorkflowOptions {
{ ..self, window_width: Int64::max(1L, width) }
}
///|
/// Add a signal to coverage analysis.
pub fn WorkflowOptions::with_coverage_signal(
self : WorkflowOptions,
signal_id : String,
) -> WorkflowOptions {
let ids = self.coverage_signal_ids.copy()
if !ids.contains(signal_id) {
ids.push(signal_id)
}
{ ..self, coverage_signal_ids: ids }
}
///|
/// Add an assertion to the workflow.
pub fn WorkflowOptions::with_assertion(
self : WorkflowOptions,
assertion : WaveformAssertion,
) -> WorkflowOptions {
let assertions = self.assertions.copy()
assertions.push(assertion)
{ ..self, assertions, }
}
///|
/// Add a protocol channel to the workflow.
pub fn WorkflowOptions::with_protocol(
self : WorkflowOptions,
channel : ProtocolChannel,
) -> WorkflowOptions {
let channels = self.protocol_channels.copy()
channels.push(channel)
{ ..self, protocol_channels: channels }
}
///|
/// Run all configured stages and retain their outputs.
pub fn run_analysis_workflow(
trace : Trace,
options : WorkflowOptions,
) -> AnalysisWorkflow {
let stages : Array[WorkflowStageResult] = []
let mut passed = true
let mut index : TraceIndex? = None
let mut windows : Array[TraceWindow] = []
let mut coverage : CoverageReport? = None
let mut assertions : AssertionSummary? = None
let protocol_results : Array[ProtocolStats] = []
let validation = deep_validate(trace)
let validation_ok = validation
.filter(fn(item) { item.severity == "error" })
.length() ==
0
stages.push(
workflow_stage(
InputValidation,
if validation_ok {
StagePassed
} else {
StageFailed
},
validation.length(),
if validation_ok {
"trace is structurally valid"
} else {
"trace has structural errors"
},
),
)
passed = passed && validation_ok
if validation_ok || !options.stop_on_failure {
let built = build_index(trace)
index = Some(built)
let index_ok = built.is_consistent(trace)
stages.push(
workflow_stage(
IndexBuild,
if index_ok {
StagePassed
} else {
StageFailed
},
built.change_positions.length(),
if index_ok {
"index is consistent"
} else {
"index mismatch"
},
),
)
passed = passed && index_ok
} else {
stages.push(
workflow_stage(
IndexBuild,
StageSkipped,
0,
"stopped after validation failure",
),
)
}
if passed || !options.stop_on_failure {
windows = fixed_windows(trace, options.window_width)
stages.push(
workflow_stage(
WindowSegmentation,
StagePassed,
windows.length(),
"fixed windows generated",
),
)
} else {
stages.push(workflow_stage(WindowSegmentation, StageSkipped, 0, "not run"))
}
if options.coverage_signal_ids.length() > 0 &&
(passed || !options.stop_on_failure) {
let all_bins = value_coverage_bins(trace)
let bins : Array[CoverageBin] = []
for id in options.coverage_signal_ids {
match workflow_signal_path(trace, id) {
Some(path) =>
for bin in all_bins {
if bin.group == path {
bins.push(bin)
}
}
None => ()
}
}
coverage = Some(coverage_report_from_bins("workflow", bins))
let score = match coverage {
Some(item) => item.score
None => 0
}
let coverage_ok = score >= 80
stages.push(
workflow_stage(
CoverageAnalysis,
if coverage_ok {
StagePassed
} else {
StageFailed
},
bins.length(),
"coverage score=" + score.to_string(),
),
)
passed = passed && coverage_ok
} else {
stages.push(
workflow_stage(
CoverageAnalysis,
StageSkipped,
0,
"no coverage signals configured",
),
)
}
if options.assertions.length() > 0 && (passed || !options.stop_on_failure) {
let summary = run_assertion_suite(trace, options.assertions)
assertions = Some(summary)
stages.push(
workflow_stage(
AssertionAnalysis,
if summary.is_success() {
StagePassed
} else {
StageFailed
},
summary.total,
"assertions=" + summary.total.to_string(),
),
)
passed = passed && summary.is_success()
} else {
stages.push(
workflow_stage(
AssertionAnalysis,
StageSkipped,
0,
"no assertions configured",
),
)
}
if options.protocol_channels.length() > 0 &&
(passed || !options.stop_on_failure) {
for channel in options.protocol_channels {
let item = protocol_stats(trace, channel)
protocol_results.push(item)
passed = passed && item.incomplete == 0
}
stages.push(
workflow_stage(
ProtocolAnalysis,
if protocol_results.all(fn(item) { item.incomplete == 0 }) {
StagePassed
} else {
StageFailed
},
protocol_results.length(),
"protocol channels analyzed",
),
)
} else {
stages.push(
workflow_stage(
ProtocolAnalysis,
StageSkipped,
0,
"no protocol channels configured",
),
)
}
stages.push(
workflow_stage(
ReportAssembly,
StagePassed,
stages.length(),
"workflow report assembled",
),
)
{
trace,
options,
stages,
index,
windows,
coverage,
assertions,
protocol_stats: protocol_results,
passed,
}
}
///|
/// Resolve a signal identifier to its declared path.
fn workflow_signal_path(trace : Trace, signal_id : String) -> String? {
for signal in trace.signals {
if signal.id == signal_id {
return Some(signal_path(signal))
}
}
None
}
///|
/// Return whether every required workflow stage passed.
pub fn AnalysisWorkflow::is_success(self : AnalysisWorkflow) -> Bool {
self.passed
}
///|
/// Return a stage result by kind.
pub fn AnalysisWorkflow::stage(
self : AnalysisWorkflow,
target : WorkflowStage,
) -> WorkflowStageResult? {
for stage in self.stages {
if stage.stage == target {
return Some(stage)
}
}
None
}
///|
/// Return the number of passed stages.
pub fn AnalysisWorkflow::passed_stage_count(self : AnalysisWorkflow) -> Int {
self.stages.filter(fn(item) { item.status is StagePassed }).length()
}
///|
/// Render stage timings and statuses as CSV.
pub fn workflow_stages_csv(stages : Array[WorkflowStageResult]) -> String {
let out = StringBuilder()
out.write_string("stage,status,duration,rows,message\n")
for stage in stages {
out.write_string(
workflow_stage_name(stage.stage) +
"," +
workflow_status_name(stage.status) +
"," +
stage.duration_units.to_string() +
"," +
stage.rows.to_string() +
"," +
escape_text(stage.message) +
"\n",
)
}
out.to_string()
}
///|
/// Render a workflow summary as JSON.
pub fn AnalysisWorkflow::to_json(self : AnalysisWorkflow) -> String {
let stages = self.stages.map(workflow_stage_json).join(",")
"{\"passed\":" +
self.passed.to_string() +
",\"passed_stages\":" +
self.passed_stage_count().to_string() +
",\"stages\":[" +
stages +
"]}"
}
///|
/// Render a workflow summary for a terminal.
pub fn AnalysisWorkflow::to_text(self : AnalysisWorkflow) -> String {
"passed=" +
self.passed.to_string() +
" stages=" +
self.passed_stage_count().to_string() +
"/" +
self.stages.length().to_string() +
" windows=" +
self.windows.length().to_string() +
" protocols=" +
self.protocol_stats.length().to_string()
}
///|
/// Build one stage result; duration is deterministic for reproducible reports.
fn workflow_stage(
stage : WorkflowStage,
status : WorkflowStageStatus,
rows : Int,
message : String,
) -> WorkflowStageResult {
{ stage, status, duration_units: rows.to_int64(), rows, message }
}
///|
/// Name a workflow stage.
pub fn workflow_stage_name(stage : WorkflowStage) -> String {
match stage {
InputValidation => "input-validation"
IndexBuild => "index-build"
WindowSegmentation => "window-segmentation"
CoverageAnalysis => "coverage-analysis"
AssertionAnalysis => "assertion-analysis"
ProtocolAnalysis => "protocol-analysis"
ReportAssembly => "report-assembly"
}
}
///|
/// Name a workflow status.
pub fn workflow_status_name(status : WorkflowStageStatus) -> String {
match status {
StagePending => "pending"
StageRunning => "running"
StagePassed => "passed"
StageSkipped => "skipped"
StageFailed => "failed"
}
}
///|
/// Encode a workflow stage as JSON.
fn workflow_stage_json(item : WorkflowStageResult) -> String {
"{\"stage\":\"" +
workflow_stage_name(item.stage) +
"\",\"status\":\"" +
workflow_status_name(item.status) +
"\",\"duration\":" +
item.duration_units.to_string() +
",\"rows\":" +
item.rows.to_string() +
",\"message\":\"" +
escape_text(item.message) +
"\"}"
}