verdictGapOf function

String? verdictGapOf({
  1. String? scenariosNote,
  2. String? previewsNote,
  3. Iterable<ComparedState> scenarioStates = const [],
  4. Iterable<ComparedState> previewStates = const [],
  5. int inconclusiveScenarios = 0,
  6. bool narrowed = false,
})

Why a comparison's verdict is incomplete, or null when it is whole.

The exit-code question, and deliberately answered in the published reader rather than in fw alone: fw compare exits 1 on exactly this sentence, and a consumer's script reading index.json — or an agent reading the compare reply — has to reach the same conclusion from the same rule, not re-implement it. A consumer's workflow once grew fifteen lines of guards around a comparison that could not run recording itself as clean; the rule exists so nobody writes those lines again. ComparisonIndex.verdictGap asks it of a file.

Three gaps. A half that recorded a scenariosNote never produced a verdict — its harness would not build, say — and whatever next prevents a half from running is covered by the note the day it appears. A half whose rows all came out ComparedState.failed is the same gap wearing a different record: failed means neither side rendered, so a half made of nothing else answered no question about the branch — and it is near-always environmental, one missing native library rather than fifty-one simultaneous regressions. And a half of nothing but ComparedState.wasBroken is that gap's mirror: wasBroken means the base alone would not render, so not one pair of pictures was ever compared — and every row at once is near-always one base-side cause (a fresh base checkout whose native assets did not build, a pin the base cannot run under), which would otherwise read as "already broken before this branch" and exit 0. The genuine mass repair exists — a branch fixing a crash in main fixes every flow — and it pays one red check whose sentence names what to look at, then clears when it merges and the base moves on; the silent wrong verdict would be paid on every branch until someone noticed.

All, not any, for both: one pre-broken or one repaired flow among compared ones is a half that did its job, and its row is an ordinary finding. ComparedState.broke deliberately gets no such rule: mass breakage on the head side is the branch's problem either way — environmental or not, a dependency the branch added that will not build is the branch's to fix — and a verdict full of broke findings is already loud.

narrowed switches the all-one-state rules off — the note rule stands. Uniformity is evidence of environment only over the whole suite; a run narrowed to named entries compares the rows somebody picked, and picking the one pre-broken flow would otherwise turn its ordinary finding into a permanent gap no change on the branch can lift.

inconclusiveScenarios is how many of the scenario rows were replayed and not compared — see ScenarioComparison.inconclusive. A half in which that is every replayed scenario compared nothing, which is the same gap an all-failed half is, and near-always the same kind of cause: a host that could not run anything to the end.

Implementation

String? verdictGapOf({
  String? scenariosNote,
  String? previewsNote,
  Iterable<ComparedState> scenarioStates = const [],
  Iterable<ComparedState> previewStates = const [],
  int inconclusiveScenarios = 0,
  bool narrowed = false,
}) {
  if (scenariosNote case var note?) {
    return 'the scenario half produced no verdict — ${note.split('\n').first}';
  }
  // The previews half records one for the same reason the scenario half does,
  // and only since a comparison could span several packages: a package whose
  // catalog will not compile used to end the whole run, which is the right
  // answer when it is the only package and the wrong one when three others
  // compared cleanly. Its rows are simply absent, and absence is what a note
  // exists to explain.
  if (previewsNote case var note?) {
    return 'the previews half produced no verdict — ${note.split('\n').first}';
  }
  if (narrowed) return null;
  if (inconclusiveScenarios > 0 &&
      !scenarioStates.any(
        (state) => state != ComparedState.skipped && !_settledUnrun(state),
      )) {
    return 'the scenario half produced no verdict — all '
        '$inconclusiveScenarios replayed scenarios were inconclusive';
  }
  return _uniformGap('scenario', 'scenarios', scenarioStates) ??
      _uniformGap('previews', 'entries', previewStates);
}