feat(808): precise overflow trap kind + fix postcondition host-import ordering (#823) - #824
Conversation
… ordering #798's integer-overflow guard trapped via a bare `unreachable`, classified kind="unreachable". Wire it to a new vera.overflow_trap host import (mirroring vera.contract_fail): the guard calls it before `unreachable`, signalling an out-of-band channel _classify_trap checks before the substring scan, so a dynamic overflow classifies kind="overflow" with the overflow Fix paragraph (now naming both the i64/@int and u64/@nat ranges). Both runtimes bind it: the wasmtime host in execute() and the browser/Node runtime.mjs import builder. While wiring the first new host import, found and fixed a latent codegen bug (#823): per-function host-import / resource flags were propagated to the module before postconditions were lowered, so a builtin or allocation used only in an ensures() predicate had its import/memory/GC declaration dropped, failing WAT compilation. Propagation now runs after _compile_postconditions, covering the body, precondition, postcondition, and lifted-closure paths for every host-import family and $alloc/$gc_sp. Nothing between the old and new position reads these flags (consumed only at module assembly), so the move is additive. Tests pin all five +/-/* sites, both int_mul trap branches, the closure and ensures()-postcondition paths, wasmtime<->Node parity, math/alloc in postconditions, and the unchanged #520/#813 controls -- each mutation-validated. Release 0.0.187. Co-Authored-By: Claude <noreply@anthropic.invalid>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a dedicated overflow trap path for integer arithmetic, propagates the new trap signal through runtime classification and the browser harness, defers postcondition flag propagation, and updates tests plus release and documentation files. ChangesOverflow trap kind and postcondition propagation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #824 +/- ##
=======================================
Coverage 92.05% 92.05%
=======================================
Files 89 89
Lines 27258 27299 +41
Branches 321 324 +3
=======================================
+ Hits 25091 25131 +40
- Misses 2159 2160 +1
Partials 8 8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
pr-review-toolkit hardening (no behaviour change): - test_runtime_traps.py: helper-level unit tests pinning the last_overflow classification precedence -- it beats the `unreachable` substring scan and loses to last_violation. An ordering invariant no end-to-end test can reach, since a single trap signals only one channel. - context.py / test_int_overflow_codegen.py: comment precision -- name the closures.py merge path, and cite #820 as the exact widen-kind tracker. Co-Authored-By: Claude <noreply@anthropic.invalid>
pr-review-toolkit summaryRan three specialized reviewers over the diff ( code-reviewer — no actionable findings. Independently confirmed the flag-propagation relocation is load-bearing (the AST pre-scan walks only the function body, never contract expressions, so a postcondition-only import reaches the module solely via the ctx-merge) and verified nothing between the old and new merge position reads the flags. comment-analyzer — no inaccuracies; all 8 issue references verified against code. Two precision nits applied:
silent-failure-hunter — sound on all three axes (staleness, ordering, browser leak); no silent-misclassification path. One observation applied:
Both reviewers also noted a non-actionable, pre-existing observation unrelated to this PR: a closure literal placed inside an |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
…-liners ROADMAP.md: remove the stale #539 row (the `--json` introspection subcommands shipped in v0.0.178 and are recorded in HISTORY); reword the #739 row now that the #421 decomposition has landed (was phrased as still pending); add #785 (GitHits MCP bookmark) -- the one open issue that was tracked in neither file. Audited every referenced number against live GitHub state (twice, incl. an independent agent pass): no other stale primary entries, and no other open issue is unreferenced (excluding #808/#823, which close in this PR). HISTORY.md: trim the verbose v0.0.158-187 rows to one-line headlines and de-bold the wholly-bold v0.0.137/138 rows. HISTORY is a per-version summary, not a CHANGELOG -- mechanism, E-codes, and secondary issues belong in CHANGELOG.md, which keeps the detail. Complements the in-file "one sentence, at most one issue link" rule. Co-Authored-By: Claude <noreply@anthropic.invalid>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
vera/codegen/closures.py (1)
447-462: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep lifted-closure host flag propagation in sync.
The overflow flag is now forwarded, but this closure-context merge still omits other host-backed builtin flags such as Markdown, regex, HTTP, inference, random, and math. If a lifted closure is the only caller, its WAT can reference a host import that the module never declares.
Proposed fix
self._decimal_imports.update(ctx._decimal_imports) self._json_ops_used.update(ctx._json_ops_used) self._html_ops_used.update(ctx._html_ops_used) + self._md_ops_used.update(ctx._md_ops_used) + self._regex_ops_used.update(ctx._regex_ops_used) + self._http_ops_used.update(ctx._http_ops_used) + self._inference_ops_used.update(ctx._inference_ops_used) + self._random_ops_used.update(ctx._random_ops_used) + self._math_ops_used.update(ctx._math_ops_used) # `#808`: a `#798` integer-overflow guard inside a lifted closure body sets🤖 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 `@vera/codegen/closures.py` around lines 447 - 462, The lifted-closure merge in the closure context propagation path is only forwarding overflow-trap state, but it still misses other host-backed builtin usage flags. Update the merge logic in the closure context handling to propagate the same host-import tracking fields used elsewhere for Markdown, regex, HTTP, inference, random, math, and any similar builtin import/usage sets from ctx into self, so module assembly can emit all required imports when a lifted closure is the only caller.
🤖 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 9-16: The changelog entry for the host-import propagation fix is
missing the `html_*` import family even though the actual fix in
`vera/codegen/functions.py` also merges `_html_ops_used` after
`_compile_postconditions`. Update the `CHANGELOG.md` bullet describing
`tests/test_codegen.py::TestPostconditionHostImportPropagation823` so it
explicitly includes `html_*` alongside the other host-import families, keeping
the summary aligned with the shipped behavior.
In `@vera/codegen/functions.py`:
- Around line 431-462: The deferred flag merge in the module assembly path is
still missing the Markdown and regex host import sets, so `ensures(...)`
lowering can set those flags without `_assemble_module` seeing them. Update the
postconditions merge in this section to also propagate `ctx._md_ops_used` and
`ctx._regex_ops_used` into the corresponding `self` fields, alongside the
existing `_map_*`, `_set_*`, and other host-flag updates, so `md_*` and
`regex_*` imports are preserved.
---
Outside diff comments:
In `@vera/codegen/closures.py`:
- Around line 447-462: The lifted-closure merge in the closure context
propagation path is only forwarding overflow-trap state, but it still misses
other host-backed builtin usage flags. Update the merge logic in the closure
context handling to propagate the same host-import tracking fields used
elsewhere for Markdown, regex, HTTP, inference, random, math, and any similar
builtin import/usage sets from ctx into self, so module assembly can emit all
required imports when a lifted closure is the only caller.
🪄 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: 3d3b4286-7a1c-472d-a437-8abb60779126
⛔ Files ignored due to path filters (5)
docs/index.htmlis excluded by!docs/**docs/index.mdis excluded by!docs/**docs/llms-full.txtis excluded by!docs/**docs/llms.txtis excluded by!docs/**uv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (21)
CHANGELOG.mdHISTORY.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomlspec/11-compilation.mdtests/test_browser.pytests/test_codegen.pytests/test_int_overflow_codegen.pytests/test_runtime_traps.pyvera/__init__.pyvera/browser/runtime.mjsvera/codegen/api.pyvera/codegen/assembly.pyvera/codegen/closures.pyvera/codegen/core.pyvera/codegen/functions.pyvera/runtime/traps.pyvera/wasm/context.pyvera/wasm/operators.py
…stale header
The "By the numbers" table is rebuilt from eight evenly-spaced snapshots to six
columns chosen by capability inflection, each labelled with a new Milestone row:
v0.0.1 (first release) -> v0.0.9 (runs end-to-end) -> v0.0.65 (GC + maturity) ->
v0.0.101 (Inference effect) -> v0.0.170 (language server) -> v0.0.187 (soundness
campaign). The v0.0.187 column carries current counts (5,459 tests, 103
conformance, 91% coverage).
Stale text corrected: the opening line ("through Stage 14, across 77 active
development days" -> "through the ongoing soundness campaign (Stage 15), across
90 active development days") and the footer ("1,400+ commits, 183 tagged
releases, 77 days" -> "1,700+ commits, 187 tagged releases, 90 days", from real
git counts). README's commit figure synced to 1,700+ for consistency.
Co-Authored-By: Claude <noreply@anthropic.invalid>
…cate CodeRabbit review of #824 found that `md_*` / `regex_*` builtins used inside a runtime-checked `requires(...)` / `ensures(...)` emit an orphaned call (e.g. `call $vera.regex_match`) with no matching import, failing WAT compilation. Unlike map/set/http/json/html/decimal/math (set on the per-function `ctx` and merged after `_compile_postconditions` by the #823 fix), md/regex have NO `ctx` set-site -- they are registered ONLY by the body host-import pre-scan (`_scan_io_ops`), which was body-only. The pre-scan now also walks the function's contract predicates, so a Markdown/regex builtin reachable only via a contract gets its import declared. Pinned by two new `TestPostconditionHostImportPropagation823` cases (regex and Markdown builtins in a postcondition over a scalar body), mutation-validated: reverting the contract scan reintroduces `unknown func: $vera.{regex_match, md_parse}`. Also corrects the #823 CHANGELOG bullet to list `html_*` and the md/regex pre-scan mechanism (CodeRabbit finding). closures.py is intentionally unchanged: the pre-scan recurses into `AnonFn` bodies, so a lifted closure's host-import ops are already captured on the module (verified -- a closure-only `sin` call emits both the import and the call); the closure merge correctly forwards only the ctx-only families (`_map_imports` / `_set_imports` / `_decimal_imports` + `vera.overflow_trap`). Co-Authored-By: Claude <noreply@anthropic.invalid>
|
@coderabbitai Re: the outside-diff note that The body host-import pre-scan ( The closure merge only needs the ctx-only families the pre-scan can't see — The one genuine gap, |
|
Tip For best results, initiate chat on the files or code changes.
The key distinction is clear:
Good catch on the real remaining gap as well: ✏️ Learnings added
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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 `@HISTORY.md`:
- Line 426: The release snapshot test count in HISTORY.md is out of sync with
the rest of the v0.0.187 docs. Update the Tests value in the HISTORY table to
match the current count used in README.md, ROADMAP.md, and TESTING.md, and keep
the same number consistent across the snapshot.
🪄 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: 1520f92b-c3a2-47e5-8563-58ea26383d7f
⛔ Files ignored due to path filters (5)
docs/index.htmlis excluded by!docs/**docs/index.mdis excluded by!docs/**docs/llms-full.txtis excluded by!docs/**docs/llms.txtis excluded by!docs/**uv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (21)
CHANGELOG.mdHISTORY.mdREADME.mdROADMAP.mdTESTING.mdpyproject.tomlspec/11-compilation.mdtests/test_browser.pytests/test_codegen.pytests/test_int_overflow_codegen.pytests/test_runtime_traps.pyvera/__init__.pyvera/browser/runtime.mjsvera/codegen/api.pyvera/codegen/assembly.pyvera/codegen/closures.pyvera/codegen/core.pyvera/codegen/functions.pyvera/runtime/traps.pyvera/wasm/context.pyvera/wasm/operators.py
CodeRabbit review of #824: the "By the numbers" v0.0.187 snapshot still read 5,459 — the count before the md/regex regression tests landed in 151a264 — while README.md / ROADMAP.md / TESTING.md are at 5,461. Synced the snapshot. (The By-the-numbers table is not a gated count, so check_doc_counts didn't flag it.) Co-Authored-By: Claude <noreply@anthropic.invalid>
Summary
Closes #808 and #823 (both follow-ups to #798).
The #798
@Int/@Natarithmetic-overflow runtime guard trapped via a bareunreachable, so a dynamic overflow was classifiedkind="unreachable"and surfaced the generic non-exhaustive-matchFix paragraph — indistinguishable from an unrelatedunreachable. This wires the guard to a newvera.overflow_traphost import (mirroringvera.contract_fail), so an integer overflow now classifieskind="overflow"and carries the overflow Fix paragraph.Design
The guard emits
call $vera.overflow_trapimmediately before itsunreachable. The host signals an out-of-band channel (last_overflow, mirroringcontract_fail'slast_violation) that_classify_trapchecks before thestr(exc)substring scan — which would otherwise match the trailingunreachablefirst. More robust than message-matching: classification never depends on wasmtime's exact trap text. Both runtimes provide the binding: wasmtime inexecute()and the browser/Noderuntime.mjsimport builder (without it, a--target browserbundleLinkErrors on any arithmetic).#823 — the postcondition import-ordering bug this exposed (also fixed here)
vera.overflow_trapwas the first new host import since codegen's per-function flag-propagation was written, and it exposed a latent bug: codegen propagated a function's accumulated host-import + resource flags from the per-function context to the module before lowering postconditions. So a builtin or allocation reachable only through anensures(...)predicate set its flag too late — the import / memory / GC declaration was omitted and the orphanedcall/global.getfailed WAT compilation. This hit the overflow guard (unknown func: $vera.overflow_trap), every other host-import family (unknown func: $vera.sin, …), and allocation (unknown global: $gc_sp).I originally filed this as a separate follow-up (#823) and deferred it; that was wrong — it's the same one-relocation fix and I was already in the code. The propagation now runs after
_compile_postconditions, fixing the whole class at once. Verified safe: nothing between the old and new position reads these flags (they're consumed only at module assembly), so the move is purely additive in correctness.Adversarial review pass
A multi-lens review (propagation / instantiation-hosts / signal-correctness / coverage / release-docs, each finding verified against current code) caught the #823 regression, an unpinned
int_mul b==INT_MINtrap branch (now tested), and that the overflow Fix paragraph named only the i64 range though #808 newly routes@Nat/u64 overflows through it (paragraph now names both).Exit checklist
vera.overflow_trapcalled beforeunreachable(mirrorsvera.contract_fail);kind="overflow"with the overflow Fix paragraph (now accurate for both i64/@Intand u64/@Nat)test_int_overflow_codegen.pyassertskind == "overflow": all five+/-/*sites, bothint_multrap branches, lifted-closure path,ensures(...)-postcondition path; controls (mutation-validated) keep Nat subtraction silently underflows to negative i64 — refinement-type soundness hole #520/verifier: @Nat → @Int widening of a value > i64.MAX is unsound (Tier-1 proves a false postcondition) #813 atunreachabletest_codegen.py::TestPostconditionHostImportPropagation823: math host-import + allocation in a postcondition over a scalar body (mutation-validated)test_browser.py: wasmtime↔Node parity (mutation-validated: missing binding → LinkError)Gate floor
pytest tests/— green ·mypy vera/— clean · conformance (103) · examples (35)check_version_sync(0.0.187 × 6) ·check_doc_counts(5457) ·check_site_assets· CHANGELOG gate · doc-example validatorsRelease prep (0.0.186 → 0.0.187)
Version bumped across all 6 sync points; CHANGELOG
[0.0.187](two entries: #808 overflow kind, #823 postcondition propagation; folds in the merged-but-unreleased doc-consistency pass); HISTORY Stage 15 row; ROADMAP #808 row deleted; spec/11 + TESTING updated; #823 removed from KNOWN_ISSUES (closed by this PR).Notes for review
check/verify-level features — covered bytest_int_overflow_codegen.py+test_runtime_traps.py+test_codegen.py+test_browser.py.nat_subguards deliberately keep bareunreachable(dedicated kinds deferred, @Nat narrowing: effect-op-argument runtime guard + dedicated guard trap kind #754 / @Nat → @Int widening: remaining deferred coercion sites (follow-up to #813) #820); comments updated to cite Preciseoverflowtrap kind for #798 integer-overflow traps (currentlyunreachable) #808 as the landed precedent.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
overflowkind and clearer “Integer overflow” diagnostics, with consistent behaviour across browser and runtime.requires(...)/ensures(...)postconditions are now correctly propagated during lowering.Tests
Documentation