///|
fn severity(verdict : @ledger.Verdict) -> Int32 {
match verdict {
Failed | Error | Unresolved => 2
RecoveredOnRetry => 1
Passed | Skipped | Incomplete => 0
}
}
///|
fn classify(
before : @ledger.CaseSummary?,
after : @ledger.CaseSummary?,
) -> ChangeKind {
match (before, after) {
(None, Some(_)) => Added
(Some(_), None) => Removed
(Some(before), Some(after)) => {
if before.verdict == @ledger.Verdict::Incomplete ||
after.verdict == @ledger.Verdict::Incomplete {
return Unknown
}
if severity(after.verdict) > severity(before.verdict) {
return Regressed
}
if severity(before.verdict) > 0 &&
(
after.verdict == @ledger.Verdict::Passed ||
(
severity(before.verdict) == 2 &&
after.verdict == @ledger.Verdict::RecoveredOnRetry
)
) {
return Improved
}
if before.verdict == after.verdict && before.shard == after.shard {
Unchanged
} else {
Changed
}
}
(None, None) => Unknown
}
}
///|
/// Compare validated, separate runs. Environment and exact case identity match;
/// shard placement can change. Missing evidence, removed inventory or a changed
/// recovery policy prevents a conclusive regression gate.
pub fn compare_evidence(
before : @wire.Evidence,
after : @wire.Evidence,
) -> Comparison {
let baseline = before.summary()
let current = after.summary()
let left : Map[(String, @ledger.CaseKey), @ledger.CaseSummary] = Map([])
let right : Map[(String, @ledger.CaseKey), @ledger.CaseSummary] = Map([])
let identities : Set[(String, @ledger.CaseKey)] = Set([])
for item in baseline.cases {
left[(item.environment, item.key)] = item
identities.add((item.environment, item.key))
}
for item in current.cases {
right[(item.environment, item.key)] = item
identities.add((item.environment, item.key))
}
let ordered = identities.to_array()
ordered.sort()
let changes : Array[Change] = []
let mut comparable = baseline.complete &&
current.complete &&
before.deny_recovered() == after.deny_recovered()
for (environment, key) in ordered {
let previous = left.get((environment, key))
let next = right.get((environment, key))
let kind = classify(previous, next)
if kind == Removed {
comparable = false
}
changes.push({ environment, key, kind, before: previous, after: next, })
}
{
before: baseline,
after: current,
before_policy: before.deny_recovered(),
after_policy: after.deny_recovered(),
comparable,
changes,
}
}
///|
/// 3: inconclusive; 1: new adverse result (including retry instability); 0:
/// no new adverse result. Existing failures still fail the separate verify gate.
pub fn Comparison::exit_code(self : Comparison) -> Int32 {
if !self.comparable {
return 3
}
for change in self.changes {
if change.kind == Regressed {
return 1
}
if change.kind == Added &&
change.after is Some(item) &&
severity(item.verdict) > 0 {
return 1
}
}
0
}