Skip to content

fix: grading and triage defects the first full-60 sweep exposed - #117

Merged
aallan merged 8 commits into
mainfrom
fix/sweep-triage-classification
Jul 28, 2026
Merged

fix: grading and triage defects the first full-60 sweep exposed#117
aallan merged 8 commits into
mainfrom
fix/sweep-triage-classification

Conversation

@aallan

@aallan aallan commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Everything here was found by running the first full-60 sweep and reading what it produced. None of it was reachable from the test suite, because every existing gate — baselines, validate, evaluator parity — runs against canonical solutions, and canonical solutions never hit any of these paths.

One of them was silently biasing the published cross-language comparison.

1. Tier 3 was not being graded for Python or TypeScript

The harness builds a test wrapper by reading the model's own type declaration. When it cannot map that declaration it correctly declines — records run_correct=None, leaves the problem ungraded rather than guessing. That decline path is right. What was wrong is how often it fired:

language problems actually scored (of 60)
Vera / Vera NL 60
Aver / AILANG ~59
TypeScript ~53
Python ~51

All twelve missing problems are Tier 3 (ADT + exhaustive match) and Tier 4. So the headline "does Vera beat Python/TypeScript" chart was comparing Vera over 60 problems against Python over ~51 — and the missing tier is exactly the one Vera's design targets.

Two causes, both in adt_render.py:

Python — a union alias is the ADT's name:

@dataclass(frozen=True)
class Cons(Generic[T]):
    head: T
    tail: "LinkedList[T]"

LinkedList: TypeAlias = Union[Nil, Cons[T]]

selfish collected the class and its ast.Name bases. Generic[T] is a Subscript, not a Name, and LinkedList is a module-level alias rather than a base — so the self-reference never resolved and the shape guard saw ('t', 'linkedlist') where it wanted ('int', 'SELF'). Self-names now include module-level aliases that reference the class.

TypeScript — a type variable is unreadable, not wrong:

type List<T> = { kind: "nil" } | { kind: "cons"; head: T; tail: List<T> };

T is not a class name, so it collapsed to "object" and could never match Int's "number". _ts_kind() now strips generics and returns None for anything unreadable; alignment matches readable types first and only then fills gaps in declared order — so a genuine swap of two readable fields is still caught. This is the same judgement _type_mismatch already made on the Python side.

Result across the sweep: 111 of 113 declines resolve. Both halves are reported, because 35 of the newly-gradeable answers are simply wrong — those lower the comparison languages' scores relative to leaving them ungraded, which is why the fix has to be applied wholesale rather than to the rows that flatter the result.

2. scripts/regrade.py — apply a grading fix without paying for the sweep again

Since #109 every row carries a code_path, so the model's actual output is on disk and a verdict can be recomputed: same code, same problems, fixed harness. That turns a ~$100 overnight re-run into a few minutes of local subprocesses.

It re-grades every row, not just the ones a fix was expected to help — grading only the latter would import every improvement while hiding any regression the same change caused. That makes it a free regression test, and the first full run is the evidence this PR rests on: no solved → transition of any kind. Nothing that passed was broken.

Only verdict fields are replaced. Token counts, timings and model identity describe the original run and carry through untouched, so a re-graded file still records what that sweep actually cost. Verdicts are compared with sandbox paths normalised, since those differ on every run by construction — without that, 64 rows looked "changed" and would have had their original error messages overwritten with paths from a run that never happened.

3. Sweep triage: two misclassifications that changed what the sweep did

A model's non-terminating code was triaged as an infrastructure fault. The transient pattern matched a bare timed out, which covers both a provider call failing and the harness's own 30s budget on the model's compiled code. Those are opposites: one is worth retrying, the other is a deterministic wrong answer. So the sweep retried it to its limit, the target read RE-RUN forever, and a surgical repair reproduced it exactly. Per-test failures carry the harness's test N: prefix and run locally with no network in play, so that prefix now settles the bucket first. Concretely: Opus 4.8 wrote a list_reverse that passes vera check and then does not terminate.

A truncation was published as a model refusal. LENGTH knew only OpenAI's finish_reason=length; Anthropic spells a token wall stop_reason=max_tokens, and its message also contains "no text block" — which the refusal pattern matched, and which was tested first. A recoverable failure was kept as a real verdict and counted in the published refusal figure. A shared is_refusal() now excludes truncations, so the monitor and the refusal chart cannot disagree again. Refusals 5 → 4.

4. rerun_failed.py — the repair tool broke exactly when it was needed

Three defects, all in the path that only runs after a sweep:

  • It globbed <model>-bench-*, unambiguous with one release on disk and ambiguous the moment a second landed. With 0.0.16 and 0.0.18 present, every repair exited ambiguous — 2 files match. The prefix now pins the bench version (--bench-version reaches older eras); the compiler segment stays a wildcard, since a target's Vera version is whatever produced it.
  • The version token was not terminated, so 0.0.18 also matched bench-0-0-180-… — a repair aimed at one release could splice rows into another's file.
  • Its in-flight guard counted rows, but a fix attempt emits a second row for the same problem, so a file could reach 60 rows while still missing problems. It now counts unique problem ids, the same denominator sweep_status uses.

5. Charts that asserted things the data no longer supported

  • The delta legend covered the largest bar. Placed lower right inside the axes — on a diverging chart, exactly where the biggest positive delta ends. Moved to the sparse upper-left in both the slide and the canonical chart.
  • The generation slide's title and subtitle were hardcoded — "Three flagships, one trajectory", "the Vera line rises at every step" — both true of a three-link chain and false the moment a fourth landed. Both are computed from the plotted points now, and the subtitle reports only languages actually drawn.
  • Two slides in one deck disagreed about the same two models. The chain read Opus 4.8 and Opus 5 from 0.0.16 while the controlled-pair slide read them from 0.0.18 — 36 graded problems against 60 — so that pair fell 6 points in TypeScript on one slide and rose 3 on the other. Every link with 0.0.18 data now reads it.
  • The chain mixed a family line with a tier jump. Fable 5 was appended as a fourth link on the mistaken basis that it was the newest model; it is the ceiling tier — more capable than Opus 5, not later than it — so the slope into it read as generational progress that never happened, and it pushed the controlled pair into the chain's middle. The chain is one family in release order again.
  • The coverage slide outlived its premise. It argued pass@1 could not see the problems without test cases; Grade the other 24 problems: generate a Vera wrapper like every other language #107 gave every problem test cases, so it rendered "0 of 60" beside a 0% hero stat that read as Vera failing everything. It now measures where vera check and vera run disagree — the programs that cleared the static gate and still failed, named individually, against the count of working programs wrongly refused. Both numbers are derived, so the captions cannot contradict them.

Scope

Generators only. The regenerated assets ship with the README rewrite in a separate PR — but the generators are here, so a fresh clone cannot produce the old charts.

ruff check . && ruff format --check . && ruff check --select S vera_bench/ clean; full suite green.

Refs #101, #115.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a tool to recalculate stored verdicts without rerunning models.
    • Coverage reporting now highlights “escapes” (checks pass, runtime fails) and “false alarms” (checks fail, runtime passes).
  • Bug Fixes

    • Improved sweep failure classification to avoid incorrect retries and mislabelled refusals.
    • Corrected canonical result selection when multiple benchmark eras are present, including version scoping and completion counting.
    • Fixed generation/coverage slide computations and restored intended release-family ordering.
    • Improved recursive type rendering and TypeScript field alignment.
  • Style

    • Adjusted legend placement/transparency and refreshed slide titles and explanatory metrics for clarity.

Both bugs surfaced on the first full-60 sweep, in the two scripts that
only run once a sweep has finished — so neither could have been caught
before now.

`sweep_status.py` classified a model's non-terminating code as an
infrastructure transient. The transient pattern matched a bare `timed
out`, which covers both a provider call failing and the harness's own
30s budget on the model's COMPILED code. Those are opposites: the first
is worth retrying, the second is a deterministic wrong answer — the
program never returned. So the sweep retried it to its retry limit, the
target read RE-RUN forever, and a surgical repair re-ran it to identical
effect. Per-test failures carry the harness's `test N:` prefix and run
locally with no network in play, so no message carrying that prefix can
be infrastructure; it now settles the bucket before any transient word
is consulted. Across the 46 swept targets this moves exactly 2 rows and
leaves 0 transients.

The concrete case is worth keeping: Opus 4.8 wrote a `list_reverse` that
passes `vera check` — contracts and types verified — and then does not
terminate. It scores as not-solved, which is correct; the fix only stops
the harness re-running it.

`rerun_failed.py` could not find its target at all once a second release
existed. It globbed the canonical name only as far as `-bench-`, which
is unambiguous with one era on disk and ambiguous the moment another
lands — `results/` keeps every release side by side. With 0.0.16 and
0.0.18 present, every repair exited `ambiguous — 2 files match`. The
prefix now pins the bench version, defaulting to the installed one, with
--bench-version to reach a superseded era; the compiler segment stays a
wildcard because a target's Vera version is whatever produced it, not
whatever is installed now. Same defect class as the sweep runner's
hardcoded version, in the one script that only ever runs after a sweep.

Refs #101.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes refine sweep classification, bench-version result repair, stored-result regrading, ADT rendering, and narrative chart generation. They also update coverage metrics, generation annotations, delta legend placement, and the changelog.

Changes

Benchmark harness updates

Layer / File(s) Summary
Sweep outcome classification
scripts/sweep_status.py, tests/test_sweep_status.py, CHANGELOG.md
Provider truncation is classified as length, harness execution failures as other, and infrastructure timeouts remain transient.
Version-scoped result repair and regrading
scripts/rerun_failed.py, scripts/regrade.py, tests/test_rerun_failed.py, CHANGELOG.md
Canonical lookup accepts --bench-version, completion counts unique problems, and stored verdicts can be recomputed with optional atomic application.
ADT type and field-shape handling
vera_bench/adt_render.py
TypeScript field alignment uses readable type matching, while recursive Python union aliases participate in shape normalisation.
Narrative slide data and coverage rendering
scripts/plot_narrative.py, CHANGELOG.md
Generation annotations derive from plotted data, and coverage reports named gate/runtime disagreements with escape and false-alarm statistics.
Delta chart legend layout
scripts/plot_results.py, scripts/plot_slide.py, CHANGELOG.md
Delta-chart legends move to the upper-left with revised layout and opacity settings.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: harness, ci

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main theme: fixing grading and sweep triage defects surfaced by the first full sweep.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sweep-triage-classification

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/rerun_failed.py`:
- Around line 248-250: The --apply flow around find_canonical must acquire and
validate ownership through the shared sweep lifecycle/lock before mutating the
selected results file with splice(). Replace the raw row-count cleanliness check
with unique problem-ID coverage and transient-row status, and ensure --force
cannot bypass this protection; reject targets currently being processed by the
sweep to prevent unlink/write races.
- Line 76: Update the benchmark filename matching logic in the rerun flow so the
generated bench-version token is terminated by the filename separator,
preventing 0.0.18 from matching 0.0.180. Add a regression case using only
0.0.180 and verify that applying 0.0.18 does not modify those rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 26fd5007-d30e-4315-b11f-45cec079424a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c5a7bf and 15f8a50.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • scripts/rerun_failed.py
  • scripts/sweep_status.py
  • tests/test_rerun_failed.py
  • tests/test_sweep_status.py

Comment thread scripts/rerun_failed.py
Comment thread scripts/rerun_failed.py
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.62%. Comparing base (2c5a7bf) to head (804d706).

Files with missing lines Patch % Lines
vera_bench/adt_render.py 63.63% 12 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #117      +/-   ##
==========================================
- Coverage   86.92%   86.62%   -0.30%     
==========================================
  Files          15       15              
  Lines        2845     2871      +26     
==========================================
+ Hits         2473     2487      +14     
- Misses        372      384      +12     
Flag Coverage Δ
python 86.62% <66.66%> (-0.30%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

aallan and others added 2 commits July 28, 2026 01:44
Five defects, all found by reading the first full-60 sweep's own output.

A truncation was published as a model refusal. `sweep_status.py` knew
only OpenAI's `finish_reason=length`; Anthropic spells a token wall
`stop_reason=max_tokens`, and its message also contains "no text block"
— which the refusal pattern matches, and which was tested first. So an
Anthropic truncation was relabelled a decline: a recoverable failure
(raise --max-tokens, re-run) was kept as a real verdict and counted in
the published refusal figure. LENGTH now covers both spellings and a
shared `is_refusal()` excludes truncations, so the monitor and the
refusal chart — which read the same pattern through different filters,
and disagreed — cannot diverge again. Across 46 targets: refusals 5 -> 4.

The delta chart's legend covered the largest bar. Placed `lower right`
INSIDE the axes, which on a diverging chart is exactly where the biggest
positive delta ends, so the strongest Vera result and its value label sat
under a semi-transparent box — and the bar showing through made hidden
data read as a rendering artefact. Moved to the sparse upper-left, in
both the slide and the canonical chart.

The generation slide asserted findings it no longer had. Its title and
subtitle were hardcoded — "Three flagships, one trajectory", "the Vera
line rises at every step" — both true of a three-link chain and false the
moment a fourth landed. Both are computed from the plotted points now.

Two slides in one deck disagreed about the same two models. The chain
read Opus 4.8 and Opus 5 from 0.0.16 while the controlled-pair slide read
them from 0.0.18 — 36 graded problems against 60 — so that pair fell 6
points in TypeScript on one slide and rose 3 on the other. Every link
with 0.0.18 data now reads it, leaving both confounds on the single step
where Opus 4 is pinned to the only release that swept it.

The coverage slide outlived its premise. It argued pass@1 could not see
the problems without test cases; #107 gave every problem test cases, so
it rendered "0 of 60" beside a 0% hero stat that read as Vera failing
everything. It now measures where `vera check` and `vera run` disagree:
the 6 programs that cleared the static gate and still failed, named
individually, against 0 working programs wrongly refused.

Assets are deliberately not in this commit; they ship with the README
rewrite. But the generators are, so a fresh clone cannot produce the old
charts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… lineup

Claude Fable 5 was appended as a fourth link on my mistaken belief that
it was the newest Anthropic model. It is the CEILING tier: more capable
than Opus 5, not later than it. Three things followed from that error.

The slope into the final point read as generational progress that never
happened — and Fable 5 falls in three of four languages, so the chart
contradicted its own "one trajectory" title. Any caption ordering the
chain chronologically was false.

It also displaced the controlled pair. With four links, Opus 4.8 -> Opus
5 sat at positions 2 and 3, so the companion --pair-only slide showed the
chain's MIDDLE segment and looked like a zoom on an arbitrary interior
step rather than the end of the line.

And it was no longer earning its place: re-pinning Opus 4.8 and Opus 5 to
0.0.18 already put the current release in the chain three times over, so
the link supplied nothing the rest did not.

The chain is Opus 4 -> Opus 4.8 -> Opus 5 again, one family in release
order, and the controlled pair is now literally its last step. The
comment says what may be added: a successor, not a bigger sibling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 65-70: Correct the changelog entry describing the delta-chart
legend placement: do not claim it was moved above the axes or cannot collide
with data. Update the wording to accurately state that the legend uses the
upper-left position inside the axes in both slide and canonical charts, without
implying collision is impossible.

In `@scripts/plot_narrative.py`:
- Around line 1040-1066: Adjust the escape-list layout in the rows_sorted
rendering loop so the y positions remain within the panel for any number of
entries, scaling the row step or text sizing based on len(rows_sorted). Preserve
the existing ordering, columns, and styling while ensuring all named escapes
remain visible instead of being clipped.
- Around line 677-683: Build the subtitle’s moved entries from the actually
plotted modes rather than wanted or CORE_MODES, using the rebound modes
collection from the plotting flow around extract_data. Keep the existing _net
calculation and None filtering, but ensure languages absent from the plotted
data cannot produce fabricated deltas in moved.
- Around line 1121-1148: The narrative contains hardcoded zero-value claims that
can contradict false_alarms. In the plot-building code around the ax2.text
caption and the fig.text subtitle, derive both messages from false_alarms:
retain the existing zero wording when it is zero, otherwise state the actual
count and avoid claiming the gate never refused correctly running code.

In `@scripts/plot_results.py`:
- Around line 742-752: Move the legends below their axes so they cannot overlap
plotted data: in scripts/plot_results.py lines 742-752, update the legend
anchoring and columns using comparison_modes, then extend the figure’s bottom
margin; in scripts/plot_slide.py lines 361-376, apply the same below-axis
anchoring with columns based on legend_handles and widen the tight_layout rect
bottom to prevent clipping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b435c050-9c64-4be3-8354-7783cdf4469a

📥 Commits

Reviewing files that changed from the base of the PR and between 15f8a50 and 8062edf.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • scripts/plot_narrative.py
  • scripts/plot_results.py
  • scripts/plot_slide.py
  • scripts/sweep_status.py
  • tests/test_sweep_status.py

Comment thread CHANGELOG.md Outdated
Comment thread scripts/plot_narrative.py Outdated
Comment thread scripts/plot_narrative.py Outdated
Comment thread scripts/plot_narrative.py Outdated
Comment thread scripts/plot_results.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/plot_narrative.py (1)

738-742: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the later Vera version in the caption.

The generation chain is pinned to bench version 0.0.18, but the slide states 0.1.8. This publishes an incorrect toolchain version and can mislead interpretation of the plotted results.

🐛 Proposed fix
-            "Opus 4 ran under Vera 0.0.112, every later point under 0.1.8; "
+            "Opus 4 ran under Vera 0.0.112, every later point under 0.0.18; "
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/plot_narrative.py` around lines 738 - 742, Update the caption text in
the fig.text call to report Vera version 0.0.18 for the later points instead of
0.1.8, preserving the surrounding wording and formatting.
♻️ Duplicate comments (3)
scripts/plot_narrative.py (3)

681-691: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Build the subtitle from the modes actually plotted.

modes has just been filtered to languages present in every generation link, but this comprehension iterates wanted/CORE_MODES. A dropped mode can therefore produce a fabricated _net value from the absent-data sentinel and appear in the subtitle despite having no plotted series.

🐛 Proposed fix
-    moved = [f"{m} {_net(m):+d}" for m in (wanted or CORE_MODES) if _net(m) is not None]
+    moved = [f"{m} {_net(m):+d}" for m in modes if _net(m) is not None]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/plot_narrative.py` around lines 681 - 691, Update the subtitle’s
`moved` comprehension to iterate over the already-filtered `modes` collection,
matching the series actually plotted. Keep the existing `_net` filtering and
formatting unchanged so dropped modes cannot appear in the subtitle.

1042-1070: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep every named escape inside the panel.

The fixed 0.125 decrement displays at most seven rows; the eighth reaches approximately y=-0.02 and is clipped. Because the list is data-derived, larger sweeps can silently omit named failures.

🐛 Proposed fix
     y = 0.855
+    step = min(0.125, 0.80 / max(len(rows_sorted), 1))
     for model, pid, cause in rows_sorted:
...
-        y -= 0.125
+        y -= step
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/plot_narrative.py` around lines 1042 - 1070, Update the escape-row
layout around rows_sorted and the y decrement so every data-derived named escape
remains within the panel, including lists longer than seven rows. Compute
spacing or otherwise adapt the available vertical range to rows_sorted’s length
while preserving the existing text ordering and formatting.

1125-1152: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive both false-alarm captions from false_alarms.

The count is already rendered and coloured red when non-zero, but the captions always claim that no working programme was rejected and that the cost is zero. That makes the slide self-contradictory whenever false_alarms > 0.

[details]

🐛 Proposed fix
-        "the gate never refused code that ran correctly",
+        (
+            "the gate never refused code that ran correctly"
+            if false_alarms == 0
+            else f"{false_alarms} working programmes were rejected"
+        ),
...
-            "The cost of that gate is the number worth watching, and it is zero.",
+            (
+                "The cost of that gate is the number worth watching, and it is zero."
+                if false_alarms == 0
+                else f"The cost of that gate is {false_alarms} rejected programmes."
+            ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/plot_narrative.py` around lines 1125 - 1152, Update the captions in
the narrative plotting section to derive their wording from false_alarms: make
the gate-refusal caption accurately reflect the false-alarm count and replace
the hard-coded “cost ... is zero” statement with the corresponding value.
Preserve the existing zero-count wording where false_alarms is zero and keep the
current styling and layout unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@scripts/plot_narrative.py`:
- Around line 738-742: Update the caption text in the fig.text call to report
Vera version 0.0.18 for the later points instead of 0.1.8, preserving the
surrounding wording and formatting.

---

Duplicate comments:
In `@scripts/plot_narrative.py`:
- Around line 681-691: Update the subtitle’s `moved` comprehension to iterate
over the already-filtered `modes` collection, matching the series actually
plotted. Keep the existing `_net` filtering and formatting unchanged so dropped
modes cannot appear in the subtitle.
- Around line 1042-1070: Update the escape-row layout around rows_sorted and the
y decrement so every data-derived named escape remains within the panel,
including lists longer than seven rows. Compute spacing or otherwise adapt the
available vertical range to rows_sorted’s length while preserving the existing
text ordering and formatting.
- Around line 1125-1152: Update the captions in the narrative plotting section
to derive their wording from false_alarms: make the gate-refusal caption
accurately reflect the false-alarm count and replace the hard-coded “cost ... is
zero” statement with the corresponding value. Preserve the existing zero-count
wording where false_alarms is zero and keep the current styling and layout
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5d2c5e6c-97fc-4a62-af22-eb3f5e87c754

📥 Commits

Reviewing files that changed from the base of the PR and between 8062edf and 935fd4d.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • scripts/plot_narrative.py

@aallan aallan changed the title fix: triage a model's own non-terminating code as a result, not a fault fix: grading and triage defects the first full-60 sweep exposed Jul 28, 2026
…erived captions

Six findings verified against current code; four were live.

`rerun_failed.py`'s bench-version token was not terminated, so `0.0.18`
also matched `bench-0-0-180-...`. On a lookup that would be harmless; this
path ends in a splice, so it could have written fresh rows into a
different release's results file. The glob now accepts only the two shapes
a real name takes after the bench segment — a compiler segment, or the
extension — with a regression case that puts 0.0.180 on disk alone and
asserts 0.0.18 does not select it.

Its in-flight guard counted rows. A fix attempt emits a second row for the
same problem, so a file can reach 60 rows while still missing problems —
and the guard exists precisely to stop a repair racing a sweep mid-write.
It now counts unique problem ids, the same denominator sweep_status uses.

The coverage slide's captions hardcoded their own zeroes: "the gate never
refused code that ran correctly" and "the cost of that gate ... is zero"
would have contradicted a non-zero false-alarm count sitting beside them.
Both derive from the count now. The escape list's row pitch was fixed at
0.125, which put an eighth escape below the panel — so a WORSE result would
have hidden the extra rows, flattering the number the slide exists to
report. Pitch scales with the count.

The generation subtitle reported net change for `wanted` rather than the
`modes` actually plotted, which could have put a number on the slide with
no series behind it.

Declined two. Moving the delta legend below the axes is a design decision
already taken the other way — upper-left, stacked, inside the axes, which
is where the maintainer asked for it. And the generation caption's "0.1.8"
is correct: 0.0.112 and 0.1.8 are Vera compiler versions, not bench
versions, which is what that sentence is about.

Also declined the sweep-lifecycle lock proposed alongside the coverage
guard: no such lifecycle exists here, and inventing one is the feature
tracked in #101, not a minimal fix. The coverage half of that finding is
what landed.

None of these change a published number — verified: the two affected
charts regenerate byte-identical. They are latent fixes that matter when
the data changes shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aallan

aallan commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai — replying to the outside-diff finding on scripts/plot_narrative.py (the generation caption, ~738-742).

Declining this one: 0.1.8 is correct as written, and 0.0.18 would make the sentence wrong.

The caption is about the Vera compiler, not the bench release:

Opus 4 ran under Vera 0.0.112, every later point under 0.1.8; every problem carries a verdict in each era.

0.0.112 and 0.1.8 are both Vera versions — that is the confound the line exists to disclose, since the compiler and its stdlib moved under the first step and a stdlib expansion lands hardest on Tier 2. The bench version is disclosed separately, in the line immediately below, in the terms that matter for it:

Only the first point is graded over 36 problems; every later one over 60, so read that first step as a change of base, not a gain.

Substituting the bench version into the first sentence would claim Opus 4 ran under "Vera 0.0.112" and the rest under "Vera 0.0.18", which is not a Vera version at all. The two numbers are close enough to look like a typo — 0.1.8 vs 0.0.18 — which is probably what prompted the finding.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@aallan Thanks for the detailed clarification. You’re right—the finding conflated the Vera compiler version with the benchmark release version. The caption correctly uses 0.1.8 to disclose the compiler/stdlib confound, while the following line separately discloses the bench-version and denominator change. I’m withdrawing the finding.


✏️ Learnings added
Learnt from: aallan
URL: https://github.com/aallan/vera-bench/pull/117

Timestamp: 2026-07-28T01:24:57.599Z
Learning: In `scripts/plot_narrative.py`’s generation caption, `0.0.112` and `0.1.8` refer to Vera compiler versions, not Vera-Bench release versions. Bench versions such as `0.0.18` are a separate version domain and are disclosed in the adjacent denominator/base-change text.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Error messages are truncated to a fixed length, and the sandbox path sits
BEFORE the cut — so a path of a different length moves where the sentence
ends, and the same advice comes back as "at compile t" instead of "at
compile time." Normalising the path was not enough; the strings still
differed by where they had been severed.

That mattered for honesty rather than tidiness. The first full run reported
177 changed rows when 111 were real, and that inflated number was headed
straight into the PR as evidence. It would also have rewritten dozens of
rows' messages with paths from a re-grade rather than the sweep that
produced them.

`_same_verdict` now discounts both: every non-message field must match
exactly, and only the message may be a truncation of its counterpart.
Same-verdict rows keep their original text. 177 -> 129 changed, with the
111 real transitions untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/regrade.py`:
- Around line 89-112: Update _same_verdict so the error_message comparison
requires a true truncation relationship: after confirming all non-message fields
match, return true only when ma is a prefix of mb or mb is a prefix of ma, while
preserving the existing empty-message behavior as appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 71e7f46a-89d0-4bf8-a284-82db184aaa1c

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6b144 and 1a243a2.

📒 Files selected for processing (1)
  • scripts/regrade.py

Comment thread scripts/regrade.py
…el was blamed

The harness splices `export` onto the entry point by string-matching
`function name(`. A generic solution writes `function listLength<T>(list:
List<T>)`, which does not contain `listLength(` — so no export was added,
the wrapper's import resolved to undefined, and every test threw
"is not a function".

That is a WRAPPING failure recorded as the model's wrong answer, and it is
not a rare shape: generics are idiomatic exactly where the ADT problems
live. Re-grading TypeScript from stored code moves 51 rows from wrong to
solved. Nothing moves the other way.

It also nearly published a much stronger claim than the data supports.
With these 51 counted as failures, Vera's measured lead over TypeScript
roughly doubled — Fable 5 read +13 and Kimi K2.6 +15 against a previously
published +7. Those numbers were an artefact of this bug.

Same defect class as the ADT mapper fixed earlier in this PR: a literal
string match that generics walk straight past. The injection now matches
the declaration rather than a literal call shape, and handles arrow
function entry points (`const f = <T>(...)`) as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aallan and others added 2 commits July 28, 2026 02:55
…SSUES

Both found while verifying the v0.0.18 re-grade, both with an exit
condition rather than a shrug.

#118 is the one that has already cost data: baselines unlinks its output
at startup and writes at the end, so any interruption destroys a file that
results/ being gitignored makes unrecoverable. aver-baseline.jsonl went
three times in one night.

#119 is smaller and mostly about honesty of route: a class with no __init__
and no fields is unambiguously nullary, and reading it as unverifiable lets
the shape guard render a call it should have refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The min-length comparison is a prefix test, and the bool(n) guard reads
like a typo until you notice that an empty string is a prefix of
everything — so startswith() would treat a message appearing or
disappearing as the same verdict. It is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aallan
aallan merged commit 864eef3 into main Jul 28, 2026
10 checks passed
@aallan
aallan deleted the fix/sweep-triage-classification branch July 28, 2026 02:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant