Skip to content

Releases: aallan/vera

v0.1.2

Choose a tag to compare

@aallan aallan released this 09 Jul 21:15
3177cbd

Added

  • A scheduled CI workflow now checks the limitation tables against live issue states (#852). check_limitations_sync.py --check-states runs 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-arm cell (Python 3.12 — platform coverage, not version coverage), and the wheel-availability gate now checks manylinux_2_38_aarch64 wheels 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-vuln flag is gone from the dependency audit (left in place it would have masked a real regression); and the actions/setup-python toolcache now carries pip 26.1.2 natively, so the pre-audit pip 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 check with a raw Python traceback (#966). The E009 TransformError is raised inside a Lark token callback, which lark wraps in VisitError — so the CLI's except VeraError never 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 a VisitError carrying a VeraError and re-raises the original, so both text and --json modes 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-exempt waiver 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 the W001/W002 warning codes; TESTING.md documents the scheduled limitations-sync workflow and scopes the # diag-fields-exempt gate 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-exempt opt-out is now honoured wherever an unresolvable field can arise (#955). The field-presence pass (check_source) appended its non-literal-severity violation and continued 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; the spec_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. A spec_ref that resolves but cites the wrong/nonexistent section, or an error_code not in ERROR_CODES, is a content error, not a tagging gap — the opt-out never suppresses either, marker or not; and the error_code pass skips non-literal codes entirely, so it has nothing to waive. On vera/ 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 Diagnostic is reachable as the helper's result (#956). The #827 narrowing (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 one Diagnostic and hands it to something other than a return / .append(...) (e.g. self.dispatch(d)) still had that ctor elected as plumbing and skipped by all three passes, so a bogus spec_ref or unregistered error_code on it would ship silently. A new _ctor_is_reachable_as_result predicate now requires the ctor be return-ed, appended, or bound to a local later return-ed/appended, gated after the existing len(ctors) == 1 check so the #827 ambiguity 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), a for/with loop target, a walrus, an augmented assignment, or an except ... as name — 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/with targets, walrus — plus import ... as, except ... as, match captures, the helper's own parameters, and nonlocal declarations 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_call treated 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 in vera/ has the self.<attr>.append(...) shape, so the check is now scoped to that. On vera/ 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

Choose a tag to compare

@aallan aallan released this 08 Jul 20:27
5de983d

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 the vera serve request lifecycle (§9.5), closure layout + call_indirect dispatch (§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 diagram vera/README.md lacked 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 with DE_BRUIJN.md), the Diagnostic record, 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 in README.md. Every diagram that replaced an ASCII original keeps the text in a collapsed <details> block tagged text — the docs feed llms-full.txt and agents reading files in a terminal, and images are invisible to both — and no diagram carries live counts (they drift outside check_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 compile does 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 in docs/index.html §Why and mirrored into the generated index.md. The set's design system, conventions, and inventory are documented in assets/diagrams/README.md; the README.md project-structure tree's stale module counts (11 → 13 codegen, 9 → 19 wasm) ride along.
  • The canonical E001 diagnostic is now guarded against drift in all five of its documentation mirrors (#829). TestErrorDisplaySync already compared README.md, docs/index.html and spec/00-introduction.md against the diagnostic generated from vera/errors.py, but two mirrors were unguarded — AGENTS.md's example --json block, and the hardcoded example in scripts/build_site.py that generates docs/index.md — and #826 had already drifted the ungated pair. Both are now compared. AGENTS.md's error_code / spec_ref / fix are matched exactly (the extractor json.loads the block, so the escaping is resolved and every field is directly comparable) and its ellipsis-truncated description / rationale are prefix-compared; build_site.py's block is extracted by anchoring on the closing code fence rather than on the spec_ref text, so a spec_ref drift 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/private visibility 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 gaining IO.read_char and the Exn<T> effect; README's "three-tier verification" delivered-claim scoped to the two implemented tiers; DESIGN.md's effect lists gaining HttpServer (and Random/Diverge rows) on a list that claims to mirror vera 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.py listed 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"; and build_site.py now rewrites image embeds to raw URLs (a blob/ page is not image bytes), fixing the four diagrams inlined into llms-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_fold is 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-element Array<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_decimal now exercises decimal_compare through a three-arm Ordering match (the "not yet supported in codegen" note was disproven by running it); and the #773 scalar-only-Eq parenthetical speaks in the past tense.
  • The verifier no longer proves false postconditions: a callee's ensures is now assumed only on the paths that establish it (#957). _translate_call_with_info (vera/smt.py) assumed each callee ensures — and each refined return's predicate — with a bare self.solver.add(...), which lands on the solver's base assertion stack. check_valid folds _path_conditions around the goal only, so a fact learned inside an if outlived the branch and became an unconditional fact about the caller's own slots (_build_callee_env binds 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) injects ret == @Nat.0 - 5, which with @Nat's implicit ret >= 0 entails @Nat.0 >= 5 — the very precondition the branch guard existed to establish. A caller's false ensures(@Nat.0 >= 5) then discharged, and vera verify printed OK / 6 verified (Tier 1) with no diagnostic for a program that vera run traps on. Worse, two calls in mutually-exclusive arms inject contradictory facts, the base solver goes UNSAT, and every obligation discharges vacuously — silently deleting real E501 diagnostics, including the ones the #776 fix above adds. Each injected fact is now wrapped by _guard_fact in z3.Implies(z3.And(*self._path_conditions), fact), matching the idiom _translate_match already 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...
Read more

v0.1.0

Choose a tag to compare

@aallan aallan released this 04 Jul 20:15
e87a996

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-Eq type (pointer identity, not value equality) are now rejected at check with E242 / E243 — the severest failure class, closed.
  • Structural show / hash / eq over composite, recursive, nested-generic, and generic-mutually-recursive ADTs; a polymorphically-recursive Box<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-Float default fixed.
  • Diagnostics. Zero-size Array / generic-@T / refinement edge cases now reject cleanly at check with a single E135 / 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

Choose a tag to compare

@aallan aallan released this 02 Jul 20:32
1215df8

Added

  • examples/async_http_fanout.vera — the 37th example and the showcase for the v0.0.192 concurrent <Async>: two async(Http.get(url)) requests started back-to-back so network latency overlaps, then awaited and folded into a Z3-proved 0..3 status summary (4 obligations Tier-1 verified). examples/async_futures.vera and EXAMPLES.md's async section were reworded to stop teaching eager-only async: eager applies to non-whitelisted shapes, direct Http.get/Http.post calls run concurrently (#841).
  • Two conformance programs pinning the apply_fn fix (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) and ch05_apply_fn_arity (negative fixture, expected_error: E201 — a wrong-arity apply_fn must fail check).
  • Spec completions after the server-effects sprint: §0.7 chapter index gains the Chapter 13 row; §7.7.6 documents the Async marker effect alongside its peers; §9.5.3 drops the stale "(future work)" framing on async Http composition; §9.3.4 scopes the Future<T> zero-overhead claim to eagerly-evaluated futures; §11.10.5 documents apply_fn's checker typing.
  • wasmtime serve line-buffering caveat (spec §13.7 + TOOLCHAIN.md), empirically confirmed: under --world server a handler's IO.print without 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; native vera serve does 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 serverwasmtime serve command sequence, HttpServer in every effect list, and the 14-chapter/eight-effect counts — mirrored in the build_site.py literals that generate index.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): HttpServer and 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-hoc vera.* 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 gains codegen/wasi.py, runtime/wasi_host.py, runtime/server.py (plus tail_position.py, text.py) with recounted package totals (~65,000 lines of Python); CONTRIBUTING.md's branch-protection section now records enforce_admins and the actual review requirement; MUTATION.md documents the stale-.pyc purge step for hand-run mutation kills; ch10_float_predicates renamed ch09_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_fn is 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.vera showed it) and typing the call as Unknown — so wrong arity or argument types passed check with exit 0 and only surfaced at compile time (a raw WAT assembler error at line 0,0, or a silently skipped function), and applying an effects(<IO>)-rowed fn value inside an effects(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_fn is 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 applied FunctionType: 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 an apply_fn result 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. Redefining fn apply_fn(...) — previously green at check time while codegen hijacked every 2+-argument call to call_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 compile used 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 --json output, so a prelude span can never render user code (inject_prelude returns the buffer it parsed; codegen's _warning/_error resolve 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 calls option_map was 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

Choose a tag to compare

@aallan aallan released this 02 Jul 17:52
91e19d1

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 server packages the same contract-checked handle(@Request -> @Response) program vera serve hosts natively (#305) as a component exporting wasi:http/incoming-handler@0.2.0stock wasmtime serve runs 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.3 acceptance). A generated adapter wrapper reads method / path-with-query / headers / body through the wasi:http interfaces into the arena, constructs the Request ADT in the guest heap from the compilation's own constructor layouts (WAT-level shadow-stack rooting throughout), calls handle, decodes the Response, 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.stderr route to the serve console; read_line/read_char/read_file/write_file/get_env/args/exit are 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 run rejects server-world artifacts with a pointer to wasmtime serve (wasmtime-py's built-in host has no wasi:http); the native vera serve driver 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 $Libc dodge and incoming-body.finish both superseded — recorded in WASI.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

Choose a tag to compare

@aallan aallan released this 02 Jul 15:27
e3927ba

Added

  • Experimental WASI Preview 2 target — vera compile/run --target wasi-p2 (#237). vera compile --target wasi-p2 emits a binary WebAssembly component whose vera.* IO + Random host imports are implemented on top of WASI 0.2 interfaces, runnable by any stock wasip2 host — wasmtime run works with no flags and no Vera bindings (wasmtime.wat2wasm accepts component text, so no external componentizer and no new dependency). The component wraps the unchanged core module (the default --target wasm emission is untouched, pinned by test): each (import "vera" "op") becomes a same-named call_indirect shim 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 the contract_fail/overflow_trap channels) 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_realloc is a bump allocator over a GC-exempt 64 KiB scratch arena below gc_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 read i32.load8_u (they are u8; retptr-slab reuse leaves garbage in the padding — found live as an EOF misread as last-operation-failed with a stale list length as a "handle"). vera run --target wasi-p2 executes the component under wasmtime-py's built-in add_wasip2() host (new vera/runtime/wasi_host.py) with the core path's ExecuteResult contract: stdout/stderr capture (+ the #543 live tee), os.environ snapshot, cwd preopen for file ops, argv, IO.read_line CRLF handling matching the core host's universal-newlines behavior (a lone \r separator stays content — spec chapter 13), and the #516 trap-kind taxonomy — a contract violation classifies as contract_violation with the full violation text recovered from the WASI stderr channel (structured frames do not cross the component boundary; the JSON envelope's frames is 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 stock wasmtime run invokes) and a plain lifted main for scalar returns (Int/Nat as s64, Float64, Unit); a String/heap-returning main runs via wasi:cli/run and 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.0 carries ok/err only, so IO.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 filesystem error-code case — quota, not disk-quota — only at add_wasip2 instantiation): 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 that handle[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 remaining vera.* 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 let bindings (String / Array<T>) were never shadow-rooted (#847, fixed by #846). The plain-let pair branch in translate_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.argsArray<String>, IO.read_lineString) is rooted only host-side during construction (_ShadowGuard, popped on return), so the first Vera-side allocation after the let could 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.print of an IO.args element after a nat_to_string call printed 2::++2@ instead of 2:aa+bb; string_join over the swept backing chased overwritten element pointers). Found stress-testing #237 under VERA_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_fileResult<String, String>, IO.get_envOption<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_request printed its progress marker between async(...) and await, 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 the async(...), so program order supplies a real happens-before (buffer write → request issuance → arrival → interrupt) and the assertion strengthens from in to ==; prompt issuance at the async point stays pinned by the #841 request-ordering and operand-stack tests, so the reorder loses nothing. The test matrix also sets fail-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, so interrupt_main()'s pending flag could materialize outside execute()'s protected region on slow runners (an xdist worker crash on two macOS jobs, a completed run with exit_code=None on ubuntu — 3/12 jobs at one head). 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-class machinery intercepts — before firing, with a bounded deadline so the test can never hang.

v0.0.193

Choose a tag to compare

@aallan aallan released this 02 Jul 11:51
fa40f5b

Added

  • <HttpServer> — verified HTTP request handling, served by vera serve (#305). A server program defines a total, contract-checked handler — public fn handle(@Request -> @Response) effects(<HttpServer>) — and vera serve prog.vera [--port N] hosts the accept loop: the loop lives in the host, so handlers need no Diverge, are termination-checked like any function, and every contract on the handler (or its helpers) is an ordinary Tier-1/Tier-3 obligation (the new examples/http_server.vera proves a status-range postcondition at Tier 1). HttpServer is a built-in marker effect (no operations; spec §7.7.5); Request (method, path, headers, body) and Response (status, headers, body) are prelude ADTs injected when referenced (headers are Map<String, String>; user definitions shadow). Per-request isolation: each request runs on a fresh module instance (fresh execute() — instantiation measured at ~0.02 ms in the Stage-0 spike), so State<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 — the vera run --json envelope shape); the connection is always answered. Host marshalling is layout-driven: CompileResult.adt_layouts exports the constructor layouts this compilation computed, build_request_adt shadow-roots every intermediate allocation (#570/#692 discipline), decode_response_adt reads the returned ADT, and both fail loudly on an unexpected layout shape; a new InstanceCaller adapts a (Store, Instance) pair to the caller protocol the heap helpers already use. Handler IO.print goes 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 recorded unsupported in fn_param_types — a post-injection pass now re-registers exactly those. New: tests/conformance/ch09_http_server.vera (level verify — needs no network, unlike ch09_http), examples/http_server.vera, spec §9.5.6, and a TOOLCHAIN.md serve recipe.

v0.0.192

Choose a tag to compare

@aallan aallan released this 02 Jul 09:43
78e5ef1

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 single vera.async_http_get / vera.async_http_post host import that issues the request on a host ThreadPoolExecutor at the async(...) point (request issuance keeps program order) and returns the Future as a #578 bit-31-tagged handle wrapper (new wrap kind 4); await probes the value's first word for the wrapper tag and blocks on vera.async_await for fused handles, passing eager values through unchanged. Two overlapping async(Http.get)s are pinned by a server-side request-ordering test (no wall-clock). Every other async shape 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 than W002'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 2c host_decref_handle(4, handle), cancelling the task and evicting the store entry (observable via ExecuteResult.host_store_sizes["future"]). Worker threads run only the pure fetch halves (fetch_get / fetch_post, split out of the Http host callbacks in vera/runtime/http.py) and never touch guest memory; the Result ADT is built at await time on the guest thread. Ctrl-C during a blocked await rides the wasmtime>=45 BaseException 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 the async point, outcome buffered host-side until await). A function may now declare a Future<T> return type (previously [E605]-skipped — Future<T> is transparent in the type mapper). The await handle-check keys on the literal type Future<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 misclassify await(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-typed let has 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 when e's effect row stays within the commutative whitelist ({Http} in v1; the Async marker 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 the async() 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.py into 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_err helpers and the EXAMPLES_DIR / EXAMPLE_FILES / CLEAN_EXAMPLES / WARN_EXAMPLES constants) extracted to a new tests/checker_helpers.py imported by all eight (matching the repo's existing from tests.<module> import precedent). 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 #419 test_codegen.py split.)
  • Split tests/test_verifier.py into 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-existing test_verifier_coverage.py (untouched). The shared harness — the _verify / _verify_ok / _verify_err / _verify_warn / _nat_sub_status helpers plus the EXAMPLES_DIR / ALL_EXAMPLES corpus constants and the _MK source template — moves to a new tests/verifier_helpers.py imported 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.py into 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-existing test_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_NAMES fixture constants — moves to a new tests/codegen_helpers.py imported per file. Mechanical and behaviour-preserving: all 1,219 tests (including the 10 stress-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 #420 test_checker.py split.)
  • Consolidated the triplicated _resolved() helper in tests/test_checker_modules.py (#835). TestCrossModuleTyping, TestVisibilityEnforcement, and TestModuleCallParsed each carried their own @staticmethod _resolved() that builds a ResolvedModule from source text, and the TestModuleCallParsed copy 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-drifted file_path form. Follow-up to the #420 split, which had deliberately preserved the duplication byte-for-byte (unifying changes the file_path value, which flows into diagnostic location.file via vera/checker/modules.py); the 45 module tests are unaffected.

v0.0.191

Choose a tag to compare

@aallan aallan released this 01 Jul 16:22
acca643

[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 every return None in vera/codegen/** and vera/wasm/**; #658 converted the 104 user-actionable SILENT_SKIP sites to CodegenSkip ([E602]). This converts the genuine INVARIANT_DEFENSIVE guards — non-forwarding dispatch fall-throughs and shape guards on states the type checker has already rejected — to raise CodegenInvariantError, surfaced at the _compile_fn boundary as an [E699] "internal compiler error, please file a bug" (severity error) instead of a silent skip or a mis-attributed [E602]. 21 sites: 2 in codegen/closures.py, 19 in wasm/operators.py; pinned by tests/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_fn for 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 None forwards dead. That is false for the #630 [E615] string-interpolation channel: _translate_interpolated_string records failing segments and returns None, which propagates up and is dropped loudly as [E615] at the _compile_fn boundary. So translate_expr / translate_block still return None reachably, and forwards of them are load-bearing PROPAGATE, preserved — not removed (satisfying the issue's "preserved (still reachable)" criterion). Five operators.py sites the audit had tagged INVARIANT were in fact such forwards and are kept as return None; the inference.py defensive sites are kept as Optional-by-contract. The invariant is now documented in vera/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 an AssertionError in TestE615LoudInterpolationFallthrough630).

v0.0.190

Choose a tag to compare

@aallan aallan released this 01 Jul 13:24
25a86df

[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() — and subprocess.run/Popen/check_output(..., text=True) captures — fall back to locale.getpreferredencoding() (cp1252 on en-US Windows) when no encoding= 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 a PYTHONUTF8=1 backstop; this is the durable fix. A new scripts/check_explicit_encoding.py AST-audits every text-mode open() / read_text() / write_text() and every subprocess(..., text=True) capture under vera/, scripts/, tests/, requiring an explicit encoding="utf-8" literal (binary / bytes mode skipped; a deliberate exception opts out with # encoding-exempt: <reason>), wired into pre-commit and the CI lint job. 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 covers tempfile.NamedTemporaryFile / TemporaryFile / SpooledTemporaryFile opened in text mode (they default to binary, but a text one is a locale-encoded write just like open(..., "w") — the idiom test helpers use to stage .vera source containing / ; 29 such sites across 9 files fixed). The vera CLI 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_char on piped UTF-8) round-trip on any locale. Together these made text I/O locale-independent and the PYTHONUTF8=1 CI 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 by tests/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.)