Skip to content

Progress/summary counters use a two-branch error vs else split over three states, so incomplete work is counted as completed (5 sites) #293

Description

@gadievron

Evidence provenance (added 2026-08-22). The run-derived figures in this issue come from a
private scan that a reader cannot reproduce (raptor-e2e-20260818, a 9.85-hour run against
a Python target). They are reported for completeness, not as independently checkable evidence.
Every code claim below is pinned to public source at b5019628 and is checkable; where a
conclusion rests on the private numbers rather than on the code, treat it as my report of what
that run showed. This label was missing when the issue was filed.

Summary

Several per-unit counters branch on error versus everything else, but the pipeline has three
outcomes, not two: completed, incomplete (the loop never reached a verdict), and errored. The
incomplete case falls into the else and is counted as completed. On our run the verify checkpoint
summary reported 175 completed where only 41 verifications actually produced a verdict.

The same two-branch shape appears at five sites. Two correct three/four-way handlers already exist in
the repository to copy — one of them in the same module as one of the broken sites.

Evidence

Runtime proof

RUN=~/.openant/projects/gadievron/raptor-e2e-20260818/scans/7dbf9c691d7/python
cat $RUN/verify_checkpoints/_summary.json
{
  "step": "Verify", "phase": "done", "total_units": 223,
  "completed": 175,
  "errors": 48,
  "error_breakdown": { "api": 48 }
}

Ground truth, by enumerating the 223 per-unit checkpoint files and keying on the shape of their
verification object ({"incomplete": true} alone = errored; incomplete: true with a verdict =
incomplete; otherwise complete):

python3 - <<'PY'
import json, glob, os
cps = [c for c in glob.glob(os.path.expanduser("$RUN/verify_checkpoints/*.json"))
       if os.path.basename(c) not in ("_summary.json", "_fingerprint.json")]
inc = err = comp = 0
for c in cps:
    v = json.load(open(c)).get("verification") or {}
    if set(v.keys()) == {"incomplete"}: err += 1
    elif v.get("incomplete"):           inc += 1
    else:                               comp += 1
print("genuinely complete:", comp, "| incomplete:", inc, "| errored:", err, "| total:", comp+inc+err)
PY
genuinely complete: 41 | incomplete: 134 | errored: 48 | total: 223

completed: 175 is 41 + 134. Every incomplete verification was counted as a completion — a 4.3x
over-report of the number that produced a verdict.

The mechanism

libs/openant-core/utilities/finding_verifier.py:673-681 (excerpt — 1 interior line elided) (HEAD b501962):

        def _summary_callback(detail, usage=None):
            """Update summary counters after each unit. Called from main thread."""
            nonlocal _summary_completed, _summary_errors, _summary_error_breakdown
            ...
            if detail == "error":
                _summary_errors += 1
                _summary_error_breakdown["api"] = _summary_error_breakdown.get("api", 0) + 1
            else:
                _summary_completed += 1

detail for an incomplete verification is a verdict string (e.g. disagreed:vulnerable->vulnerable),
not "error", so it increments _summary_completed.

The same shape, five sites

site third state that lands in else what it corrupts
utilities/finding_verifier.py:673-689 incomplete verification checkpoint _summary.json counts (proof above)
utilities/context_enhancer.py:744-767 INCOMPLETE_CLASSIFICATION units enhance summary counts
core/analyzer.py:598-613 inconclusive / insufficient_context analyze progress counts
utilities/finding_verifier.py:740-751 incomplete verification the verification_note prose ("Changed from X to X")
experiment.py:590-597 incomplete verification harness-only

enhance shows the same divergence in its own artifacts: its checkpoint summary reports
errors: 0, completed: 5475 while enhance.report.json records 3 incomplete units.

Correct handlers already in-tree

  • core/verifier.py:274-330 buckets error / incomplete / verdict separately — the best template.
  • core/reporter.py:419-428 handles agree / incomplete / else, though it has no error bucket.
  • utilities/context_enhancer.py:978-1027 (_compute_agentic_stats) buckets incomplete correctly —
    in the same module as the broken counter above, which is the strongest available evidence that
    site is an oversight rather than a deliberate simplification.

Why it matters

These counters are what an operator watches during a long run and what the checkpoint summary
preserves afterwards. A verify phase that adjudicated 41 of 223 candidates reported 175 completed;
nothing in the summary distinguishes "reached a verdict" from "ran 20 iterations and gave up". The
error count is correct, so the summary looks internally consistent (175 + 48 = 223) while being wrong
about the number that matters.

Suggested fix

  1. Give each counter a third bucket (incomplete / needs_review) and branch on the structured flag
    the producer already writes (verification.incomplete, INCOMPLETE_CLASSIFICATION), not on the
    detail string.

    Added 2026-08-21 — this is not uniformly applicable, and the difference matters for scoping
    the work.
    The structured flag is reachable at some of the cited sites but not all:

    • Reachable. utilities/context_enhancer.py:744 is def _update_summary(classification, unit)
      and already reads through unit (:752, :758), so a flag on the unit can be consulted with
      no signature change.
    • Not reachable without a signature change. core/analyzer.py:598 is
      def _summary_callback(finding, usage=None) — it receives the verdict string and usage,
      nothing else. Both call sites pass only that: :273 and :299 are
      summary_callback(out["finding"], usage=out.get("usage")). The full result object exists at
      those sites — :302 reads out["result"] two lines later — but is not handed to the callback.

    So at the analyzer site this fix requires widening the callback signature and passing
    out["result"] (or the flag off it) from :273 and :299. That is still small, but it is a
    change to a call contract rather than a one-line branch, and I filed the item as though it were
    the latter everywhere.

  2. Emit the new bucket in write_summary so _summary.json carries completed / incomplete /
    errors that sum to total_units with all three visible.

  3. Copy the bucketing from core/verifier.py:274-330 rather than writing a fourth variant.

  4. Add a test asserting completed + incomplete + errors == total_units and that an incomplete unit
    does not increment completed.

What I am not claiming

  • I am not claiming the counts are wrong in the step reports. verify.report.json correctly
    carries needs_review: 134 and error_count: 48; the defect is in the checkpoint summary and the
    progress counters, which is why the two disagree.
  • I have runtime evidence for the verify site only. The other four are established from source; the
    enhance divergence above is corroborating but I did not enumerate its per-unit checkpoints.
  • experiment.py:590-597 is harness code and is listed for completeness, not as a product defect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions