Skip to content

v0.0.124: trap source backtrace (#516 Stage 2)#546

Merged
aallan merged 7 commits into
mainfrom
v0.0.124-trap-source-mapping
Apr 28, 2026
Merged

v0.0.124: trap source backtrace (#516 Stage 2)#546
aallan merged 7 commits into
mainfrom
v0.0.124-trap-source-mapping

Conversation

@aallan

@aallan aallan commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Closes #516 Stage 2 (Stage 3 — per-kind Fix: paragraphs — remains).

What changed

Pre-fix, runtime traps surfaced only the kind taxonomy from Stage 1 (divide_by_zero / out_of_bounds / etc.) plus the trap message. The user got "Integer division by zero" with no indication of which of their functions divided by zero — they had to read the raw wasmtime backtrace with hex offsets and translate WAT names back to source.

Stage 2 walks wasmtime.Trap.frames after classification, looks each frame's WAT name up in a new CompileResult.fn_source_map (built during codegen by _register_fn for top-level functions and by the closure-lifting pass for $anon_N helpers), and produces a structured backtrace: list of {func, file, line_start, line_end, is_builtin} dicts, leaf-first to match wasmtime / gdb / Python convention.

Before:

Error: Integer division by zero

After:

Error: Integer division by zero
Source backtrace:
  in divide  (/tmp/trap_demo.vera:1-5)
  in main  (/tmp/trap_demo.vera:7-13)

JSON envelope adds:

"frames": [
  {"func": "divide", "file": "/tmp/trap_demo.vera",
   "line_start": 1, "line_end": 5, "is_builtin": false},
  {"func": "main",   "file": "/tmp/trap_demo.vera",
   "line_start": 7, "line_end": 13, "is_builtin": false}
]

Why per-function, not per-line

Per-line requires correlating WAT instructions to WASM byte offsets after assembly, which wasmtime-py doesn't expose (no public WAT-to-WASM debug-info channel). Per-function is what the issue itself accepts as Stage 2 success: "even a coarse 'trap inside cell_at at or after line 84' would be a big step up".

Resolution rules in _resolve_trap_frames

  • Built-ins are tagged as <builtin>, not dropped. WAT functions named alloc / gc_collect / contract_fail, plus anything starting with exn_ / vera. / closure_sig_, are runtime infrastructure with no Vera source. Surfacing them would claim a misleading file:line; tagging them keeps the chain visible without lying.
  • Monomorphized generics suffix-strip at the rightmost $. identity$Intidentity. $ cannot appear in user-written Vera identifiers, so any $ in a WAT name was inserted by the monomorphizer (_mangle_fn_name).
  • Lifted closures register under anon_N with the source span of the original fn(...) syntactic site, so a trap inside a closure points at the closure body, not at the synthetic top-level wrapper.
  • Unknown user-named frames are surfaced with <unknown> location, not dropped — better to show the WAT name with no location than lose the frame entirely.

CLI surface

cmd_run text mode appends a Source backtrace: block after the error line. Leading runtime-helper frames are collapsed with a (suppressed N runtime-helper frames above first user code) marker so the user sees their own code at the top of the trace. JSON mode adds a frames array to each diagnostic with the full structured backtrace (including built-ins, so machine consumers see the full picture for telemetry).

Tests

15 new tests across 3 classes in tests/test_runtime_traps.py:

  • TestResolveTrapFrames516 (7 tests) — unit tests for the resolver with a _FakeFrame shim: user-fn resolution, built-in tagging, built-in-prefix matching, monomorphized base-name fallback, unknown-name surfacing, defensive empty-frames behaviour, leaf-first ordering.
  • TestTrapSourceBacktrace516 (5 tests) — end-to-end via cmd_run: text-mode surface, leaf-first ordering across stream output, JSON envelope frames array, contract-violation backtrace, direct execute() WasmTrapError.frames attachment.
  • TestSourceMapPopulation516 (3 tests) — light-weight inspection of CompileResult.fn_source_map: top-level fns registered, lifted closures registered under anon_N, built-in helpers NOT registered (regression guard against bogus user-frame entries).

Test plan

  • pytest tests/ -q — 3,576 passed, 14 skipped (was 3,561+14)
  • mypy vera/ — clean
  • python scripts/check_conformance.py — all 81 pass
  • python scripts/check_examples.py — all 33 pass
  • python scripts/check_doc_counts.py — consistent (3,590 tests)
  • python scripts/check_version_sync.py — version 0.0.124 consistent
  • All pre-commit hooks pass
  • Manual: trap inside user fn shows file:line; trap inside built-in collapses leading helper frames; JSON envelope packs full frame chain

Co-Authored-By: Claude noreply@anthropic.invalid

Summary by CodeRabbit

  • New Features

    • Runtime trap errors now include structured source backtraces (file/line). Text-mode prints a “Source backtrace” block (suppresses leading runtime-helper frames); JSON diagnostics always include a full frames array and never write that array to stderr.
  • Tests

    • Added extensive unit and end-to-end tests for frame resolution, text/JSON backtrace rendering, contract-violation traces and guaranteed backtrace population.
  • Documentation

    • Updated changelogs, roadmap, README, HISTORY and testing docs for v0.0.124 and Stage 2 backtrace shipping.

Closes #516 Stage 2 (Stage 3 -- per-kind Fix paragraphs -- remains).

Pre-fix, runtime traps surfaced only the kind taxonomy from Stage 1
(divide_by_zero / out_of_bounds / etc.) plus the trap message.  The
user got "Integer division by zero" with no indication of which of
their functions divided by zero -- they had to read the raw wasmtime
backtrace with hex offsets and translate WAT names back to source.

Stage 2 walks wasmtime.Trap.frames after classification, looks each
frame's WAT name up in a new CompileResult.fn_source_map (built
during codegen by _register_fn for top-level functions and by the
closure-lifting pass for $anon_N helpers), and produces a structured
backtrace: list of {func, file, line_start, line_end, is_builtin}
dicts, leaf-first to match wasmtime / gdb / Python convention.

Per-function granularity, not per-line: wasmtime-py doesn't expose
WAT-to-WASM debug-info plumbing, so byte-offset -> source-line
mapping isn't viable.  The issue's own success criterion is "even a
coarse 'trap inside cell_at at or after line 84' would be a big step
up", which is per-function.

Resolution rules in _resolve_trap_frames (vera/codegen/api.py):

* Built-ins are tagged as <builtin>, not dropped.  WAT functions
  named alloc / gc_collect / contract_fail, plus anything starting
  with exn_ / vera. / closure_sig_, are runtime infrastructure with
  no Vera source.  Surfacing them would claim a misleading
  file:line; tagging them keeps the chain visible without lying.

* Monomorphized generics suffix-strip at the rightmost $ (identity$Int
  -> identity).  $ cannot appear in user-written Vera identifiers, so
  any $ in a WAT name was inserted by the monomorphizer.

* Lifted closures register under anon_N with the source span of the
  original fn(...) syntactic site, so a trap inside a closure points
  at the closure body, not at the synthetic top-level wrapper.

* Unknown user-named frames are surfaced with <unknown> location, not
  dropped.  Better to show the WAT name with no location than lose
  the frame entirely; any future source-map gap is diagnosable from
  the unknown markers.

cmd_run text mode appends a "Source backtrace:" block after the
error line.  Leading runtime-helper frames are collapsed with a
"(suppressed N runtime-helper frames above first user code)" marker
so the user sees their own code at the top of the trace.  JSON mode
adds a frames array to each diagnostic with the full structured
backtrace (including built-ins, so machine consumers see the full
picture for telemetry).

Tests: 15 new tests across 3 classes in tests/test_runtime_traps.py:

* TestResolveTrapFrames516 (7) -- unit tests with a _FakeFrame shim:
  user-fn resolution, built-in tagging, built-in-prefix matching,
  monomorphized base-name fallback, unknown-name surfacing,
  defensive empty-frames behaviour, leaf-first ordering.

* TestTrapSourceBacktrace516 (5) -- end-to-end via cmd_run: text-mode
  surface, leaf-first ordering across stream output, JSON envelope
  frames array, contract-violation backtrace, direct execute()
  WasmTrapError.frames attachment.

* TestSourceMapPopulation516 (3) -- light-weight inspection of
  CompileResult.fn_source_map: top-level fns registered, lifted
  closures registered under anon_N, built-in helpers NOT registered
  (regression guard against bogus user-frame entries).

3,576 tests pass + 14 skipped (was 3,561+14).  mypy clean.  All 81
conformance + 33 examples pass.  uv.lock regenerated for version
bump.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown

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

Adds Stage 2 runtime-trap source mapping: compiler emits fn_source_map and prelude_fn_names; runtime trap frames are resolved into structured frames (file, line span, is_builtin) via _resolve_trap_frames, attached to WasmTrapError, surfaced in CLI text/JSON, and covered by new tests.

Changes

Cohort / File(s) Summary
Version metadata
pyproject.toml, vera/__init__.py, README.md
Bump package/module version to 0.0.124 and update release/test counts.
Release notes & docs
CHANGELOG.md, HISTORY.md, KNOWN_ISSUES.md, ROADMAP.md, TESTING.md
Add v0.0.124 release notes; mark Stage 2 (source backtraces) as shipped; update test counts and release/compare links.
Compiler source-map plumbing
vera/codegen/core.py, vera/codegen/registration.py, vera/codegen/closures.py
Collect and propagate _fn_source_map and _prelude_fn_names during compilation: register function spans, record lifted anon_* closures (when span present), scrub prelude/monomorphized names, and export both on CompileResult.
Trap resolution & API
vera/codegen/api.py
Add CompileResult.fn_source_map and prelude_fn_names; introduce TrapFrame dataclass with to_dict(); extend WasmTrapError with frames; implement _find_frames_in_exception_chain and _resolve_trap_frames (strip $, handle anon_N, builtin/prelude detection, <unknown> fallback); execute() now populates WasmTrapError.frames.
CLI output
vera/cli.py
JSON diagnostics always include diag["frames"] (list of dicts via to_dict()); text-mode appends a formatted "Source backtrace:" block, omitting a leading run of runtime-helper builtin frames above the first user frame and formatting builtin/unknown/user spans.
Tests
tests/test_runtime_traps.py
Large new Stage 2 test suite: unit tests for _resolve_trap_frames, end-to-end CLI/execute backtraces (text & JSON), contract-violation backtraces, WasmTrapError.frames on execute(), deterministic helper-frame collapsing, and compile-time fn_source_map entries for top-level and lifted closures.

Sequence Diagram(s)

sequenceDiagram
    participant Codegen as CodeGenerator
    participant Registry as Registration/Closures
    participant CompileRes as CompileResult
    participant Wasm as wasmtime
    participant Resolver as _resolve_trap_frames
    participant CLI as cmd_run/execute

    Codegen->>Registry: register functions, lift closures
    Registry-->>Codegen: populate _fn_source_map (name -> file,start,end)
    Codegen->>CompileRes: return CompileResult(fn_source_map, prelude_fn_names)
    Wasm-->>CompileRes: trap with wasmtime.Trap.frames
    CompileRes->>Resolver: resolve_frames(wasm_frames, fn_source_map, prelude_fn_names)
    Resolver-->>CompileRes: list of {func,file,line_start,line_end,is_builtin}
    CompileRes->>CLI: raise/return WasmTrapError with frames
    CLI->>CLI: format output (text: "Source backtrace:", JSON: diagnostics[0].frames)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • #547: Adds WasmTrapError.frames and CompileResult.fn_source_map — closely aligns with this PR's structured backtrace work.

Possibly related PRs

  • #533: Earlier trap-diagnostics work — foundational changes to WasmTrapError / cmd_run that this PR extends.
  • #536: Closure-lifting changes — overlaps in anon closure handling and fn_source_map population.

Suggested labels

compiler, tests, docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% 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 clearly summarises the main change: implementing Stage 2 of issue #516 by adding trap source backtraces to v0.0.124.
Linked Issues check ✅ Passed The PR fully implements Stage 2 objectives from issue #516: trap frame resolution, source-location mapping, structured frame representation, text/JSON output modes, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to Stage 2 requirements: codegen extensions (fn_source_map, prelude_fn_names), TrapFrame dataclass, frame resolution logic, CLI output formatting, and extensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v0.0.124-trap-source-mapping

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

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.78947% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.04%. Comparing base (4bab6eb) to head (54abcf7).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
vera/cli.py 87.50% 2 Missing ⚠️
vera/codegen/api.py 96.92% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #546      +/-   ##
==========================================
+ Coverage   91.02%   91.04%   +0.01%     
==========================================
  Files          58       58              
  Lines       22034    22126      +92     
  Branches      259      259              
==========================================
+ Hits        20056    20144      +88     
- Misses       1971     1975       +4     
  Partials        7        7              
Flag Coverage Δ
javascript 56.83% <ø> (ø)
python 94.98% <95.78%> (+<0.01%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_runtime_traps.py`:
- Around line 974-1000: The test currently only checks that an "anon_N" key
exists in result.fn_source_map; enhance it to validate the mapped source span
for the lifted closure by locating one anon key from result.fn_source_map (use
the same anon_keys selection) and asserting its value contains the expected
(file, line_start, line_end) tuple (i.e., the source span for the inline fn body
in test_lifted_closure_registered_under_anon_id); reference result.fn_source_map
and anon_keys to fetch the mapping and compare the mapping's
file/line_start/line_end fields to the known coordinates of the closure in the
provided source string.
- Around line 844-910: The JSON-mode contract-violation path in
test_contract_violation_carries_backtrace() doesn't assert that stderr is empty
and doesn't verify diagnostics[0]["frames"] when cmd_run is invoked in JSON
mode; update the test (or add a new JSON variant) to call rc =
cmd_run(str(path), as_json=True), assert rc == 1, parse captured.out as JSON to
check that diagnostics[0] contains a "frames" list with the expected "positive"
and "main" user frames, and also assert captured.err == "" to enforce the
JSON-mode invariant (use existing symbols
test_contract_violation_carries_backtrace, cmd_run, captured, and diagnostics).

In `@vera/cli.py`:
- Around line 688-695: The code currently only sets diag["frames"] when
exc.frames is truthy; change it to always include the frames key so the JSON
shape is consistent: unconditionally assign diag["frames"] = exc.frames
(ensuring exc is the WasmTrapError instance carrying the frames from
_resolve_trap_frames()), instead of the existing if exc.frames: branch so
consumers receive "frames": [] when there are no resolved frames.

In `@vera/codegen/api.py`:
- Around line 247-276: The frame name needs normalization: before checking
_BUILTIN_NAMES, _BUILTIN_PREFIXES, and fn_source_map, strip a single leading '$'
from the local variable name (e.g., normalize name = name[1:] if
name.startswith('$')) so names like "$alloc" or "$main" will match builtin sets
and the fn_source_map lookup; update the docstring for the func field
(previously "wasmtime strips it already") to say "any leading `$` stripped (some
wasmtime versions omit it)". Ensure you apply the normalized name for both the
is_builtin check and the fn_source_map lookup while preserving the original name
where you still want to display the raw frame if necessary.
🪄 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

Run ID: 9adf33f3-1ab0-48e2-b2ce-0ffe8fa793e9

📥 Commits

Reviewing files that changed from the base of the PR and between 4bab6eb and 7f8bc71.

⛔ Files ignored due to path filters (5)
  • docs/index.html is excluded by !docs/**
  • docs/index.md is excluded by !docs/**
  • docs/llms-full.txt is excluded by !docs/**
  • docs/llms.txt is excluded by !docs/**
  • uv.lock is excluded by !**/*.lock, !uv.lock
📒 Files selected for processing (14)
  • CHANGELOG.md
  • HISTORY.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • pyproject.toml
  • tests/test_runtime_traps.py
  • vera/__init__.py
  • vera/cli.py
  • vera/codegen/api.py
  • vera/codegen/closures.py
  • vera/codegen/core.py
  • vera/codegen/registration.py

Comment thread tests/test_runtime_traps.py
Comment thread tests/test_runtime_traps.py
Comment thread vera/cli.py Outdated
Comment thread vera/codegen/api.py
Four CodeRabbit findings actioned plus filed #547 to track #516 Stage 3
(per-kind Fix paragraphs) per the workflow rule "PR-generated new
issues must be added to a tracker in the same PR".

Code fixes:

* vera/codegen/api.py _resolve_trap_frames: defensive leading-$
  strip on frame name before the builtin-allowlist check and the
  fn_source_map lookup.  Verified empirically that current
  wasmtime-py strips $ already (a divide-by-zero trap inside
  `(func $bad ...)` returns func_name='bad'), but the normalisation
  is one cheap line and protects against any future wasmtime
  version that leaves the $ in.  Without it, a regression there
  would silently break every backtrace lookup with no clear
  diagnostic.

* vera/cli.py JSON envelope: always include the "frames" key (even
  as an empty list) instead of the old `if exc.frames:` guard.
  Matches the always-present `trap_kind` shape; consumers can
  iterate `diag["frames"]` directly without `.get(..., [])`
  ceremony.  Schema stability for a structural field outweighs the
  envelope-minimalism win.

Test fixes:

* tests/test_runtime_traps.py
  test_lifted_closure_registered_under_anon_id: was checking only
  that an anon_N key exists; now also asserts the registered span
  value (file = "<unknown>" via the compile-from-string path,
  line_start = 4, line_end = 6 — the exact lines of the inline
  closure literal in the test fixture).  A future bug that
  registered the wrong AST node (e.g. picking up the enclosing
  array_map call instead of the AnonFn) would fail this test
  with a clear coordinate mismatch instead of silently surfacing
  the wrong file:line on a real trap inside the closure body.

* tests/test_runtime_traps.py: added
  test_contract_violation_json_mode_includes_frames as a JSON
  variant of the existing text-mode contract-violation backtrace
  test.  Pins the JSON envelope's frames array AND the JSON-mode-
  no-stderr-leak invariant from #543.  Without it, a future
  refactor could surface the backtrace in text mode but drop it
  from the JSON path, and CI would stay green.

Docs:

* TESTING.md: 3,590 -> 3,591 total tests; test_runtime_traps.py
  row updated 39->40 / 1,021->1,098 lines.
* ROADMAP.md, README.md, HISTORY.md: 3,590 -> 3,591 across all
  three references.

#547 (Stage 3) is filed with concrete content (per-kind Fix
paragraph drafts, refactor scope for _classify_trap, CLI surface
plan, test plan).  Will be picked up after #517 (TCO) lands so
the stack_exhausted Fix paragraph can reference return_call as
shipped instead of "see #517 for the planned fix".

Co-Authored-By: Claude <noreply@anthropic.invalid>

@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 the current code and only fix it if needed.

Inline comments:
In `@HISTORY.md`:
- Line 261: Trim the HISTORY.md entry for v0.0.124 to a single concise,
user-facing sentence (e.g., "v0.0.124 — Runtime traps now include source
backtraces") and remove the implementation and UI details (references to
WasmTrapError.frames, `Source backtrace:` block, `frames` JSON field,
per-function granularity, and `vera run` text mode wording) from that row; move
the removed technical/UI specifics into CHANGELOG.md under the v0.0.124 section
so the HISTORY remains a one-line summary while CHANGELOG contains the detailed
notes.

In `@README.md`:
- Line 184: Update the README paragraph that currently claims every runtime
error ships with a tailored "Fix:" section so it accurately reflects the shipped
state for v0.0.124: change the diagnostics wording to state that source
backtraces are now included (Stage 2 for traps) but that per-kind "Fix:"
suggestion paragraphs (Stage 3) are still pending; specifically edit the
sentence containing "Vera is in **active development** at v0.0.124" and any
adjacent diagnostic claim lines to remove/soften the blanket "every runtime
error" / tailored "Fix:" wording and replace it with a concise statement that
backtraces are available while "Fix:" suggestions remain forthcoming.

In `@ROADMAP.md`:
- Line 11: Update the roadmap’s opening version string to match the documented
totals by replacing the phrase "Vera v0.0.119" with "Vera v0.0.124" in the top
summary sentence so the release baseline matches the v0.0.124-era counts
described below.

In `@TESTING.md`:
- Line 82: Update the TESTING.md summary to explicitly list the Stage 2
assertions: mention `_resolve_trap_frames` unit tests enforce leaf-first
ordering of frames and include cases for
user-fn/built-in/monomorphized/unknown-name/no-frames; and call out the
text-mode `cmd_run` end-to-end checks that assert the suppression marker for
collapsed leading runtime-helper frames (as well as the JSON-mode
backtrace/trap_kind invariants already described). Ensure the phrasing names
`_resolve_trap_frames`, `cmd_run`, "leaf-first", and "suppression marker" so the
test inventory precisely matches the shipped contract.
🪄 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

Run ID: 79f38120-3123-494b-a6a6-14df5448ee74

📥 Commits

Reviewing files that changed from the base of the PR and between 7f8bc71 and d858fd8.

📒 Files selected for processing (7)
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_runtime_traps.py
  • vera/cli.py
  • vera/codegen/api.py

Comment thread HISTORY.md Outdated
Comment thread README.md Outdated
Comment thread ROADMAP.md Outdated
Comment thread TESTING.md Outdated
Comment thread vera/codegen/api.py
aallan and others added 2 commits April 27, 2026 22:04
Three of four CodeRabbit findings actioned plus two new tests for
the suppression-marker code path that round 4's TESTING.md update
implied existed.

Actioned:

* HISTORY.md v0.0.124 row trimmed to a single concise sentence.
  The previous wording carried implementation detail
  (WasmTrapError.frames, Source backtrace: block, JSON envelope
  shape, granularity rationale) that belongs in CHANGELOG, not in
  the HISTORY one-line-summary table.  This is the *third* time
  the user has corrected the same drift (memory note tagged at
  v0.0.121 noted it was "the third time" then; this is now four).

* ROADMAP.md opening line "Vera v0.0.119 delivers..." -> "Vera
  v0.0.124 delivers...".  The compiler-pipeline blurb itself is
  unchanged (no pipeline additions since v0.0.119); the version
  number was just stale.

* tests/test_runtime_traps.py: added
  test_text_mode_collapses_leading_runtime_helper_frames and
  test_text_mode_does_not_collapse_when_all_frames_are_builtins.
  Pre-fix the suppression-marker logic in cli.py was untested
  (real GC / allocator traps with builtin-leaf frames are
  timing-sensitive and not deterministically reproducible from a
  Vera fixture); new tests monkeypatch vera.codegen.execute to
  raise a WasmTrapError with synthetic frame chains, so the pure
  suppression logic is exercised against deterministic input.
  First test: 2 builtin leaf frames + 1 user frame -> assert
  "suppressed 2 runtime-helper frames" appears, builtin frames
  collapsed, user frame surfaced at top.  Second test: builtin-
  only chain -> assert NO suppression marker, both helpers
  displayed (the "only collapse if a user frame remains" guard).

* TESTING.md: row description updated to name _resolve_trap_frames,
  cmd_run, "leaf-first" ordering, and "suppression marker"
  explicitly per CodeRabbit's request.  Coverage is now accurate
  (was previously claiming "order" without naming "leaf-first";
  not claiming suppression-marker coverage which now exists).

Not actioned:

* README.md "Fix:" claim — the README's "Every error includes...
  how to fix it" sentence at line 74 is in the "Errors are
  instructions" section illustrated by an E001 example.  The claim
  is about compile-time errors (E001-E702 with stable Diagnostic
  shape carrying description / rationale / fix / spec_ref) and has
  been accurate since v0.0.43 when stable error codes shipped.
  Runtime trap diagnostics are a separate surface that's still in
  progress (Stage 2 shipped; Stage 3 tracked as #547).  The
  CodeRabbit instruction was to edit "the sentence containing
  'Vera is in **active development** at v0.0.124'" but there's no
  Fix-claim adjacent to that sentence (it's at line 74, the
  flagged line is 184) and the actual Fix-claim it's about
  isn't invalidated by Stage 3 still being open.  Replying inline
  on the PR with this analysis.

Doc counts: 3,591 -> 3,593 across TESTING.md / ROADMAP.md /
README.md / HISTORY.md (two new tests added in this round).

Co-Authored-By: Claude <noreply@anthropic.invalid>
CodeRabbit flagged a real bug I missed in the previous round 3 commit:
prelude / inject_prelude functions were falling through to <unknown>
in the trap-frame resolver instead of being tagged as <builtin>, and
worse — they were ALREADY landing in fn_source_map with completely
bogus coordinates pointing at the prelude's *embedded* source string,
not the user's file.

Concrete failure mode pre-fix:

  Source map after compiling a 3-line user program with option_unwrap_or:
    'option_and_then'  -> ('/tmp/foo.vera', 31, 40)
    'option_map'       -> ('/tmp/foo.vera', 20, 29)
    'option_unwrap_or' -> ('/tmp/foo.vera',  9, 18)
    'result_map'       -> ('/tmp/foo.vera', 55, 64)
    'result_unwrap_or' -> ('/tmp/foo.vera', 44, 53)
    'run'              -> ('/tmp/foo.vera',  1,  3)

The user's file is 3 lines long.  The 9-64 range coordinates come
from inject_prelude's parse_to_ast on a synthetic Vera string — they
have line numbers from THAT parse, attached to the user's file path.
A trap inside option_unwrap_or would have surfaced as
"in option_unwrap_or (/tmp/foo.vera:9-18)" pointing at a region of
the user's file that doesn't exist.

Original detection criterion was decl.span is None (in registration.py)
on the assumption that synthetic AST nodes wouldn't have spans.
Wrong: inject_prelude calls parse_to_ast on inline Vera source, so
the resulting FnDecls DO have spans (just from the synthetic source).

Fix: detect prelude functions by their position in the registration
flow instead of by an AST-level marker.  In core.py, the post-prelude
loop (which calls _register_fn for FnDecls not in `existing_fns`) is
exactly the set of injected prelude declarations.  Each call there
now (1) registers via _register_fn as before, then (2) immediately
moves the entry from _fn_source_map to _prelude_fn_names.  Result:
prelude functions are tagged as <builtin> by the resolver and the
source map only contains user code + lifted closures with valid
coordinates.

Resolver changes (api.py _resolve_trap_frames):

* New optional `prelude_fn_names: set[str] | None` parameter.
  Default None preserves the existing 2-arg signature for backward
  compat (test helpers, future LSP consumers).

* New is_prelude check: matches the exact name first, then tries
  the base name for monomorphized generics (so `array_map$Int` —
  even though array_map is a translator builtin not a prelude FnDecl
  — would resolve correctly if it ever became one in the future).
  This mirrors the suffix-strip rule already used for the source-
  map lookup.

* Wired through execute() so the WasmTrapError.frames returned to
  CLI / library consumers reflects the corrected classification.

Tests: added 4 new tests, total 46 in test_runtime_traps.py:

* test_prelude_function_tagged_as_builtin — synthetic prelude name
  via _FakeFrame, asserts is_builtin=True and file=<builtin>
* test_monomorphized_prelude_tagged_as_builtin — synthetic
  array_map$Int frame against {array_map} prelude set, asserts
  base-name fallback works
* test_prelude_fn_names_optional_for_backward_compat — pins the
  optional shape of the new parameter
* test_prelude_functions_registered_as_builtins — full compile of
  a program using option_unwrap_or; asserts the prelude combinators
  land in prelude_fn_names AND are NOT in fn_source_map (the bogus-
  coordinates regression that motivated the fix)

Doc counts: 3,593 -> 3,597 across TESTING.md / ROADMAP.md / README.md
(four new tests).

Co-Authored-By: Claude <noreply@anthropic.invalid>

@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: 3

♻️ Duplicate comments (1)
HISTORY.md (1)

261-261: ⚠️ Potential issue | 🟡 Minor

Trim the row to a single outcome sentence.

This still reads like CHANGELOG detail; keep only the user-facing outcome here and move the Stage 3 follow-up to CHANGELOG.md.

Based on learnings: HISTORY file is single-line summaries, and detail is for the CHANGELOG.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@HISTORY.md` at line 261, Edit the HISTORY.md table row for v0.0.124 so it
contains only a single user-facing outcome sentence (e.g. "Runtime traps now
include a source backtrace.") and remove the Stage 3 / per-`kind` follow-up
text; move the Stage 3 tracking note (the reference to Stage 3 and issue `#547`)
into CHANGELOG.md as a detailed follow-up entry. Target the table row starting
with "v0.0.124" and the phrase "Runtime traps now include a source backtrace"
when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_runtime_traps.py`:
- Around line 775-778: The comment is outdated: prelude/builtin classification
no longer depends on decl.span being None; instead Update the wording to state
that prelude functions are tracked via the CompileResult.prelude_fn_names set
(populated by _register_fn when a function is registered as part of the prelude)
and that the resolver consults prelude_fn_names alongside the runtime-helper
allowlist when deciding builtin/prelude handling; apply the same replacement for
the other similar comments (previous span-based wording) near the other
occurrence.

In `@vera/codegen/api.py`:
- Around line 105-107: Update the stale comment about prelude detection to
remove the span-based heuristic and document the current registration-flow
criterion: explain that synthetic prelude Fns are identified by their
registration position during compile_program (and are registered by _register_fn
for injected preludes), not by decl.span being None; also mention that
_resolve_trap_frames consumes that registration-based metadata so future readers
won’t revert to the old decl.span check.

In `@vera/codegen/core.py`:
- Around line 137-164: The comments above self._fn_source_map and
self._prelude_fn_names are stale and describe an old heuristic (span is None /
monomorphised-name behavior); update them to reflect the current flow: note that
inject_prelude via parse_to_ast now assigns real spans and that prelude
classification is determined by registration order during _register_fn (not by
checking FnDecl.span), and remove or rewrite the monomorphised-name warning to
state that trap-time resolver strips rightmost `$` suffix when looking up base
names; also mention that built-in helpers remain tagged as "<builtin>" and that
_prelude_fn_names is populated based on registration flow rather than span
absence.

---

Duplicate comments:
In `@HISTORY.md`:
- Line 261: Edit the HISTORY.md table row for v0.0.124 so it contains only a
single user-facing outcome sentence (e.g. "Runtime traps now include a source
backtrace.") and remove the Stage 3 / per-`kind` follow-up text; move the Stage
3 tracking note (the reference to Stage 3 and issue `#547`) into CHANGELOG.md as a
detailed follow-up entry. Target the table row starting with "v0.0.124" and the
phrase "Runtime traps now include a source backtrace" when making the change.
🪄 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

Run ID: 3a7368cc-8339-455c-ab99-1e47ac0cbee9

📥 Commits

Reviewing files that changed from the base of the PR and between d858fd8 and 0703045.

📒 Files selected for processing (8)
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_runtime_traps.py
  • vera/codegen/api.py
  • vera/codegen/core.py
  • vera/codegen/registration.py

Comment thread tests/test_runtime_traps.py Outdated
Comment thread vera/codegen/api.py Outdated
Comment thread vera/codegen/core.py
All four CodeRabbit findings actioned.  Three are stale-comment
fixes pointing at the previous commit's prelude-detection refactor
that left explanatory comments referring to the old `decl.span is
None` heuristic; one is the fifth-time HISTORY drift on the same
v0.0.124 row.

Stale-comment fixes (all three reflect the actual current criterion
"prelude detection is by registration-flow position in compile_program,
not by AST-level span absence" — the failure mode being avoided is
that inject_prelude calls parse_to_ast on inline Vera source, so its
synthetic FnDecls do have spans, just spans pointing at synthetic
source coordinates):

* tests/test_runtime_traps.py — TestResolveTrapFrames516
  test_prelude_function_tagged_as_builtin docstring updated.
* vera/codegen/api.py — CompileResult.prelude_fn_names field
  comment updated; explicitly notes detection is registration-flow
  not span-based, with the failure mode that motivated the change.
* vera/codegen/core.py — both _fn_source_map and _prelude_fn_names
  field comments updated; documents the post-inject_prelude loop
  in compile_program as the actual registration site, retains the
  monomorphized-name suffix-strip rule documentation, retains the
  built-in helper exclusion rationale.

HISTORY trim (5th time on the same drift):

* HISTORY.md v0.0.124 row collapsed to a single outcome sentence:
  "Runtime traps now include a source backtrace ([#516] Stage 2)."
  The Stage 3 / #547 follow-up reference moved into the CHANGELOG
  (new "Stage 3 follow-up" subsection in the v0.0.124 entry) where
  forward-looking process information belongs.  Per the memory note
  hardened in the previous round: "more than one ` — ` separator OR
  more than one sentence = already drifted, cut".  The row now has
  zero separators and one sentence — within bounds.

Co-Authored-By: Claude <noreply@anthropic.invalid>

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
vera/codegen/core.py (1)

317-319: ⚠️ Potential issue | 🟡 Minor

Filter monomorphised prelude functions out of _fn_source_map here as well.

The post-inject_prelude cleanup only removes the original prelude names. When a generic prelude helper is instantiated later, _register_fn(mdecl) adds the $-suffixed clone back with its synthetic prelude span, so CompileResult.fn_source_map can still contain bogus entries such as array_map$Int. That makes correct trap attribution depend on _resolve_trap_frames consulting prelude_fn_names before any exact source-map hit.

Based on learnings, "CompileResult has a prelude_fn_names set ... The resolver consults this set ... It also applies the rightmost-$ strip rule to monomorphized prelude calls."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vera/codegen/core.py` around lines 317 - 319, When registering monomorphised
prelude helpers in the loop that calls _register_fn for each mdecl, skip adding
entries to _fn_source_map for synthetic prelude clones: compute the effective
base name by removing the rightmost '$' suffix (if present) from mdecl.name and,
if that base name is in CompileResult.prelude_fn_names (or
self._compile_result.prelude_fn_names), do not call _register_fn for that mdecl;
this prevents $-suffixed prelude instantiations (e.g. array_map$Int) from being
reintroduced into _fn_source_map and aligns with how _resolve_trap_frames and
prelude_fn_names are used elsewhere.
♻️ Duplicate comments (1)
tests/test_runtime_traps.py (1)

763-784: ⚠️ Potential issue | 🟡 Minor

Correct the stale span-based invariant in these comments.

These docstrings still say injected prelude/builtin functions are identified by missing spans or by decl.span is not None. That is no longer the model: prelude functions do have spans, and they stay out of fn_source_map because the post-prelude registration flow moves them into prelude_fn_names.

Based on learnings, the original decl.span is None heuristic for prelude detection was incorrect; the correct criterion is registration-flow position in compile_program.

Also applies to: 1341-1344

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_runtime_traps.py` around lines 763 - 784, Update the outdated
comments that claim prelude/builtin functions are detected by missing spans
(e.g., "decl.span is None") and instead document the current behavior: prelude
functions are registered via the post-prelude registration loop in
compile_program and are recorded in CompileResult.prelude_fn_names (which is why
they are absent from fn_source_map), not by decl.span; also remove or correct
any references to the old span-based heuristic (including mentions near
fn_source_map, inject_prelude, and decl.span) and mirror this corrected
explanation in the other affected block around lines 1341-1344.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_runtime_traps.py`:
- Around line 1059-1194: Add a JSON-mode sibling to the existing text-mode
synthetic-frame tests: reuse the same synthetic_frames and monkeypatched
fake_execute that raises WasmTrapError, call cmd_run(str(path), as_json=True),
assert rc == 1 and capsys shows captured.err == "" and then parse the JSON
diagnostics (e.g., inspect diagnostics[0]["frames"]) to assert the builtin
helper frames (gc_collect and alloc with is_builtin=True and file "<builtin>")
are present and not filtered or rewritten; keep the existing text-mode
assertions unchanged.

In `@vera/codegen/api.py`:
- Around line 205-218: Define a dataclass (e.g., Stage2Frame or TrapFrame) that
models the trap frame fields used by _resolve_trap_frames and any consumers (CLI
formatter, JSON output) instead of using list[dict[str, object]]; update the
exception class attribute self.frames and the _resolve_trap_frames
signature/return type to use list[TrapFrame], modify the resolver to construct
and return TrapFrame instances, and adjust downstream code
(formatters/serializers) to accept TrapFrame objects (or convert them to dicts
at the serialization boundary) so mypy can type-check field access and prevent
key drift.
- Around line 2791-2799: The code currently passes the top-level exc to
_resolve_trap_frames, which misses frame data if a nested exception (e.g., a
Trap on __cause__ or __context__) carries .frames; change the call site to walk
the exception chain (using __cause__ and __context__, same pattern used for
_VeraExit handling) to find the first exception object that has a .frames
attribute and pass that exception into _resolve_trap_frames instead of the
original exc; keep existing usage of _classify_trap(exc, last_violation)
unchanged and only swap the argument fed to _resolve_trap_frames.

---

Outside diff comments:
In `@vera/codegen/core.py`:
- Around line 317-319: When registering monomorphised prelude helpers in the
loop that calls _register_fn for each mdecl, skip adding entries to
_fn_source_map for synthetic prelude clones: compute the effective base name by
removing the rightmost '$' suffix (if present) from mdecl.name and, if that base
name is in CompileResult.prelude_fn_names (or
self._compile_result.prelude_fn_names), do not call _register_fn for that mdecl;
this prevents $-suffixed prelude instantiations (e.g. array_map$Int) from being
reintroduced into _fn_source_map and aligns with how _resolve_trap_frames and
prelude_fn_names are used elsewhere.

---

Duplicate comments:
In `@tests/test_runtime_traps.py`:
- Around line 763-784: Update the outdated comments that claim prelude/builtin
functions are detected by missing spans (e.g., "decl.span is None") and instead
document the current behavior: prelude functions are registered via the
post-prelude registration loop in compile_program and are recorded in
CompileResult.prelude_fn_names (which is why they are absent from
fn_source_map), not by decl.span; also remove or correct any references to the
old span-based heuristic (including mentions near fn_source_map, inject_prelude,
and decl.span) and mirror this corrected explanation in the other affected block
around lines 1341-1344.
🪄 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

Run ID: 47e149a5-33d1-4343-9ced-757f883e6077

📥 Commits

Reviewing files that changed from the base of the PR and between 0703045 and 61edf6d.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • HISTORY.md
  • TESTING.md
  • tests/test_runtime_traps.py
  • vera/codegen/api.py
  • vera/codegen/core.py

Comment thread tests/test_runtime_traps.py
Comment thread vera/codegen/api.py Outdated
Comment thread vera/codegen/api.py
…in walk + monomorphized-prelude skip

All five CodeRabbit findings actioned.  The two structural ones are
shape-of-the-shipped-API decisions worth getting right before this
PR lands; the three smaller ones are correctness / cleanliness /
test-coverage tightening.

* `TrapFrame` dataclass replaces `list[dict[str, object]]`
  (vera/codegen/api.py).  New `@dataclass(frozen=True)` with five
  fields (func / file / line_start / line_end / is_builtin) and a
  `to_dict()` method for JSON serialisation at the envelope
  boundary.  `_resolve_trap_frames` now returns `list[TrapFrame]`;
  `WasmTrapError.frames` is `list[TrapFrame]`.  Mypy can now type-
  check field access — the previous stringly-typed dict allowed
  silent typos like `frame["fucn"]`.  Safe to change shape now
  because the API hasn't shipped externally yet (Stage 2 is in
  this still-open PR).

* Exception-chain walk for `.frames` via new
  `_find_frames_in_exception_chain` helper (vera/codegen/api.py).
  Some wasmtime call paths wrap a `Trap` (which carries `frames`)
  inside a `WasmtimeError` (which doesn't); inspecting only the
  outer exception silently drops the backtrace.  The helper walks
  `__cause__` / `__context__` until a frame sequence is found —
  same pattern as the existing `_VeraExit` chain walk in
  `execute()`.  Folded into the resolver itself rather than at the
  call site so direct callers (tests, future LSP) get the chain
  walk for free.

* Skip monomorphized prelude registration (vera/codegen/core.py).
  After Pass 1.5 (monomorphize), each clone's `_register_fn` would
  re-add prelude entries to `_fn_source_map` with the same bogus
  coordinates the post-prelude loop just scrubbed out for the base
  names (e.g. `option_unwrap_or$Int` -> the original generic's
  prelude span).  Now: after registering each `mdecl`, suffix-strip
  at the rightmost `$` and if the base is in `_prelude_fn_names`,
  pop the entry from `_fn_source_map`.  The trap resolver already
  handled this correctly via the same suffix-strip rule, so this
  is cleanliness rather than a correctness fix — but cleaner to
  not have dead entries in the map.

* New `test_json_mode_preserves_full_frame_chain_including_builtins`
  in tests/test_runtime_traps.py — JSON-mode sibling of the text-
  mode collapse tests.  Pins the contract that JSON mode preserves
  the FULL backtrace (helpers included, leaf-first order) while
  text mode collapses for display.  Without this, a refactor
  could accidentally apply the text-mode collapse to JSON output
  too and downstream consumers (telemetry, LSP) would silently
  start receiving filtered chains.  Same monkeypatch strategy as
  the text-mode tests so a single mock surface exercises both
  paths.

* Stale span-based comment fixed in test (line 1341-1344).  The
  `test_no_spurious_entries_for_builtins` docstring still claimed
  built-ins are detected via "decl.span is None" — leftover from
  round 4.  Replaced with the accurate explanation: WASM helpers
  ($alloc, $gc_collect, $contract_fail, $exn_*, $vera.*) are
  emitted directly into WAT by the assembly module and never go
  through `_register_fn`, which is why they don't appear in the
  source map.  Prelude functions go through `_register_fn` and
  are then moved to `_prelude_fn_names` by the post-prelude loop
  — that path is covered separately by
  `test_prelude_functions_registered_as_builtins`.

Cli.py + tests updated to use `frame.func` / `frame.file` etc.
instead of `frame["func"]` / `frame.get("func")` ; the JSON
envelope serialises via `[f.to_dict() for f in exc.frames]` so
the wire format stays unchanged.  Mypy clean; full suite passes
3,584 + 14 skipped (was 3,583 + 14).

Doc counts: 3,597 -> 3,598 across TESTING.md / ROADMAP.md /
README.md / HISTORY.md (one new test added).

Co-Authored-By: Claude <noreply@anthropic.invalid>

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ROADMAP.md`:
- Line 11: Update the Milestone 3c text that still says “expand from 30 to 50+
examples” to reflect the current canonical count of 33 examples; locate the
Milestone 3c sentence in ROADMAP.md (the line mentioning example growth) and
replace “30” (and any “50+” phrasing if intended as baseline) with “33” and add
a parenthetical note pointing to check_doc_counts.py as the authoritative source
for the example count so future updates reference the script (edit the Milestone
3c description and any nearby numeric mentions to match the new canonical
count).

In `@tests/test_runtime_traps.py`:
- Around line 831-845: Rename the test and update its docstring to reflect the
actual semantics: change the function name
test_frames_preserved_in_outermost_first_order to something like
test_frames_preserved_in_innermost_first_order (or
test_frames_preserved_leaf_first) and update the docstring to state "Order
matches wasmtime's backtrace (innermost/leaf-first)" so it matches the inline
comment and the assertion that expects ["inner", "outer"]; keep the body,
assertion and the inline comment unchanged.

In `@vera/cli.py`:
- Around line 755-762: Move the suppression message to after the "Source
backtrace:" header so it reads as part of the backtrace: in the block that
checks user_idx (variables user_idx and hidden) and prints to sys.stderr, first
print the "Source backtrace:" header (the print("Source backtrace:",
file=sys.stderr) call), then, if user_idx and user_idx > 0, print the
suppression line using the existing hidden calculation and pluralization logic
to sys.stderr; retain the exact message formatting and file=sys.stderr target
but swap the order so the header comes before the "(suppressed …)" line.
🪄 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

Run ID: a4b469f2-5c5b-4049-98c3-4c44ed0381b0

📥 Commits

Reviewing files that changed from the base of the PR and between 61edf6d and b2c59fe.

📒 Files selected for processing (8)
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_runtime_traps.py
  • vera/cli.py
  • vera/codegen/api.py
  • vera/codegen/core.py

Comment thread ROADMAP.md
Comment thread tests/test_runtime_traps.py Outdated
Comment thread vera/cli.py Outdated
All three CodeRabbit findings actioned.

* ROADMAP Milestone 3c "expand from 30 to 50+ examples" -> "expand
  from 33 to 50+ examples" plus a parenthetical pointing future
  updates at scripts/check_doc_counts.py as the canonical source.
  The "30" figure dated to v0.0.101 / Stage 9; we've shipped three
  examples since (string_utilities + two others) without bumping
  this baseline.

* tests/test_runtime_traps.py: renamed
  test_frames_preserved_in_outermost_first_order ->
  test_frames_preserved_in_leaf_first_order.  The previous name
  was the literal opposite of what the test asserts — the body
  asserts ["inner", "outer"] (leaf-first / innermost-first) and
  the inline comment correctly says "wasmtime returns inner-first;
  we preserve that order so the human reading the backtrace sees
  the leaf first".  Docstring rewritten to use the correct
  terminology with a note explaining why we follow wasmtime / gdb
  (leaf-first) rather than Python's traceback convention (root-
  first).  Body, assertion, and inline comment unchanged — only
  the name and docstring drifted.

* vera/cli.py: moved the suppression message to fire AFTER the
  "Source backtrace:" header rather than before it.  Reads more
  naturally: the suppression line is metadata about the backtrace
  below it, so it belongs under the heading.  Pre-fix:

      (suppressed 2 runtime-helper frames above first user code)
    Source backtrace:
      in main  (foo.vera:1-3)

  Post-fix:

    Source backtrace:
      (suppressed 2 runtime-helper frames above first user code)
      in main  (foo.vera:1-3)

* tests/test_runtime_traps.py
  test_text_mode_collapses_leading_runtime_helper_frames: added
  a position-ordering assertion so a future reorder regression
  fails immediately.  Substring checks alone wouldn't catch it.
  New assertion: header_pos < suppress_pos < main_pos.

Doc counts: TESTING.md test_runtime_traps.py line count refreshed
1,440 -> 1,465 (the new ordering assertion added 25 lines).  Full
suite still 3,598 / 3,584 passed + 14 skipped.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan aallan merged commit ead70e8 into main Apr 28, 2026
20 checks passed
@aallan aallan deleted the v0.0.124-trap-source-mapping branch April 28, 2026 13:55
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.

Runtime traps need Vera-native diagnostics, not raw wasmtime stack traces

1 participant