feat(trace): carry the causing step in violation witnesses and summary

This commit is contained in:
pj committed 2026-06-05 22:34:07 +05:30
1 parent ff06fd9b1d
commit e2a94d4889
3 files changed
+117 -11

No files matched your search

+27 -9
View File
@@ -8,6 +8,8 @@ import (
"fmt"
"io"
"log/slog"
"maps"
"slices"
"strings"
"time"
@@ -264,10 +266,7 @@ func Run(ctx context.Context, options Options) (Summary, error) {
}
summary.Steps = stepIndex
if len(violations) > 0 {
summary.Violations = append(summary.Violations, ViolationRecord{
StepIndex: stepIndex,
Properties: violations,
})
summary.Violations = append(summary.Violations, violationRecords(violations, witnesses, stepIndex)...)
}
// Wait actions are themselves a settling: skip the idle poll. Actions
// that mutate the UI fall through to WaitForIdle so the next step's
@@ -290,10 +289,7 @@ func Run(ctx context.Context, options Options) (Summary, error) {
// left pending. Properties already violated mid-run are not re-reported.
if ended := options.Verifier.Finalize(); len(ended) > 0 {
witnesses := collectWitnesses(options.Verifier, ended, logger, stepIndex)
summary.Violations = append(summary.Violations, ViolationRecord{
StepIndex: stepIndex,
Properties: ended,
})
summary.Violations = append(summary.Violations, violationRecords(ended, witnesses, stepIndex)...)
finalStep := trace.Step{
Index: stepIndex,
Timestamp: time.Now(),
@@ -784,6 +780,26 @@ func captureMetrics(ctx context.Context, options Options, logger *slog.Logger, s
}
}
// violationRecords groups newly-violated properties by the step their witness
// attributes the violation to (the causing step), falling back to the
// detection step for properties without a witness. Records are ordered by
// step; properties keep the sorted order NewlyViolatedProperties produced.
func violationRecords(properties []string, witnesses map[string]trace.Witness, detectionStep int) []ViolationRecord {
byStep := map[int][]string{}
for _, name := range properties {
step := detectionStep
if witness, ok := witnesses[name]; ok && witness.Step > 0 {
step = witness.Step
}
byStep[step] = append(byStep[step], name)
}
records := make([]ViolationRecord, 0, len(byStep))
for _, step := range slices.Sorted(maps.Keys(byStep)) {
records = append(records, ViolationRecord{StepIndex: step, Properties: byStep[step]})
}
return records
}
// collectWitnesses gathers the violation witness for each newly-violated
// property, logs its cause, and returns them keyed by property name for the
// trace. Properties without a captured witness are skipped.
@@ -798,10 +814,12 @@ func collectWitnesses(verifierInstance *verifier.Verifier, properties []string,
continue
}
logger.Warn("property violated",
"step", stepIndex, "property", name, "reason", witness.Reason, "error", witness.IsError)
"step", witness.Step, "detected_step", stepIndex,
"property", name, "reason", witness.Reason, "error", witness.IsError)
witnesses[name] = trace.Witness{
Reason: witness.Reason,
IsError: witness.IsError,
Step: witness.Step,
Extractors: witness.Extractors,
}
}