diff --git a/README.md b/README.md index 12f3a7a1e..acd48d238 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,13 @@ Joltrin (formerly SOP) is an open-source Go library that checks an AI agent's ac

+ + + + + +
Joltrin full demo on YouTube
Full demo
Joltrin short on YouTube
Short
+ ## A blocked step, then the safe order A production database drop is blocked until the backup is taken and validated: diff --git a/tools/blocklog/blocklog.go b/tools/blocklog/blocklog.go index 8ca5abc6e..114249f98 100644 --- a/tools/blocklog/blocklog.go +++ b/tools/blocklog/blocklog.go @@ -364,28 +364,34 @@ func (l *Log) Stats(workflow, version string) Stats { l.mu.Lock() defer l.mu.Unlock() - type stepKey struct { + type ruleRun struct{ rule, trace string } + type blockKey struct { + rr ruleRun step verify.StepID missing verify.State } - type ruleRun struct{ rule, trace string } + // counts is how many distinct blocked steps a rule had in one run, and how + // many of them recovered. Held by value so a run costs no allocation. + type counts struct{ blocked, recovered int } cutoff := l.now().Add(-l.ttl) runs := map[string]struct{}{} - blocked := map[ruleRun]map[stepKey]bool{} // true once the step recovered + recovered := make(map[blockKey]bool, len(l.entries)) // true once the step recovered + perRun := map[ruleRun]counts{} for _, e := range l.entries { if e.Workflow != workflow || e.Version != version || e.At.Before(cutoff) { continue } - rr, sk := ruleRun{e.BlockedBy, e.TraceID}, stepKey{e.Step, e.MissingState} switch e.Kind { case KindRun: runs[e.TraceID] = struct{}{} case "": - if blocked[rr] == nil { - blocked[rr] = map[stepKey]bool{} - } - if _, seen := blocked[rr][sk]; !seen { - blocked[rr][sk] = false + rr := ruleRun{e.BlockedBy, e.TraceID} + bk := blockKey{rr, e.Step, e.MissingState} + if _, seen := recovered[bk]; !seen { + recovered[bk] = false + c := perRun[rr] + c.blocked++ + perRun[rr] = c } } } @@ -395,27 +401,25 @@ func (l *Log) Stats(workflow, version string) Stats { if e.Kind != KindRecovered || e.Workflow != workflow || e.Version != version || e.At.Before(cutoff) { continue } - if steps := blocked[ruleRun{e.BlockedBy, e.TraceID}]; steps != nil { - sk := stepKey{e.Step, e.MissingState} - if _, ok := steps[sk]; ok { - steps[sk] = true - } + rr := ruleRun{e.BlockedBy, e.TraceID} + bk := blockKey{rr, e.Step, e.MissingState} + if done, ok := recovered[bk]; ok && !done { + recovered[bk] = true + c := perRun[rr] + c.recovered++ + perRun[rr] = c } } byRule := map[string]*RuleStats{} - for rr, steps := range blocked { + for rr, c := range perRun { rs := byRule[rr.rule] if rs == nil { rs = &RuleStats{BlockedBy: rr.rule} byRule[rr.rule] = rs } rs.BlockedRuns++ - all := true - for _, recovered := range steps { - all = all && recovered - } - if all { + if c.recovered == c.blocked { rs.RecoveredRuns++ } }