Releases: aallan/vera
Releases · aallan/vera
Release list
v0.1.2
Added
- A scheduled CI workflow now checks the limitation tables against live issue states (#852).
check_limitations_sync.py --check-statesruns Mondays 07:00 UTC and on demand: every issue a KNOWN_ISSUES.md, vera/README.md, spec, SKILL.md, or LSP_SERVER.md limitation row cites is queried against the tracker, and a closed issue still listed as a limitation fails the run — the drift class the v0.1.1 sweep caught by hand is now caught on a schedule. Deliberately a visibility signal on the Actions tab rather than a required merge check, since issue state drifts independently of any particular PR. External contribution by @chethanuk. - Linux aarch64 gains an advisory CI lane (#702). The test matrix gains an
ubuntu-24.04-armcell (Python 3.12 — platform coverage, not version coverage), and the wheel-availability gate now checksmanylinux_2_38_aarch64wheels for all three Python versions; README §Supported platforms lists the tested cell, with Python 3.11/3.13 on aarch64 remaining wheel-checked only. The new cell is advisory for now — it runs on every commit but is deliberately not in the required merge checks until the arm runners prove reliable. External contribution by @chethanuk.
Changed
- Two CI workarounds are retired — their removal triggers fired (#537). Pygments 2.20.0 (March 2026) shipped the CVE-2026-4539 fix, so the
--ignore-vulnflag is gone from the dependency audit (left in place it would have masked a real regression); and theactions/setup-pythontoolcache now carries pip 26.1.2 natively, so the pre-auditpip install --upgrade pip(CVE-2026-3219) is gone too. Both KNOWN_ISSUES CI-workarounds rows retire with them, per the table's bridges-not-permanent-exceptions contract.
Fixed
- An invalid string escape no longer crashes
vera checkwith a raw Python traceback (#966). The E009TransformErroris raised inside a Lark token callback, which lark wraps inVisitError— so the CLI'sexcept VeraErrornever fired and a user typo like"bad \q escape"produced exit 1 with zero bytes of JSON and a traceback on stderr, contradicting spec §0.5 and §0.5.8.transform()now unwraps aVisitErrorcarrying aVeraErrorand re-raises the original, so both text and--jsonmodes emit the proper diagnostic envelope. The E009 diagnostics themselves are upgraded from description-only to the full instruction format — rationale, fix, and spec_ref for both the invalid-escape and malformed-interpolation classes — retiring a# diag-fields-exemptwaiver whose "the grammar prevents this" premise was false (spec §1.6 defines invalid escapes as a user-facing error). Found by the v0.1.2 pre-release sweep. - The pre-release sweep corrected the drift the gates don't reach. TESTING.md's contract-verification block was three campaigns stale (claimed 256 of 280 obligations, 91.4% static; live is 283 of 378, 74.9% — the denominator grew with the soundness campaign's auto-synthesised primitive-op obligations, and the per-example Tier-3 table is regenerated from live
vera verify --json, which also surfaced the #967 summary off-by-one, now a tracked Stage 19 limitation); the README/FAQ test counts catch up to 6,846; HISTORY's v0.1.0 by-the-numbers column is restored to its true snapshot (6,779 — a v0.1.1-cycle edit had bumped it to the then-current count) with the growth-chart figure regenerated in lockstep; the spec-block counts correct to 189; §0.5.1 acknowledges theW001/W002warning codes; TESTING.md documents the scheduled limitations-sync workflow and scopes the# diag-fields-exemptgate rows to the #955 semantics; and three test-code comments citing since-fixed issues (#918, #706, #635) are retired. check_diagnostic_fields.py's# diag-fields-exemptopt-out is now honoured wherever an unresolvable field can arise (#955). The field-presence pass (check_source) appended its non-literal-severity violation andcontinued before its own opt-out lookup ran, so a marker on that exact call was never consulted even in the one pass that otherwise honours it; thespec_ref-validity pass never consulted the opt-out at all. Fixed for the unresolvable (non-literal) sub-case only: a non-literal severity or spec_ref can't be checked statically and so is opt-out-able, same as a missing field — with the marker found anywhere across a multi-line call's span, not just the argument's own line. Aspec_refthat resolves but cites the wrong/nonexistent section, or anerror_codenot inERROR_CODES, is a content error, not a tagging gap — the opt-out never suppresses either, marker or not; and theerror_codepass skips non-literal codes entirely, so it has nothing to waive. Onvera/the exempt set is unaffected (the three existing real markers are all missing-field cases).main()'s remedy text now states this precisely instead of implying the opt-out waives everything it lists. External contribution by @chethanuk.- The diagnostic-fields gate's plumbing-skip now also confirms the sole own-scope
Diagnosticis reachable as the helper's result (#956). The#827narrowing (v0.1.1) requires a genuine helper method whose sole own-scope construction is ambiguity-free, but never checked that the construction was actually used as the helper's output — a helper that builds its oneDiagnosticand hands it to something other than areturn/.append(...)(e.g.self.dispatch(d)) still had that ctor elected as plumbing and skipped by all three passes, so a bogusspec_refor unregisterederror_codeon it would ship silently. A new_ctor_is_reachable_as_resultpredicate now requires the ctor be return-ed, appended, or bound to a local later return-ed/appended, gated after the existinglen(ctors) == 1check so the#827ambiguity handling is untouched. The name-based return/append match has no real data-flow, so a local rebound after the ctor-binding assign — plain reassignment (d = Diagnostic(...);d = something_else;return d), afor/withloop target, a walrus, an augmented assignment, or anexcept ... asname — would otherwise still match on the name and be wrongly treated as reachable — fixed by counting binding sites generically rather than by enumerating statement forms (every Store-context name — assignment or unpack of any shape,for/withtargets, walrus — plusimport ... as,except ... as,matchcaptures, the helper's own parameters, andnonlocaldeclarations in nested functions; a bare annotation carries no assignment and is not counted): a name bound more than once anywhere in own scope is treated as unreliable rather than reachable, and the return/append name-match only counts at-or-after the binding line. Separately,_is_append_calltreated ANY.append(...)call as evidence of reachability, including an append to an unrelated throwaway local (tmp = []; tmp.append(d)) that the helper never reads again — every real diagnostic-list sink invera/has theself.<attr>.append(...)shape, so the check is now scoped to that. Onvera/the exempt set is unchanged (all five real helpers return or append their sole ctor directly), so nothing new is flagged in the live tree — the gap was latent, closed defensively. External contribution by @chethanuk.
v0.1.1
Added
- The documentation now carries a hand-authored SVG diagram set — thirty-three figures across the spec, the compiler README, and the top-level docs (
assets/diagrams/). The second wave adds the subtyping and effect-row lattices (§2.8, §7.8), module resolution with the transitive-visibility rule (§8.6), the async model and thevera serverequest lifecycle (§9.5), closure layout +call_indirectdispatch (§11.10), browser import introspection (§12.9.1), the WASI arena and server-world sequence (§13.3, §13.7), the FAQ's three verification layers, contract-driven-testing loop, and Dafny/Lean/Koka/F* comparison matrix, the CONTRIBUTING gate pipeline, the DE_BRUIJN binding-stack timeline, and the host-binding-families map (vera/README.md). The headline is the overall compiler architecture diagramvera/README.mdlacked entirely; alongside it: the spec §11.1 pipeline, the §12.5 memory layout and a new$alloc/mark-sweep GC flow, the §13.2 WASI component + dispatch table, the §6.8 three-tier verification decision flow, the §7.5.2 effect-handler suspend/resume sequence, the wasmtime embedding chain, the checker's three passes, Z3 proof-by-refutation, De Bruijn slot resolution (two figures, shared withDE_BRUIJN.md), theDiagnosticrecord, the CLI↔pipeline map (TOOLCHAIN.md), the three-layer/four-level testing model (TESTING.md), the LSP proof-delta session (LSP_SERVER.md), a by-the-numbers growth chart (HISTORY.md), and a workflow hero + the architecture inREADME.md. Every diagram that replaced an ASCII original keeps the text in a collapsed<details>block taggedtext— the docs feedllms-full.txtand agents reading files in a terminal, and images are invisible to both — and no diagram carries live counts (they drift outsidecheck_doc_counts.py's reach; the one exception, the growth chart, plots the historical release columns and says so). The architecture and pipeline figures draw the check → {verify | compile} fork truthfully —vera compiledoes not consume verify results and contract guards are always emitted — where the replaced ASCII's "runtime contract insertion for Tier 3" caption echoed the spec drift tracked in #958. The landing page gains its own figure in the site's bolder design language —docs/loop-web.svg, the write → prove → ship loop with the diagnostics return, embedded indocs/index.html§Why and mirrored into the generatedindex.md. The set's design system, conventions, and inventory are documented inassets/diagrams/README.md; theREADME.mdproject-structure tree's stale module counts (11 → 13 codegen, 9 → 19 wasm) ride along. - The canonical
E001diagnostic is now guarded against drift in all five of its documentation mirrors (#829).TestErrorDisplaySyncalready comparedREADME.md,docs/index.htmlandspec/00-introduction.mdagainst the diagnostic generated fromvera/errors.py, but two mirrors were unguarded —AGENTS.md's example--jsonblock, and the hardcoded example inscripts/build_site.pythat generatesdocs/index.md— and #826 had already drifted the ungated pair. Both are now compared.AGENTS.md'serror_code/spec_ref/fixare matched exactly (the extractorjson.loadsthe block, so the escaping is resolved and every field is directly comparable) and its ellipsis-truncateddescription/rationaleare prefix-compared;build_site.py's block is extracted by anchoring on the closing code fence rather than on thespec_reftext, so aspec_refdrift yields a precise Expected/Got diff instead of an opaque "block not found". Every guard is mutation-validated — drifting any mirror, or the canonical diagnostic, turns the corresponding test RED. The example is still hand-duplicated across the five mirrors; single-sourcing it so nothing can drift is tracked in #954. External contribution by @chethanuk.
Changed
- ROADMAP.md is reworked into a staged sprint plan. The tier/milestone mix is replaced by six themed stages continuing HISTORY.md's numbering — Stage 19 verification completeness, 20 single-source, 21 effect hardening, 22 the verified tool server, 23 agent experience, 24 browser — each with a rationale, an exit criterion, and an issue table, ordered from the design principles (verification truth first, then structural drift-proofing, then the flagship's capabilities, then the experience around them). The Stage 17 burndown set the model: a stage is a concentrated sprint over a coherent issue class, and it moves to HISTORY.md when its table empties. All 100 open issues are placed exactly once (staged, horizon-arc, ongoing, not-doing-now, or speculative; verified mechanically), pulling in the verification-limitation family that previously lived only in KNOWN_ISSUES.md; the browser sync-XHR fix (#355) moves from Http hardening to the browser sprint since every fix option shares the JSPI suspend machinery. Rides along: DESIGN.md's module row claimed "explicit re-exports", which don't exist (#127 is open) — it now says
public/privatevisibility and points at #127.
Fixed
- A six-auditor documentation-consistency sweep reconciled every stale claim it could verify against the tree, the registry, and the tracker. The one behavioural-claim error:
vera/README.md§Runtime contracts still described the pre-#957 world — codegen "classifies contracts using the verifier's tier results" and omits Tier-1 guards — contradicting the architecture diagram above it; it now states the truth (compile never consults the verifier; guards are always emitted; the §11.8 aspiration stays tracked in #958). The rest is drift, each verified before fixing: SKILL.md's conformance count (103 → 143) and its spec table gaining the Chapter 13 row; the CLAUDE.md/AGENTS.md pipeline gaining the resolve stage; FAQ's feature bullets gainingIO.read_charand theExn<T>effect; README's "three-tier verification" delivered-claim scoped to the two implemented tiers; DESIGN.md's effect lists gainingHttpServer(andRandom/Divergerows) on a list that claims to mirrorvera effects --json, its Tier-1 coverage sentence aligned to spec §6.8, and the tiers + effect-row diagrams embedded; KNOWN_ISSUES gaining rows for the open #439 and #770 limitations; HISTORY's stage index gaining Stages 16–17 and the intro catching up to 94 development days;vera/README.md's module-map line counts regenerated from disk (worst drift:verifier.pylisted at 1,005 lines, actually 6,582); spec §12's Random prose no longer citing the closed #465 as a tracker and the heap-growth wording unified to "toward higher addresses"; andbuild_site.pynow rewrites image embeds to raw URLs (ablob/page is not image bytes), fixing the four diagrams inlined intollms-full.txt. Examples and conformance fixtures came back clean — no workaround shapes for fixed bugs survive there. - Five stale bug-era annotations are retired from the test suite, each verified by running the affected tests before and after — found by the sweep's test auditors: the #869 table-forcing
array_foldis removed from the monomorphize fixture (the fixture itself is now the regression pin, sum unchanged); the #570-era "1,000 elements to stay under the bug threshold" GC graph is promoted to the true 5,000-elementArray<Box>wide graph its docstring always intended; the #516 module docstring no longer defers "Stage 2 (source mapping) and Stage 3 (Fix:paragraphs)" as future work — both shipped (v0.0.124/v0.0.125) and are exercised throughout the file;ch09_decimalnow exercisesdecimal_comparethrough a three-armOrderingmatch (the "not yet supported in codegen" note was disproven by running it); and the #773 scalar-only-Eqparenthetical speaks in the past tense. - The verifier no longer proves false postconditions: a callee's
ensuresis now assumed only on the paths that establish it (#957)._translate_call_with_info(vera/smt.py) assumed each calleeensures— and each refined return's predicate — with a bareself.solver.add(...), which lands on the solver's base assertion stack.check_validfolds_path_conditionsaround the goal only, so a fact learned inside anifoutlived the branch and became an unconditional fact about the caller's own slots (_build_callee_envbinds the callee's parameters to the caller's terms). The escaped fact is circular:dec5 requires(@Nat.0 >= 5) ensures(@Nat.result == @Nat.0 - 5)injectsret == @Nat.0 - 5, which with@Nat's implicitret >= 0entails@Nat.0 >= 5— the very precondition the branch guard existed to establish. A caller's falseensures(@Nat.0 >= 5)then discharged, andvera verifyprintedOK/6 verified (Tier 1)with no diagnostic for a program thatvera runtraps on. Worse, two calls in mutually-exclusive arms inject contradictory facts, the base solver goes UNSAT, and every obligation discharges vacuously — silently deleting realE501diagnostics, including the ones the#776fix above adds. Each injected fact is now wrapped by_guard_factinz3.Implies(z3.And(*self._path_conditions), fact), matching the idiom_translate_matchalready used; the call translator was the only site that did not. Same soundness as discarding the facts, strictly more precision. Behaviour-neutral across all 143 conformance p...
v0.1.0
v0.1.0 — the first minor release: zero known bugs.
Over the bug burndown, every open bug-labelled issue — 37 in all — was fixed on a single release/v0.1.0 integration branch, each on its own adversarially-reviewed PR, then the release was cut with a full documentation sweep and the literal "No known bugs."
Highlights
- Silent-miscompile gates.
compare/ ordering on a user ADT (had compiled to a heap-pointer compare) and==on a non-Eqtype (pointer identity, not value equality) are now rejected at check withE242/E243— the severest failure class, closed. - Structural
show/hash/eqover composite, recursive, nested-generic, and generic-mutually-recursive ADTs; a polymorphically-recursiveBox<Box<T>>degrades to a clean skip rather than a traceback. - Effect handlers work over composite / parameterized type arguments.
- Module imports — transitive, nested fn-type-alias, single-uppercase ADT names, and closure-body generics — all resolve.
- Verifier no longer crashes on nested same-ADT constructors; injective Z3 sort names; ADT-typed call-site precondition obligations; contract-predicate degradation.
- Browser
State<T>composite-Floatdefault fixed. - Diagnostics. Zero-size
Array/ generic-@T/ refinement edge cases now reject cleanly at check with a singleE135/E206, never a codegen traceback or a duplicate error.
By the numbers
6,779 tests, 143 conformance programs, 37 examples, 91% code coverage, and a 14-chapter specification.
Full detail for every fix is in CHANGELOG.md and the release PR #938.
🤖 Release prepared with Claude Code
v0.0.196
Added
examples/async_http_fanout.vera— the 37th example and the showcase for the v0.0.192 concurrent<Async>: twoasync(Http.get(url))requests started back-to-back so network latency overlaps, then awaited and folded into a Z3-proved0..3status summary (4 obligations Tier-1 verified).examples/async_futures.veraandEXAMPLES.md's async section were reworded to stop teaching eager-only async: eager applies to non-whitelisted shapes, directHttp.get/Http.postcalls run concurrently (#841).- Two conformance programs pinning the
apply_fnfix (suite now 106):ch05_apply_fn_typing(verify level — alias-typed application, variadic two-parameter application, deliberately non-commutative subtraction so a slot swap is caught) andch05_apply_fn_arity(negative fixture,expected_error: E201— a wrong-arityapply_fnmust failcheck). - Spec completions after the server-effects sprint: §0.7 chapter index gains the Chapter 13 row; §7.7.6 documents the
Asyncmarker effect alongside its peers; §9.5.3 drops the stale "(future work)" framing on async Http composition; §9.3.4 scopes theFuture<T>zero-overhead claim to eagerly-evaluated futures; §11.10.5 documentsapply_fn's checker typing. wasmtime serveline-buffering caveat (spec §13.7 + TOOLCHAIN.md), empirically confirmed: under--world servera handler'sIO.printwithout a trailing newline is held in the host's line buffer (flushed by the next newline, dropped at shutdown if none comes) — end log lines with\n; nativevera servedoes not buffer this way.- veralang.dev surfaces the server/WASI work: two new Key-features cards (Verified HTTP handlers; WASI 0.2 components), a third Runs-Everywhere column with the
--target wasi-p2 --world server→wasmtime servecommand sequence,HttpServerin every effect list, and the 14-chapter/eight-effect counts — mirrored in thebuild_site.pyliterals that generateindex.md/llms.txt/llms-full.txt(the hardcoded chapter list there gains Chapter 13).
Changed
- Documentation sweep after the server-effects sprint (v0.0.192–v0.0.195):
HttpServerand the WASI targets are now surfaced in README (tagline, delivered-features), FAQ, and DESIGN.md's target/Async rows;KNOWN_ISSUES.md's WASI limitation row is rescoped from the closed #237 to the honest remainder tracked by #853 (wasi-p2 covers IO+Random only; the default target still uses ad-hocvera.*imports), and the same rescope retitles vera/README's row to "Partial WASI support"; stale coverage-gap rows for the shipped #592/#645 removed; vera/README's module map gainscodegen/wasi.py,runtime/wasi_host.py,runtime/server.py(plustail_position.py,text.py) with recounted package totals (~65,000 lines of Python); CONTRIBUTING.md's branch-protection section now recordsenforce_adminsand the actual review requirement; MUTATION.md documents the stale-.pycpurge step for hand-run mutation kills;ch10_float_predicatesrenamedch09_float_predicates(its content, manifest chapter, and spec_ref were all chapter 9); counts refreshed everywhere (37 examples, 106 conformance programs, 14 spec chapters, live test totals). Scheduled limitation-state checking in CI is tracked by #852.
Fixed
apply_fnis now typed by the checker — no more spurious E200, and misuse is a check-time error (#854). The documented primitive for applying a stored function value was unknown to the checker: every use fell into the unresolved-bare-call path, drawing a[E200] Unresolved function 'apply_fn'warning on green programs (vera check examples/closures.verashowed it) and typing the call asUnknown— so wrong arity or argument types passedcheckwith exit 0 and only surfaced at compile time (a raw WAT assembler error at line 0,0, or a silently skipped function), and applying aneffects(<IO>)-rowed fn value inside aneffects(pure)function passed entirely (a reachable effect-soundness hole: the fn-typed parameter route was open even though constructing such a closure in a pure fn already tripped E122).apply_fnis variadic and effect-polymorphic — arity, argument types, result type, and effect row all come from the applied value's fn type — so it cannot be a fixed-signature registry row; it is now a checker special form typed structurally against the appliedFunctionType: wrong arity →[E201], wrong argument type / non-function first argument →[E202], the applied row joins the caller's used effects and is checked against the declared row ([E122]/[E125]), and the result is the fn type's return type (a body returning anapply_fnresult against the wrong declared return is now[E121]). TypeVar-parameterised fn-type aliases (the prelude combinator shape, e.g.@Mapper<A, B>params) check as before. Redefiningfn apply_fn(...)— previously green at check time while codegen hijacked every 2+-argument call tocall_indirect, silently skipping the caller — is now the same[E151]one-canonical-form error as any built-in redefinition. Mutation-validated four ways (special form disabled, effect-row join skipped, arity check skipped, E151 reject-set entry dropped — each flips exactly its discriminating tests RED).- Prelude combinator skip-warnings no longer fire on every compile, and prelude diagnostics no longer misattribute to user source (#851). Every
vera compileused to emit five[E602]/[E604]warnings about the generic Option/Result prelude combinators (option_unwrap_or,option_map,option_and_then,result_unwrap_or,result_map) being skipped — code the user didn't write and, in most programs, never calls — with locations that resolved the prelude buffer's line numbers against the user's file (a 5-line hello cited "line 61"; longer files rendered unrelated user source under the caret). Two fixes: (1) synthetic origin — diagnostics about prelude-injected declarations (and their mono clones, and nodes inside their bodies) now cite the synthetic file<prelude>and quote the actual prelude source line, in both text and--jsonoutput, so a prelude span can never render user code (inject_preludereturns the buffer it parsed; codegen's_warning/_errorresolve prelude-origin nodes against it); (2) reachability suppression — E602/E604/E605 skip-warnings for prelude functions are dropped unless the program actually references them, via a transitive call-target scan rooted at every non-prelude declaration (including generic user fns the mono collector never visits, and imported module fns), so a minimal program compiles with zero warnings while a program that references a skipped combinator without a compilable instantiation keeps exactly that combinator's warning as the honest pre-runtime signal (now correctly attributed). A program that successfully callsoption_mapwas already clean for the called fn via the #604 mono-compiled suppression; with the unreferenced four now silent too, it also compiles warning-free. User-defined unsupported functions warn exactly as before, with user-file locations (pinned by test). Mutation-validated four ways (suppression deleted, origin tag dropped, transitivity broken, body-node flag stuck false — each flips its discriminating test RED).
v0.0.195
Added
--world server— verified HTTP handlers as portable wasi:http components (Stage D of the server-effects sprint; spec §13.7).vera compile --target wasi-p2 --world serverpackages the same contract-checkedhandle(@Request -> @Response)programvera servehosts natively (#305) as a component exportingwasi:http/incoming-handler@0.2.0— stockwasmtime serveruns it unmodified, no flags (live-tested: routing, handler-set headers, a 1 MiB body byte-identical under GC stress, trap → host 500; imports pinned at@0.2.0, which wasmtime's semver-compatible lookup links — the design study also probed@0.2.3acceptance). A generated adapter wrapper reads method / path-with-query / headers / body through the wasi:http interfaces into the arena, constructs theRequestADT in the guest heap from the compilation's own constructor layouts (WAT-level shadow-stack rooting throughout), callshandle, decodes theResponse, and drives the outgoing-response resource sequence (borrow-before-transfer, child-stream-drop-before-finish, 4096-byte write chunks). Headers without a host:Map<String, String>operations are host imports on the core target, but a Vera Map is two plain guest-heap blocks — the server world implements the String-keyed Map ops in guest WAT with the host's exact semantics (position-preserving update, later-insert-wins, power-of-two capacity growth), pinned by a host-vs-served differential battery (mixed-case / absent / duplicate / 41-header matrix + insertion-order agreement); non-String Map instantiations and all other collection families stay gated. The server-world surface is honest and explicit:IO.print/IO.stderrroute to the serve console;read_line/read_char/read_file/write_file/get_env/args/exitare rejected with a diagnostic (negative-probed: those imports do not link under the wasi:http proxy world); bodies are buffered (streaming is future work); request headers share the ~63 KiB arena; an out-of-range status or forbidden header answers 500 instead of trapping the server. The cli-world emission is byte-identical to v0.0.194 (pinned).vera runrejects server-world artifacts with a pointer towasmtime serve(wasmtime-py's built-in host has no wasi:http); the nativevera servedriver is unchanged — one handler, two deployment paths. Design decisions live-validated before implementation (topology: MAIN owns memory, serve wrapper in the adapter, dispatch table 16→32 slots; the check-7$Libcdodge andincoming-body.finishboth superseded — recorded inWASI.md); five mutation kills (later-wins parity, map update-vs-append, Request-build shadow-push via an in-suite eager-GC surgery test, cli-pin leak — which first false-greened on a stale.pyc, purged and confirmed RED — and a family-gate line).
v0.0.194
Added
- Experimental WASI Preview 2 target —
vera compile/run --target wasi-p2(#237).vera compile --target wasi-p2emits a binary WebAssembly component whosevera.*IO + Random host imports are implemented on top of WASI 0.2 interfaces, runnable by any stock wasip2 host —wasmtime runworks with no flags and no Vera bindings (wasmtime.wat2wasmaccepts component text, so no external componentizer and no new dependency). The component wraps the unchanged core module (the default--target wasmemission is untouched, pinned by test): each(import "vera" "op")becomes a same-namedcall_indirectshim through a funcref dispatch table the main module defines and exports, and a compiler-generated adapter core module implements all 14 IO/Random ops (plus thecontract_fail/overflow_trapchannels) over canon-lowered WASI imports, planting itself into the table via active elem segments strictly before any lifted export can run — the naive main↔adapter import topology is an instantiation cycle, which the component model forbids.cabi_reallocis a bump allocator over a GC-exempt 64 KiB scratch arena belowgc_heap_start(host-written data needs no rooting and a collection can never move a half-written block — the #593/#695 UAF class, host-side); data crossing back into Vera is copied into GC-heap blocks under explicit shadow-stack rooting, and a 500-arg GC-pressure stress pins it. Canonical-ABI variant discriminants are readi32.load8_u(they are u8; retptr-slab reuse leaves garbage in the padding — found live as an EOF misread aslast-operation-failedwith a stale list length as a "handle").vera run --target wasi-p2executes the component under wasmtime-py's built-inadd_wasip2()host (newvera/runtime/wasi_host.py) with the core path'sExecuteResultcontract: stdout/stderr capture (+ the #543 live tee),os.environsnapshot, cwd preopen for file ops, argv,IO.read_lineCRLF handling matching the core host's universal-newlines behavior (a lone\rseparator stays content — spec chapter 13), and the #516 trap-kind taxonomy — a contract violation classifies ascontract_violationwith the full violation text recovered from the WASI stderr channel (structured frames do not cross the component boundary; the JSON envelope'sframesis empty — spike check 5's documented degradation). A program using any host family beyond IO/Random gets a diagnostic naming the family — never a silent fallback to the core target. Both entry worlds are exported:wasi:cli/run@0.2.0(what stockwasmtime runinvokes) and a plain liftedmainfor scalar returns (Int/Natass64,Float64,Unit); aString/heap-returningmainruns viawasi:cli/runand reports no value. Inherent WASI 0.2 divergences are documented in the new spec chapter 13 and pinned by tests:wasi:cli/exit@0.2.0carries ok/err only, soIO.exit(n)degrades to exit status 0/1 under every wasip2 host. Validation is live, not parse-only (the design study caught a mis-spelled filesystemerror-codecase —quota, notdisk-quota— only atadd_wasip2instantiation): the suite instantiates and executes every artifact, a dual-target conformance differential runs all 88 run-level conformance programs under both targets and requires byte-identical stdout/stderr (71 execute identically; the rest are family-gated,main-less, or wall-clock-dependent — the sweep also surfaced thathandle[Exn]programs need the wasip2 runner to enable the exceptions proposal like the core engine does), and a stock-wasmtime-CLI smoke test runs where the CLI is installed. This closes #237 as written (its listed ops are exactly the IO family) as an experimental WASI Preview 2 target (IO + Random surface) — not a blanket "WASI 0.2 compliant" claim; the remainingvera.*families (Map, Set, Decimal, Json, Html, Md, Regex, Math, Http, Inference, State, Async) stay host-bound on the core target. Also: the doc-builtin-shadowing scanner now skips.claude/(session-tooling worktrees carry full repo copies that re-flagged every spec chapter).
Fixed
- GC use-after-free: pair-typed
letbindings (String/Array<T>) were never shadow-rooted (#847, fixed by #846). The plain-letpair branch intranslate_block(vera/wasm/context.py) stored the (ptr, len) pair into WASM locals without pushing the pointer onto the GC shadow stack — the last unrooted sibling of the #705 scalar-i32 let fix and the #707 let-destruct pair fix (whose review comment had already named this gap class). Unobservable for Vera-side producers — an array literal or string builtin shadow-pushes its own freshly-allocated block at the alloc site, and that push survives to the function epilogue, masking the missing let root — but a host-import pair (IO.args→Array<String>,IO.read_line→String) is rooted only host-side during construction (_ShadowGuard, popped on return), so the first Vera-side allocation after theletcould collect the block while the locals still pointed at it: the free list overwrites the payload's first words and reads through the binding see reclaimed bytes (IO.printof anIO.argselement after anat_to_stringcall printed2::++2@instead of2:aa+bb;string_joinover the swept backing chased overwritten element pointers). Found stress-testing #237 underVERA_EAGER_GC=1; no WASI code involved, and reachable under ordinary collection pressure. Pair lets are now rooted unconditionally — static and null pointers are ignored by the conservative scan's heap range check, so the push is harmless for non-heap values. New targeted eager-GC tests pin the fix (args / read_line reproducers) and confirm the neighbouring host-import ADT paths (IO.read_file→Result<String, String>,IO.get_env→Option<String>) were already rooted by the #705/#707-era fixes. - De-flaked the #841 live-interrupt test; one red combo no longer cancels the CI matrix (#848).
test_async_await_keyboard_interrupt_live_requestprinted its progress marker betweenasync(...)andawait, racing the worker thread's request — whose arrival gates the test's interrupt — against the guest reaching the print: a lost race yields the correct exit 130 with empty stdout and failed the stdout assertion (twice on macos-26 runners across PR #846's matrices, each time cancelling the other 11 fail-fast jobs). The print now precedes theasync(...), so program order supplies a real happens-before (buffer write → request issuance → arrival → interrupt) and the assertion strengthens frominto==; prompt issuance at theasyncpoint stays pinned by the #841 request-ordering and operand-stack tests, so the reorder loses nothing. The test matrix also setsfail-fast: false, so a single red combo reports alone instead of cancelling eleven healthy jobs. Round 2 (PR #849, closes #848): the reorder alone proved insufficient — server arrival is produced by the executor worker thread and says nothing about the main thread's position, sointerrupt_main()'s pending flag could materialize outsideexecute()'s protected region on slow runners (an xdist worker crash on two macOS jobs, a completed run withexit_code=Noneon ubuntu — 3/12 jobs at one head). The interrupter now also pollssys._current_frames()until the main thread is verifiably parked inhost_async_await→Future.result— the interruptible wait the #595-class machinery intercepts — before firing, with a bounded deadline so the test can never hang.
v0.0.193
Added
<HttpServer>— verified HTTP request handling, served byvera serve(#305). A server program defines a total, contract-checked handler —public fn handle(@Request -> @Response) effects(<HttpServer>)— andvera serve prog.vera [--port N]hosts the accept loop: the loop lives in the host, so handlers need noDiverge, are termination-checked like any function, and every contract on the handler (or its helpers) is an ordinary Tier-1/Tier-3 obligation (the newexamples/http_server.veraproves a status-range postcondition at Tier 1).HttpServeris a built-in marker effect (no operations; spec §7.7.5);Request(method, path, headers, body) andResponse(status, headers, body) are prelude ADTs injected when referenced (headers areMap<String, String>; user definitions shadow). Per-request isolation: each request runs on a fresh module instance (freshexecute()— instantiation measured at ~0.02 ms in the Stage-0 spike), soState<T>cannot leak between requests (pinned by test). A runtime contract violation (or any trap) inside a handler answers 500 with the trap diagnostic as a JSON body (trap_kind, message, frames — thevera run --jsonenvelope shape); the connection is always answered. Host marshalling is layout-driven:CompileResult.adt_layoutsexports the constructor layouts this compilation computed,build_request_adtshadow-roots every intermediate allocation (#570/#692 discipline),decode_response_adtreads the returned ADT, and both fail loudly on an unexpected layout shape; a newInstanceCalleradapts a (Store, Instance) pair to the caller protocol the heap helpers already use. HandlerIO.printgoes to the server console; request handling is sequential in v1 (concurrency: #406); native-only (the browser runtime does not serve HTTP — documented divergence, spec §12.9.3). Also fixed en route: Pass-1 function signatures that reference prelude ADTs in params/return were computed before prelude registration and recordedunsupportedinfn_param_types— a post-injection pass now re-registers exactly those. New:tests/conformance/ch09_http_server.vera(levelverify— needs no network, unlikech09_http),examples/http_server.vera, spec §9.5.6, and aTOOLCHAIN.mdserve recipe.
v0.0.192
Added
- Concurrent
<Async>:async(Http.get/post(...))now runs on a host worker thread (#841). The concurrency half deferred when #59 shipped the language surface:async(Http.get(url))/async(Http.post(url, body))— with call-free argument expressions — fuse into a singlevera.async_http_get/vera.async_http_posthost import that issues the request on a hostThreadPoolExecutorat theasync(...)point (request issuance keeps program order) and returns theFutureas a #578 bit-31-tagged handle wrapper (new wrap kind 4);awaitprobes the value's first word for the wrapper tag and blocks onvera.async_awaitfor fused handles, passing eager values through unchanged. Two overlappingasync(Http.get)s are pinned by a server-side request-ordering test (no wall-clock). Every otherasyncshape keeps the eager identity lowering (spec §9.5.4 now says an implementation MAY evaluate concurrently when the effect row is commutative; §7.4 records the ordering guarantee). The fusion predicate is strictly narrower thanW002's whitelist, so a "evaluates eagerly" warning can never coexist with concurrent execution — and it lives in one shared module (vera/wasm/async_fusion.py) consumed by both import-emission passes, so the pre-scan and the translator cannot desync. GC follows the full opaque-handle contract, mutation-validated: the wrapper is shadow-rooted at the async site (operand-stack window pinned) and an unawaited future's wrapper is reclaimed by Phase 2chost_decref_handle(4, handle), cancelling the task and evicting the store entry (observable viaExecuteResult.host_store_sizes["future"]). Worker threads run only the pure fetch halves (fetch_get/fetch_post, split out of theHttphost callbacks invera/runtime/http.py) and never touch guest memory; theResultADT is built at await time on the guest thread. Ctrl-C during a blockedawaitrides thewasmtime>=45BaseException trampoline to the exit-130 handler, and the executor is torn down (shutdown(cancel_futures=True)) on every exit path. The browser runtime stays eager (spec-conformant; identical values, request fires synchronously at theasyncpoint, outcome buffered host-side untilawait). A function may now declare aFuture<T>return type (previously[E605]-skipped —Future<T>is transparent in the type mapper). Theawaithandle-check keys on the literal typeFuture<Result<String, String>>and covers slots, parameters, direct compositions, and calls — bare, imported, or module-qualified — whose declared return is that type (derived from the cross-module return-type registry — with module-qualified calls resolved by(path, name), so a colliding local of a different future shape cannot misclassifyawait(m::grab(...))in either direction; the local-only first cut and the bare-name qualified keying were both caught in review with repros and fixed with RED-first tests). Alias-typed shapes are rejected before codegen today (an alias-typedlethas no WASM representation →[E602]skip); the one unclassifiable shape — an indirectly-called closure returning this future type — is a documented limitation in KNOWN_ISSUES.md. W002— async concurrency-eligibility warning (#841).async(e)is only made concurrent whene's effect row stays within the commutative whitelist ({Http}in v1; theAsyncmarker itself has no operations and cannot order anything). For any other row the checker now says so —async(IO.print(...))warns that it evaluates eagerly at theasync()site — instead of letting a program imply concurrency it does not get. The rule resolves effect-op calls to their parent effect and function calls to their declared rows, recursively, and treats anything unresolvable as conservatively non-commutative. Mutation-validated four ways (rule deleted, whitelist over-permissive, whitelist over-firing, fn-call rows ignored — each flips exactly its discriminating tests RED).
Changed
- Split
tests/test_checker.pyinto eight phase-focused test files (#420). The monolithic 6,752-line, 60-class checker test file is replaced by eight themed files —test_checker_types.py,test_checker_patterns.py,test_checker_functions.py,test_checker_effects.py,test_checker_modules.py,test_checker_errors.py,test_checker_builtins_collections.py,test_checker_builtins_strings.py— each under 1,200 lines, with the shared header (the_check/_errors/_warnings/_check_ok/_check_clean/_check_errhelpers and theEXAMPLES_DIR/EXAMPLE_FILES/CLEAN_EXAMPLES/WARN_EXAMPLESconstants) extracted to a newtests/checker_helpers.pyimported by all eight (matching the repo's existingfrom tests.<module> importprecedent). Mechanical and behaviour-preserving: all 572 tests are carried over unchanged and each class moves whole; no test is added, removed, or modified. (Companion to the #419test_codegen.pysplit.) - Split
tests/test_verifier.pyinto nine theme-focused test files (#839). The 9,356-line, 38-class verifier test file — the largest remaining after the #419 split, and the verifier/SMT oracle for the mutation sweep — is replaced by nine theme files (test_verifier_contracts.py,_nat_obligations,_primitive_ops,_calls_modules,_adt_decreases,_refinements,_shadow_audits,_mutation_obligations,_mutation_gates_smt), each under 1,500 lines, alongside the pre-existingtest_verifier_coverage.py(untouched). The shared harness — the_verify/_verify_ok/_verify_err/_verify_warn/_nat_sub_statushelpers plus theEXAMPLES_DIR/ALL_EXAMPLEScorpus constants and the_MKsource template — moves to a newtests/verifier_helpers.pyimported per file. Mechanical and behaviour-preserving: all 474 tests are carried over unchanged, every class / helper / constant moves byte-for-byte (46/46-block differential against the pre-split file), and the[tool.mutmut]oracle selection tracks the new files. The issue's 8-file plan became nine under the 1,500-line ceiling (the #746 refinement-predicate class alone is ~1,300 lines, and the #387 hardening battery split in two). Completes the oversized-test-oracle split program (#420, #419, #839). - Split
tests/test_codegen.pyinto twenty-one feature-focused test files (#419). The monolithic 21,225-line, 161-class codegen test file — the largest file in the tree, and the codegen oracle for the mutation sweep — is replaced by twenty-one feature files (test_codegen_expressions.py,_calls,_infrastructure,_interpolation,_effects,_data_types,_arrays,_refinements,_strings,_string_builtins,_numeric,_io,_collections,_json,_decimal,_host_effects,_nat_guards,_translator_fixes,_gc_alloc,_gc_rooting,_gc_reclamation), each under 1,500 lines (the issue's acceptance criterion), alongside the six pre-existingtest_codegen_*modules. The shared harness — the eleven_compile*/_run*/ WAT-and-GC assertion helpers (four of which lived interspersed between classes) plus the_IO_PRELUDE/_INLINE_BUILTIN_NAMESfixture constants — moves to a newtests/codegen_helpers.pyimported per file. Mechanical and behaviour-preserving: all 1,219 tests (including the 10stress-marked) are carried over unchanged and every class, helper, and constant moves byte-for-byte; per-file import blocks are computed from actual usage, and the[tool.mutmut]oracle selection tracks the new files. The issue's original 9-file plan was drawn when the file was 10,019 lines / 118 classes; at 21,225 / 161 the same theme boundaries yield twenty-one files. This closes out roadmap Tier 1 (safety net and runtime robustness). (Companion to the #420test_checker.pysplit.) - Consolidated the triplicated
_resolved()helper intests/test_checker_modules.py(#835).TestCrossModuleTyping,TestVisibilityEnforcement, andTestModuleCallParsedeach carried their own@staticmethod _resolved()that builds aResolvedModulefrom source text, and theTestModuleCallParsedcopy had drifted (file_path=Path("/fake")vs the others'Path(f"/fake/{'/'.join(path)}.vera")). Replaced all three with a single module-level_resolved_module()called directly, unifying on the non-driftedfile_pathform. Follow-up to the #420 split, which had deliberately preserved the duplication byte-for-byte (unifying changes thefile_pathvalue, which flows into diagnosticlocation.fileviavera/checker/modules.py); the 45 module tests are unaffected.
v0.0.191
[0.0.191] - 2026-07-01
Changed
- Codegen type-check-impossible guards now raise
CodegenInvariantError([E699]) (#657, follow-up to #626). The #626 audit classified everyreturn Noneinvera/codegen/**andvera/wasm/**; #658 converted the 104 user-actionable SILENT_SKIP sites toCodegenSkip([E602]). This converts the genuine INVARIANT_DEFENSIVE guards — non-forwarding dispatch fall-throughs and shape guards on states the type checker has already rejected — toraise CodegenInvariantError, surfaced at the_compile_fnboundary as an[E699]"internal compiler error, please file a bug" (severityerror) instead of a silent skip or a mis-attributed[E602]. 21 sites: 2 incodegen/closures.py, 19 inwasm/operators.py; pinned bytests/test_codegen_invariant_e699.py. Behaviour-preserving — the converted paths are# pragma: no cover(type-check-impossible). A closure-body invariant now propagates to_compile_fnfor a single[E699](rather than being caught in_compile_lifted_closure, which emitted[E699]and returned None so the enclosing function also got a spurious[E602]"closure skipped"); and the quantifier-predicate guard is tightened to require exactly one parameter (so a malformed multi-parameter predicate raises rather than silently using the first). - Corrected the #626 audit's PROPAGATE premise (#657). The audit assumed #658 made every codegen leaf raise, leaving the PROPAGATE
if x is None: return Noneforwards dead. That is false for the #630[E615]string-interpolation channel:_translate_interpolated_stringrecords failing segments and returnsNone, which propagates up and is dropped loudly as[E615]at the_compile_fnboundary. Sotranslate_expr/translate_blockstill returnNonereachably, and forwards of them are load-bearing PROPAGATE, preserved — not removed (satisfying the issue's "preserved (still reachable)" criterion). Fiveoperators.pysites the audit had tagged INVARIANT were in fact such forwards and are kept asreturn None; theinference.pydefensive sites are kept asOptional-by-contract. The invariant is now documented invera/skip.py("Reachable None via the [E615] channel") and at every preserved forward, so a future cleanup pass can't repeat the mistake (which the test suite caught as anAssertionErrorinTestE615LoudInterpolationFallthrough630).
v0.0.190
[0.0.190] - 2026-07-01
Changed
- Text I/O is UTF-8 regardless of the host locale, enforced by a gate (#645). Python's text-mode
open()/Path.read_text()/Path.write_text()— andsubprocess.run/Popen/check_output(..., text=True)captures — fall back tolocale.getpreferredencoding()(cp1252 on en-US Windows) when noencoding=is given, so a Vera source / doc / fixture / program output containing→,—, or any non-ASCII byte failed on a locale-default Windows shell. #641 papered over CI with aPYTHONUTF8=1backstop; this is the durable fix. A newscripts/check_explicit_encoding.pyAST-audits every text-modeopen()/read_text()/write_text()and everysubprocess(..., text=True)capture undervera/,scripts/,tests/, requiring an explicitencoding="utf-8"literal (binary / bytes mode skipped; a deliberate exception opts out with# encoding-exempt: <reason>), wired into pre-commit and the CIlintjob. It found and fixed 160 bare file-I/O sites across 16 files plus 97 subprocess text captures across 18 files (far more than the ~30 the issue estimated — the suite has grown ~5× since #641). The audit's scope also coverstempfile.NamedTemporaryFile/TemporaryFile/SpooledTemporaryFileopened in text mode (they default to binary, but a text one is a locale-encoded write just likeopen(..., "w")— the idiom test helpers use to stage.verasource containing→/—; 29 such sites across 9 files fixed). TheveraCLI reconfigures stdin as well as stdout/stderr to UTF-8 at startup, so a Vera program's non-ASCII output and input (e.g.IO.read_charon piped UTF-8) round-trip on any locale. Together these made text I/O locale-independent and thePYTHONUTF8=1CI backstop (#641) was removed — verified by a clean Windows matrix with the backstop gone (the tempfile and stdin gaps were surfaced by that matrix, not the audit, which is why removing the backstop is its own acceptance test). Pinned bytests/test_check_explicit_encoding.py(checker logic, scope-discovery coverage, and a repo-clean assertion). (Folds in the stdio / subprocess work that had briefly been split to #832.)