Skip to content

feat(wasi): experimental WASI Preview 2 target — vera compile/run --target wasi-p2 — v0.0.194#849

Merged
aallan merged 7 commits into
mainfrom
feat/237-wasi-p2-target
Jul 2, 2026
Merged

feat(wasi): experimental WASI Preview 2 target — vera compile/run --target wasi-p2 — v0.0.194#849
aallan merged 7 commits into
mainfrom
feat/237-wasi-p2-target

Conversation

@aallan

@aallan aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner

What

An experimental WASI Preview 2 target (IO + Random surface)vera compile/run --target wasi-p2 (#237) — and release v0.0.194. Stage C of the server-effects sprint (WASI.md).

vera compile --target wasi-p2 program.vera   # binary WebAssembly component
wasmtime run program.wasm                    # stock host, no flags, no Vera bindings
vera run --target wasi-p2 program.vera       # built-in wasip2 host, core-path ExecuteResult contract

The honesty bar from the plan: programs whose host-import surface is IO/Random emit pure wasip2 components runnable by stock wasmtime — and anything else is a diagnostic naming the family, never a silent fallback. This closes #237 as written (its listed ops are exactly the IO family); it is not a blanket "WASI 0.2 compliant" claim.

Design (validated live before implementation)

Every WASI import spelling and both entry lifts were proven by live Component(engine, wat) parse and Linker.add_wasip2() instantiation before the emitter was written — parse-only validation missed a real bug (filesystem error-code case 6 is quota, not disk-quota) that only instantiation caught.

  • Dispatch-table topology (vera/codegen/wasi.py): the component wraps the unchanged core module — each (import "vera" "op") becomes a same-named call_indirect shim through a funcref table MAIN defines and exports; a generated adapter core module implements all 14 IO/Random ops (+ contract_fail/overflow_trap) over canon-lowered WASI imports and elem-plants itself into the table at instantiation, strictly before any lifted export can run. The naive main↔adapter topology is an instantiation cycle (forbidden); the table is defined after the closure table so existing call_indirect sites keep table 0.
  • GC-exempt arena: cabi_realloc bumps over a fixed 64 KiB region below gc_heap_start (reset per op; 128-byte retptr slab), so the mark-sweep GC never scans it and a collection can never move a half-written host block (the Conway's Life at 12x30 still corrupts strings from gen 1 onwards (additional trigger beyond #588) #593/Map<K, T_heap> values stored in _map_store are invisible to conservative GC scan — post-walk reachability of JObject children #695 UAF class, host-side). Data crossing back into Vera is copied out under WAT-level shadow-stack rooting — pinned by a 500-arg GC-pressure stress with collections mid-copy-loop; oversized argv traps cleanly at the arena cap.
  • u8 discriminants: canonical-ABI variant discs read with i32.load8_u, not i32.load — slab reuse leaves garbage in the padding; found live as an EOF err(closed) misread as last-operation-failed with a stale list length as a "handle" (unknown handle index 16).
  • Host runner (vera/runtime/wasi_host.py): always sets a WasiConfig (config-less wasip2 call = process SIGABRT, per the spike), captures stdout/stderr into ExecuteResult (+ the IO.print buffered until exit — animations and TUIs invisible #543 live tee with an incremental UTF-8 decoder — the 4096-byte write cap can split multibyte sequences), snapshots os.environ, preopens the cwd, and classifies traps into the Runtime traps need Vera-native diagnostics, not raw wasmtime stack traces #516 kind taxonomy from the backtrace text + the WASI stderr channel (contract_violation keeps the full violation text; structured frames don't cross the component boundary — spike check 5's documented degradation, frames is empty).
  • Entries: wasi:cli/[email protected] (what stock wasmtime run invokes) + a plain lifted main for scalar returns; String/heap-returning main runs via wasi:cli/run with no value (documented).
  • Documented divergences (inherent to WASI 0.2, each pinned by test — new spec chapter 13): wasi:cli/exit carries ok/err only, so IO.exit(n) degrades to 0/1 under every wasip2 host; no structured trap frames; launch-time env snapshot.

Tests (RED-first; 8 mutations killed)

tests/test_wasi_target.py (139 tests):

  • Emission: parse + instantiate for hello / all-14-op kitchen sink / no-memory programs; interface imports gated by op-dependency closure; both entry exports; String-main lift skip.
  • Execution under the real wasip2 host: print/stderr separation, scalar lifts, wasi:cli/run drive, env hit/miss + 200-var scan, argv (argv[0] skip, 500-arg GC stress, >64 KiB trap), file round-trips + Err("no-entry")/no-preopen errno mapping, 64 KiB chunked read, stdin incl. UTF-8 multibyte + EOF parity, time bracketing, sleep, random bounds, exit, contract-violation text on WASI stderr, overflow.
  • Family gate: http/math/state programs rejected naming the family; no artifact written; JSON envelope.
  • Core-emission pin: default --target wasm WAT untouched.
  • CLI integration (16): binary-component artifact (validated by Component parse — a core module fails it), --wat component text, family-gate diagnostics in text + JSON, vera run value/stdout/JSON envelopes, exit 0/1 degradation, --fn rejection, trap-kind classification through the component boundary.
  • Dual-target conformance differential: all 88 run-level conformance programs under both targets, byte-identical stdout/stderr required — 71 execute identically; 13 family-gated, 3 main-less, 1 wall-clock (all loud skips). The sweep caught a real bug: handle[Exn] components need the exceptions proposal enabled on the runner's engine (fixed; the two EH programs now pass the differential).
  • Stock-host smoke: the compiled artifact under the wasmtime CLI (skips where not installed).
  • Mutations killed: args backing-root removal, u8-disc regression, dispatch-slot collision, arena/heap overlap (emitter, by the implementation agent); classification predicate, env passthrough, exit-code discard, core-bytes-instead-of-component (runner/CLI) — each RED then restored.

Found in passing

#847 — pre-existing core bug (no WASI involved): IO.args elements were not GC-rooted against later allocations; out-of-bounds trap under VERA_EAGER_GC=1, latent otherwise. Localized by a host-parity differential (Python host and WASI adapter corrupt byte-identically → guest-side rooting, not the adapter). Filed as #847; the maintainer-driven PR #846 landed the pair-let rooting fix (that issue is handled there, so this PR carries no KNOWN_ISSUES row for it). Also fixed durably: the doc-builtin-shadowing scanner now skips .claude/ (session worktrees carry repo copies that re-flagged every spec chapter).

Also fixes #848 (round 2)

The #846 print-reorder de-flake proved insufficient at this PR's merge head: 3/12 matrix jobs failed with new modes (xdist worker crash ×2, exit_code=None ×1). Root cause: the interrupter fires on server arrival — produced by the executor worker thread regardless of the main thread's position — and interrupt_main() only sets a pending SIGINT flag, which on slow runners can materialize outside execute()'s protected region. The interrupter now also polls sys._current_frames() until the main thread is verifiably parked in host_async_awaitFuture.result (the interruptible wait the #595 machinery intercepts) before firing, with a bounded deadline so the test can never hang. Sub-second local runtimes (vs the 10 s deadline) prove the gate matches rather than falling through. Full analysis on the reopened #848.

Docs

New spec/13-wasi.md (architecture, arena, supported surface, entries, divergences, conformance evidence); spec §11.1 target pointer; README (commands + honest feature paragraph); SKILL.md §WASI Preview 2 compilation; TOOLCHAIN.md recipe; CLAUDE.md commands + chapter range; WASI.md loose-ends closed (spike keepers landed as this suite); allowlist re-anchors (3 fixer duplicate-key collisions resolved against real fence lines — the #606 pitfall, again).

Release v0.0.194

Version sync (6 sites), CHANGELOG section + link refs, ROADMAP (#237 removed; #406 gate note updated), HISTORY Stage-16 row + By-the-numbers v0.0.194 "WASI 0.2 target" column appended (the lead sentence was also stale at "Six releases" for a 7-column table — now "Eight"/8; maintainer's call whether this column stays permanent like Inference/HttpServer or gets refreshed away by the next routine release), doc counts (5,754 tests / 90 files / 14 spec chapters).

Verification

  • full suite green + stress green; mypy clean (96 files); ruff check (+ --select S) clean without --fix
  • conformance 104 / examples 36 / doc-counts / version-sync / site-assets / limitations / encoding / diagnostic-fields / licenses / wheel gate / spec+SKILL+README+FAQ+EXAMPLES+HTML example gates / doc-builtin-shadowing
  • 8 mutation kills, restored green
  • end-to-end demo: vera compile --target wasi-p2 artifact runs under stock wasmtime run with no flags

Closes #237
Closes #848

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an experimental WASI Preview 2 target for compiling and running components.
    • Documented new wasi-p2 command-line workflows and updated project guidance.
  • Bug Fixes
    • Fixed a garbage-collection crash in certain pair-binding cases.
    • Improved the live interrupt test to reduce CI flakiness.
    • Expanded documentation scanning to skip .claude/ directories.
  • Documentation
    • Updated changelog, history, roadmap, testing, and architecture docs with the new release and target details.

…arget wasi-p2 — v0.0.194

Emit a binary WebAssembly component whose vera.* IO + Random imports are
implemented over WASI 0.2 interfaces (dispatch-table shim topology, GC-exempt
cabi_realloc arena, adapter core module elem-planted before any lift), runnable
by stock wasmtime with no flags and no Vera bindings.  vera run --target wasi-p2
executes it under the built-in add_wasip2 host with the core-path ExecuteResult
contract, including trap-kind classification recovered from the backtrace text
and the WASI stderr channel.  Unsupported host families are a diagnostic naming
the family, never a silent fallback.  Dual-target conformance differential:
71 of 88 run-level programs byte-identical across targets (rest family-gated,
main-less, or wall-clock; loud skips).  New spec chapter 13 documents the
architecture and the inherent WASI 0.2 divergences (exit codes degrade to 0/1,
no structured trap frames).  Also: doc-shadowing scanner skips .claude/;
IO.args GC-rooting bug found in passing filed as #847 with a KNOWN_ISSUES row.

Refs #237

Co-Authored-By: Claude <[email protected]>
@coderabbitai

coderabbitai Bot commented Jul 2, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ed0e4d2d-e53c-4c33-8dd4-fb3d4b13576d

📥 Commits

Reviewing files that changed from the base of the PR and between 8c29880 and 417b824.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • TESTING.md
  • tests/test_runtime_traps.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

📝 Walkthrough

Walkthrough

This PR adds an experimental WASI Preview 2 target across code generation, runtime execution, CLI integration, tests, and documentation, and updates release/version metadata. It also renumbers SKILL.md fenced-example allowlist entries and skips .claude in the doc shadowing scan.

Changes

WASI Preview 2 target implementation

Layer / File(s) Summary
WASI-P2 emission model
vera/codegen/wasi.py
Defines the op model, WASI interface/lowering fragments, host-family gating, MAIN rewriting, adapter generation, and final component assembly for wasi-p2.
Runner and CLI wiring
vera/runtime/wasi_host.py, vera/cli.py
Adds the wasip2 host runner and wires compile/run to emit and execute components with updated help, validation, and target handling.
WASI target tests
tests/test_wasi_target.py, tests/test_runtime_traps.py
Adds end-to-end WASI target coverage, dual-target conformance, smoke/regression cases, and a live-interrupt de-flake for keyboard interrupt timing.
Docs and release metadata
spec/13-wasi.md, spec/11-compilation.md, README.md, CLAUDE.md, SKILL.md, TOOLCHAIN.md, TESTING.md, WASI.md, ROADMAP.md, HISTORY.md, CHANGELOG.md, pyproject.toml, vera/__init__.py
Documents the target, updates spec and project status text, and bumps release/version metadata to 0.0.194.

Unrelated maintenance updates

Layer / File(s) Summary
SKILL allowlist and doc scan maintenance
scripts/check_skill_examples.py, scripts/check_doc_builtin_shadowing.py
Renumbers SKILL fenced-example allowlist keys and skips .claude directories in the doc shadowing scanner.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VeraCLI
  participant emit_wasi_component
  participant execute_wasi_p2
  participant Wasmtime
  VeraCLI->>emit_wasi_component: compile --target wasi-p2
  VeraCLI->>execute_wasi_p2: run --target wasi-p2
  execute_wasi_p2->>emit_wasi_component: emit component from CompileResult
  execute_wasi_p2->>Wasmtime: instantiate component with WasiConfig
  Wasmtime-->>execute_wasi_p2: run main or wasi:cli/run
Loading

Possibly related PRs

  • aallan/vera#601: Also updates scripts/check_skill_examples.py allowlist data keyed by SKILL.md fence line numbers.
  • aallan/vera#650: Directly related scripts/check_skill_examples.py allowlist renumbering for shifted fenced example lines.
  • aallan/vera#842: Also adjusts the same allowlist mapping to track SKILL.md fence renumbering.

Suggested labels: compiler, tests, spec, ci, docs

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Diagnostics Carry An Error Code ⚠️ Warning New wasi-p2 JSON diagnostics in vera/cli.py lack error_code, while Diagnostic.to_dict() serialises stable codes when present. Add stable E/W codes to the new wasi-p2 error paths and emit them in the JSON diagnostics; register the codes in vera/errors.py if needed.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately names the main change: adding an experimental WASI Preview 2 target for vera compile/run --target wasi-p2 and the v0.0.194 release.
Linked Issues check ✅ Passed Changes implement the WASI P2 target and docs for #237, and harden the live interrupt test for #848 with event-gated timing and a timeout fallback.
Out of Scope Changes check ✅ Passed No clearly unrelated code paths were introduced; the docs, tests, scripts, and release metadata all support the WASI P2 work or the interrupt fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Changelog Covers Public-Surface Changes ✅ Passed CHANGELOG.md explicitly covers the new vera compile/run --target wasi-p2 surface, family diagnostics, ExecuteResult/trap divergences, and spec chapter 13; no other public-surface code changed.
Spec And Implementation Move Together ✅ Passed PASS: spec/13-wasi.md matches the new wasi-p2 compiler/runtime behaviour in vera/codegen/wasi.py and vera/runtime/wasi_host.py, including family gates and entry semantics.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/237-wasi-p2-target

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

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.85687% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.00%. Comparing base (be7b8da) to head (417b824).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
vera/codegen/wasi.py 97.82% 9 Missing ⚠️
vera/runtime/wasi_host.py 88.15% 9 Missing ⚠️
vera/cli.py 90.24% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #849      +/-   ##
==========================================
+ Coverage   91.90%   92.00%   +0.09%     
==========================================
  Files          93       95       +2     
  Lines       27838    28363     +525     
  Branches      332      332              
==========================================
+ Hits        25585    26095     +510     
- Misses       2245     2260      +15     
  Partials        8        8              
Flag Coverage Δ
javascript 65.23% <ø> (ø)
python 95.00% <95.85%> (+0.04%) ⬆️

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

aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

Review round 1 — pr-review-toolkit code-reviewer

Empirical review of c5d8ecc (findings verified by running the compile/run paths, not read-only): no blocking findings.

Confirmed by execution: family gate ordering ahead of the JSON success envelope; JSON envelope parity with the core path (incl. the documented exit-code degradation); wasmtime.ExitTrap is a WasmtimeError subclass, so the catch order in wasi_host.py is load-bearing and correct; the contract-message stderr strip is safe even when the program prints text identical to the violation message; emit_wasi_component provably does not mutate CompileResult; the 11-family gate + supported-Random set covers every *_ops_used field, with an unrecognized-import RuntimeError backstop; arena cabi_realloc traps on OOM rather than corrupting; the dual-target differential genuinely executes 71/88 programs; Windows fixture rules and the honesty-rule sweep (no "WASI 0.2 compliant" claim outside explicit disclaimers) hold.

Two low-severity observations, both considered and kept as-is:

  • vera run --target wasi-p2 --fn <nonexistent> reports the generic "not found in exports" message; the tailored "runs 'main' only" message fires only for a real non-main export. That layering is intentional — "not found" is the accurate diagnosis for a nonexistent function on any target, and an existing-but-unliftable export gets the target-specific explanation.
  • A String-returning main reports no value under wasi-p2 (no scalar lift) — documented in the runner docstring, spec chapter 13 (Sections 13.5–13.6), and the CHANGELOG entry; stdout is preserved identically.

No code changes from this round.

… — windows-latest CI

The core host reads stdin through Python's universal-newlines text
layer, so read_line never returns a trailing \r on any platform; the
adapter now strips a trailing \r alongside the \n (RED-first via an
explicit CRLF-bytes stdin test that failed on every platform).  A lone
\r separator stays content — documented divergence, spec section 13.6.
The LF stdin fixture now writes binary so its bytes are deterministic
cross-platform.  The time-bracket test gains 100 ms of cross-clock
slack (wall-clock vs time.time() quantization overshot by 1 ms on
windows-latest) — still catches unit errors, the test's real target.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

CI round 1 (c5d8ecc): two windows-latest failures, both fixed in 8f366e2.

  • read_line CRLF parity — a real adapter gap, not a fixture issue. The core host reads stdin through Python's universal-newlines text layer, so IO.read_line never returns a trailing \r on any platform; the adapter stripped only \n. The stdin fixture (text-mode write → \r\n on disk on Windows) surfaced it. Fixed in the emitted WAT (strip a trailing \r alongside the \n), RED-first via an explicit CRLF-bytes test that failed on every platform; the LF fixture now writes binary so its bytes are deterministic. Residue (a lone \r separator; read_char seeing the \r of a CRLF pair) is a documented divergence in spec §13.6.
  • Time-bracket flake: guest wasi:clocks/wall-clock vs host time.time() are different clock APIs; windows-latest overshot the strict bracket by 1 ms. The bracket now carries 100 ms cross-clock slack — still an order-of-magnitude unit check, which is the test's real target.

Counts rebumped (5,755 tests / test_wasi_target.py 140), full local battery green.

…the sprint

Carrying the row in this PR would only conflict with the #846 rebase
that removes it (maintainer direction on the #849/#846 coordination).
Issue #847 remains open and tracked; #846 closes it on merge.

Skip-changelog: KNOWN_ISSUES-only edit; #847 tracking is on the issue and PR #846

Co-Authored-By: Claude <[email protected]>

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

Caution

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

⚠️ Outside diff range comments (1)
TESTING.md (1)

11-13: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the conformance count everywhere in this file.

The overview now says 104 conformance programs, but the Conformance Suite prose later in this file still says 103. As per the count-sync guidance, replace the old literal everywhere.

🤖 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 `@TESTING.md` around lines 11 - 13, The conformance program count is
inconsistent across TESTING.md: the overview already uses the new total, but the
Conformance Suite prose still has the old literal. Update every remaining 103
reference in that document to match the 104 count, using the existing
“Conformance programs” and “Conformance Suite” wording as anchors so the totals
stay in sync throughout the file.

Source: Learnings

🤖 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`:
- Around line 430-449: The release metrics table in HISTORY.md has an incorrect
spec chapter count for v0.0.194. Update the "Spec chapters" value in the summary
table to match the documented coverage from spec/13-wasi.md, keeping it at 13
rather than 14, and ensure the v0.0.194 column remains consistent with the rest
of the release statistics.

In `@README.md`:
- Around line 222-224: The README summary counts are out of sync with the later
project-structure block, so update every stale literal in this file to match the
current totals. Use the existing status text near the top and the
project-structure section as the anchors, and make sure the examples count and
spec chapter count are consistent everywhere, including any references tied to
the spec chapters and examples.

In `@ROADMAP.md`:
- Line 11: The spec chapter total in the roadmap overview is incorrect and
should remain 13, not 14. Update the summary text that lists test, conformance,
example, and spec chapter counts so it matches the actual additions from
spec/13-wasi.md and keeps the chapter count aligned with chapter 13 only.

In `@scripts/check_skill_examples.py`:
- Around line 104-112: The inline note in the examples table is stale and
references a non-existent allowlist entry, which will confuse future
maintenance. Update the comment in check_skill_examples.py so it points to the
current bare-add example entry in the same block, using the surrounding keys
like the FRAGMENT entries for “Wrong: bare `@Int` + `@Int` without indices” and the
nearby “Correct” entries to confirm the right location.

In `@tests/test_wasi_target.py`:
- Around line 483-494: The `test_random_int_respects_inclusive_bounds` check
only samples `Random.random_int(1, 6)` and can miss a bug where the upper bound
is excluded, so strengthen it with a deterministic boundary case. Extend the
test in `tests/test_wasi_target.py` to also exercise the `Random.random_int`
path with equal lower/upper bounds (for example via a small `_compile_ok`
sample), and assert it returns that exact value so inclusive semantics are
verified without relying on chance.

In `@vera/codegen/wasi.py`:
- Around line 1249-1275: The _get_preopen helper is returning an owned preopen
descriptor from get-directories and the current callers only use it as a
borrowed handle for open-at, which causes component resource leaks on repeated
file operations. Update the preopen handling so the first descriptor is retained
for the process lifetime in the generated WASM, or explicitly drop the owned
descriptor on each file-operation path after open-at; use _get_preopen and the
related open-at call sites as the places to fix this behavior.
- Around line 724-732: The reserved-name validation in wasi.py is too broad
because it scans the entire WAT text for markers like "$wasi_tbl" and
"$__wasi_run", which incorrectly catches string literals and other data
payloads. Update the check in the code path that raises the ValueError so it
only inspects actual WAT identifiers, or explicitly skips data segments before
matching reserved names. Keep the logic centered around the existing reserved
marker loop and the same error message, but narrow the matching so literal
payload content is not treated as a reserved identifier collision.
- Around line 2418-2429: The lifted main export mapping in the wasi codegen path
only handles unit, i64, and f64 results, so i32 scalar mains are missed. Update
the main export logic in the lift/main generation block so `lay.main_results`
also recognizes the i32/bool scalar case and emits the corresponding lifted
`main` function/export, using the existing `lift_result`/`parts.append` flow to
keep the plain `main` export available.

In `@vera/runtime/wasi_host.py`:
- Around line 43-46: The wasi host module is importing WasmTrapError from the
wrong place; switch it to the canonical definition used by the CLI trap handler.
Update the imports in the wasi_host module so WasmTrapError comes from
vera.codegen.api, while keeping only _classify_trap imported from
vera.runtime.traps, and leave the rest of the runner logic unchanged.

---

Outside diff comments:
In `@TESTING.md`:
- Around line 11-13: The conformance program count is inconsistent across
TESTING.md: the overview already uses the new total, but the Conformance Suite
prose still has the old literal. Update every remaining 103 reference in that
document to match the 104 count, using the existing “Conformance programs” and
“Conformance Suite” wording as anchors so the totals stay in sync throughout the
file.
🪄 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: 48bfdf9a-323b-4e9c-b09f-f6cbc394a833

📥 Commits

Reviewing files that changed from the base of the PR and between fa40f5b and c5d8ecc.

⛔ Files ignored due to path filters (6)
  • docs/SKILL.md is excluded by !docs/**
  • 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 (20)
  • CHANGELOG.md
  • CLAUDE.md
  • HISTORY.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • SKILL.md
  • TESTING.md
  • TOOLCHAIN.md
  • WASI.md
  • pyproject.toml
  • scripts/check_doc_builtin_shadowing.py
  • scripts/check_skill_examples.py
  • spec/11-compilation.md
  • spec/13-wasi.md
  • tests/test_wasi_target.py
  • vera/__init__.py
  • vera/cli.py
  • vera/codegen/wasi.py
  • vera/runtime/wasi_host.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread HISTORY.md
Comment thread README.md Outdated
Comment thread ROADMAP.md Outdated
Comment thread scripts/check_skill_examples.py Outdated
Comment thread tests/test_wasi_target.py
Comment thread vera/codegen/wasi.py Outdated
Comment thread vera/codegen/wasi.py
Comment thread vera/codegen/wasi.py
Comment thread vera/runtime/wasi_host.py Outdated
… narrowing, doc sync

- Cache the first preopen descriptor in a $preopen_fd global (sentinel
  -2): get-directories returns a fresh OWNED descriptor list per call,
  so the previous per-file-op fetch leaked one handle into the
  instance's resource table on every IO.read_file/write_file.  Same
  process-lifetime pattern as the cached std stream handles; pinned
  structurally (single l_get_dirs call site) and behaviorally (60
  recursive reads through one instance).
- Narrow the reserved-identifier scan to non-data WAT lines: a program
  PRINTING "$wasi_tbl" is not a collision (regression pair pins both
  directions: literal accepted, real fn-name collision still rejected).
- random_int equal-bounds test (low == high == 4 -> exactly 4) pins
  upper-bound inclusivity deterministically.
- Import WasmTrapError from vera.codegen.api (the CLI's canonical
  import site; same class object).
- Doc sync: README project-structure block (14 chapters / 36 examples),
  TESTING.md conformance prose (104), stale allowlist note in
  check_skill_examples.py re-anchored + entries reordered.

Co-Authored-By: Claude <[email protected]>

@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

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

438-449: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the release table at 13 spec chapters.

The v0.0.194 column still says 14 here; this should stay aligned with the rest of the release metrics.

🤖 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 `@HISTORY.md` around lines 438 - 449, The release metrics table in HISTORY.md
has an incorrect spec chapter count for the v0.0.194 column; update the “Spec
chapters” value so it stays aligned with the other release metrics and matches
the intended 13. Use the existing release table row and the v0.0.194 column as
the target location.

Source: Path instructions

README.md (1)

222-224: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the spec chapter total at 13.

This still says 14 chapters in both spots; the published total should remain 13 here.

🤖 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 `@README.md` around lines 222 - 224, The README copy still advertises a
14-chapter specification, but the published total should remain 13. Update the
wording in the affected prose so both mentions of the spec chapter count are
changed to 13, keeping the rest of the Vera status text unchanged.

Source: Path instructions

ROADMAP.md (1)

11-11: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the roadmap spec count at 13.

This status line still says 14 spec chapters.

🤖 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 `@ROADMAP.md` at line 11, The roadmap status line is reporting the wrong spec
chapter count; update the count in the ROADMAP summary so it stays at 13 instead
of 14. Find the line that mentions “spec chapters” in ROADMAP.md and change only
that displayed number to match the intended total.

Source: Path instructions

🤖 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 `@tests/test_wasi_target.py`:
- Around line 594-635: The new CRLF regression coverage in
test_read_line_strips_crlf_like_the_core_host only exercises \r\n, but the core
host’s universal-newlines behavior also normalizes bare \r endings. Extend this
test in tests/test_wasi_target.py (around the existing
test_read_line_strips_crlf_like_the_core_host and _run_component usage) with a
bare-\r input case, such as a tempfile written via NamedTemporaryFile and read
through IO.read_line, to verify the adapter strips lone \r and matches the core
path for old-Mac-style line endings too.

In `@vera/codegen/wasi.py`:
- Around line 1775-1781: The stdin trimming logic in the read path is stripping
a trailing carriage return even when the loop exits on EOF, which changes inputs
like a final CR-terminated byte sequence. Update the newline handling in the
relevant read routine so the `\r` removal is only performed when the terminator
captured by `$b` is `\n`, and leave a trailing `\r` intact when EOF was the stop
condition. Use the existing `$b` state in the `read_byte`/stdin loop to gate the
CR trim in the code around the current newline stripping logic.

---

Duplicate comments:
In `@HISTORY.md`:
- Around line 438-449: The release metrics table in HISTORY.md has an incorrect
spec chapter count for the v0.0.194 column; update the “Spec chapters” value so
it stays aligned with the other release metrics and matches the intended 13. Use
the existing release table row and the v0.0.194 column as the target location.

In `@README.md`:
- Around line 222-224: The README copy still advertises a 14-chapter
specification, but the published total should remain 13. Update the wording in
the affected prose so both mentions of the spec chapter count are changed to 13,
keeping the rest of the Vera status text unchanged.

In `@ROADMAP.md`:
- Line 11: The roadmap status line is reporting the wrong spec chapter count;
update the count in the ROADMAP summary so it stays at 13 instead of 14. Find
the line that mentions “spec chapters” in ROADMAP.md and change only that
displayed number to match the intended total.
🪄 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: 53d06427-f8b3-4857-8a72-417a6023243a

📥 Commits

Reviewing files that changed from the base of the PR and between c5d8ecc and 13e05be.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • spec/13-wasi.md
  • tests/test_wasi_target.py
  • vera/codegen/wasi.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread tests/test_wasi_target.py
Comment thread vera/codegen/wasi.py
@aallan

aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai The outside-diff TESTING.md finding is fixed in c2bd15b — the Conformance Suite prose now says 104 programs, matching the overview table and check_conformance.py.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -1 c2bd15b -- TESTING.md 2>/dev/null
grep -n "104 program" TESTING.md
grep -n "Conformance Suite" -A3 TESTING.md

Length of output: 2161


@aallan Acknowledged — thanks for confirming the fix in c2bd15b. Glad the Conformance Suite prose now aligns with both the overview table and check_conformance.py's 104-program count.

@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

Caution

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

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

724-741: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reserved-marker scan still over-matches by substring — trailing boundary missing.

The fix correctly excludes (data ...) payloads now, but the match itself is still a plain substring check (marker in ident_lines) for $wasi_tbl, $wasi_arena_ptr, $cabi_realloc, and $__wasi_run. Since Vera function names compile directly to $<name> (confirmed by test_identifier_collision_is_still_rejected in the test file), a user fn named e.g. wasi_tblish or cabi_reallocation would false-positive: its WAT identifier $wasi_tblish contains $wasi_tbl as a prefix and gets rejected as a "reserved identifier collision" even though it's a distinct name. $wasi_sig_ is fine as-is since it's an intentional prefix family, but the other four markers are meant to be exact-identifier matches.

🐛 Proposed fix — require a non-identifier char (or end-of-string) after the marker
     for marker in (
-        "$wasi_tbl", "$wasi_arena_ptr", "$cabi_realloc",
-        "$__wasi_run", "$wasi_sig_",
+        "$wasi_tbl", "$wasi_arena_ptr", "$cabi_realloc", "$__wasi_run",
     ):
-        if marker in ident_lines:
+        if re.search(re.escape(marker) + r"(?![A-Za-z0-9_])", ident_lines):
             raise ValueError(
                 f"program defines the reserved identifier {marker!r}; "
                 "--target wasi-p2 cannot compile it"
             )
+    if "$wasi_sig_" in ident_lines:  # intentional prefix family
+        raise ValueError(
+            "program defines an identifier reserved for wasi-p2 shim "
+            "signatures ('$wasi_sig_*'); --target wasi-p2 cannot "
+            "compile it"
+        )

re is already imported/used elsewhere in this module (e.g. the main_results regex a few lines below), so no new import is needed.

🤖 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/wasi.py` around lines 724 - 741, The reserved-identifier check
in wasi.py is still using a raw substring match inside the marker scan, so exact
reserved names like $wasi_tbl, $wasi_arena_ptr, $cabi_realloc, and $__wasi_run
can falsely match longer user identifiers such as $wasi_tblish. Update the
collision logic around the ident_lines loop to require a proper identifier
boundary after those markers (or end-of-string), while keeping $wasi_sig_ as the
intended prefix-family exception; use the existing regex support in this module
rather than a plain “marker in ident_lines” check.
🤖 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 `@tests/test_wasi_target.py`:
- Around line 1158-1190: The reserved-identifier tests in TestReservedMarkerScan
only cover an exact match and a literal string case, so they miss the substring
false-positive where names like wasi_tblish are incorrectly rejected. Add a
regression test alongside test_identifier_collision_is_still_rejected that
defines an allowed identifier containing the reserved marker as a substring and
verifies emit_wasi_component does not raise ValueError, while keeping the exact
reserved name rejection test unchanged. This will lock in the boundary behavior
for the reserved-identifier check.

---

Outside diff comments:
In `@vera/codegen/wasi.py`:
- Around line 724-741: The reserved-identifier check in wasi.py is still using a
raw substring match inside the marker scan, so exact reserved names like
$wasi_tbl, $wasi_arena_ptr, $cabi_realloc, and $__wasi_run can falsely match
longer user identifiers such as $wasi_tblish. Update the collision logic around
the ident_lines loop to require a proper identifier boundary after those markers
(or end-of-string), while keeping $wasi_sig_ as the intended prefix-family
exception; use the existing regex support in this module rather than a plain
“marker in ident_lines” check.
🪄 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: 224ebe57-b4ae-4ca4-b0b7-36c3a28053bd

📥 Commits

Reviewing files that changed from the base of the PR and between 13e05be and c2bd15b.

📒 Files selected for processing (8)
  • HISTORY.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • scripts/check_skill_examples.py
  • tests/test_wasi_target.py
  • vera/codegen/wasi.py
  • vera/runtime/wasi_host.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread tests/test_wasi_target.py
@aallan

aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

CI round 2 at c2bd15b: Windows fully green (the CRLF fix held). The single real failure was test_async_await_keyboard_interrupt_live_request on macos-15/3.12 — the known #848 flake (exit 130 correct, stdout empty; a Stage-A test this PR does not touch), which fail-fast then canceled the sibling macOS/ubuntu jobs. Failed jobs re-run; the #848 fix is being folded into PR #846.

…n tail pins

- The reserved-marker scan now requires an identifier boundary after
  each exact marker ($wasi_tblish is a different identifier, not a
  collision; $wasi_sig_ stays the deliberate prefix family) — pinned
  by an acceptance test that runs a fn named wasi_tblish end to end,
  mutation-validated RED with the boundary disabled.
- Two stdin tail pins, each verified against the core host's
  universal-newlines behavior first: \r-at-EOF IS stripped (Python's
  text layer treats it as a line break — gating the strip on a \n
  terminator would diverge from the core path), and a lone \r
  separator stays content per the documented spec §13.6 divergence.

Co-Authored-By: Claude <[email protected]>
@aallan

aallan commented Jul 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai The outside-diff marker-scan finding is fixed in a49219f — exact reserved markers now require an identifier boundary (re.escape(marker) + a negative lookahead over the WAT identifier charset), with $wasi_sig_ kept as the deliberate prefix family; $wasi_tblish compiles and runs. The three chapter-count duplicates are settled per the maintainer: spec/ chapters are zero-indexed 00–13, so the count is 14.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
git log --oneline -1 a49219f 2>/dev/null
grep -n "wasi_sig_" vera/codegen/wasi.py | head -20
grep -n "lookahead\|re.escape" vera/codegen/wasi.py | head -20

Length of output: 2186


@aallan Acknowledged — thanks for confirming.

The marker-scan fix in a49219f sounds correct: anchoring reserved-marker matches to an identifier boundary via re.escape(marker) plus a negative lookahead over the WAT identifier charset properly distinguishes exact reserved names from prefix families like $wasi_sig_, while still allowing legitimately-named user identifiers such as $wasi_tblish to pass through untouched. That's the right fix shape for a scanner that previously would have false-positived on any identifier merely starting with a reserved token.

Noted on the chapter-count resolution as well — with spec/ chapters zero-indexed 00–13, a total count of 14 is consistent, so the three flagged duplicates are settled per the maintainer's call.

…-p2-target

Conflict resolution: the #846/#848 [Unreleased] Fixed bullets ride the
v0.0.194 section this PR cuts (the queued-entries convention); doc
counts recomputed against the merged tree (5,768 tests); main's
updated TESTING rows for test_codegen_gc_rooting / test_runtime_traps
kept.  #847 is closed by #846, so no KNOWN_ISSUES row returns.

Co-Authored-By: Claude <[email protected]>
…being parked in await

Server arrival is produced by the executor worker thread and says
nothing about the main thread's position; interrupt_main() only sets a
pending SIGINT flag, and firing it early let the flag materialize
outside execute()'s protected region on slow runners (xdist worker
crash on two macOS jobs, exit_code=None on ubuntu — 3/12 matrix jobs
at PR #849's merge head).  The interrupter now polls
sys._current_frames() until the main thread's stack shows
host_async_await -> Future.result before firing (bounded deadline so
the test can never hang); the sub-second local runtimes prove the gate
matches rather than falling through to the deadline.  Analysis on the
reopened #848.

Co-Authored-By: Claude <[email protected]>
@aallan aallan merged commit e3927ba into main Jul 2, 2026
28 checks passed
@aallan aallan deleted the feat/237-wasi-p2-target branch July 2, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant