Skip to content

Releases: aallan/vera

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.)

v0.0.189

Choose a tag to compare

@aallan aallan released this 01 Jul 11:13
70031fb

Changed

  • The UTF-8 "safe decode" invariant now lives in one helper (#592). Six sites decoded WASM-memory bytes with errors="replace" so a corrupt String (ptr, len) pair from an upstream codegen bug surfaces as U+FFFD rather than a raw Python UnicodeDecodeError escaping wasmtime's trampoline as a "python exception" cause — the #516 / #522 / #589 contract that a user-level program never produces a Python traceback. The invariant was re-implemented at all six (host_print / host_stderr / host_contract_fail / the String-return extractor in vera/codegen/api.py, _read_wasm_string in vera/runtime/heap.py, and _read_string in vera/wasm/markdown.py) and guarded by six structural source-grep tests plus one end-to-end test covering only host_print — so five of the six sites had no behavioural coverage, and the greps would break under exactly the refactor the issue proposed. The errors="replace" decode now lives in one place — a new vera.runtime.text.safe_utf8_decode (the ROADMAP Tier-2 single-source-of-truth theme, same class as the #828 error-code registry) — reached only through a shared _slice_and_decode helper (vera/runtime/heap.py) that the three WASM-memory string readers (_read_wasm_string and a new _read_string_export there, and markdown _read_string) delegate to. The host_print / host_stderr / host_contract_fail host imports and the String-return extractor route through those readers instead of decoding inline, so no bytes(...).decode(...) call survives outside _slice_and_decode (a net simplification of execute() — the closures no longer re-implement the memory read). The six structural greps are replaced by one helper unit test plus three end-to-end tests that wire the production readers (_read_wasm_string, _read_string_export, markdown _read_string) over a memory region seeded with invalid UTF-8, so a strict-decode regression surfaces as a UnicodeDecodeError escaping the trampoline — caught at one point per reader rather than needing a grep per call site, and transitively covering the host imports / extractor that route through them; the host_print end-to-end test is retained to pin the trampoline fact itself, independently of the production path. Behaviour-preserving. (The network-response decode sites in http.py / inference.py are a separate, already-resolved family — #591, closed — with deliberate per-site handling (errors="replace" for Http.get / Http.post; intentionally strict for Inference.complete, so a non-UTF-8 LLM response fails as a structured Result::Err rather than silently gaining U+FFFD), pinned by their own TestNetworkResponseUtf8Hygiene591 class; they are outside this WASM-memory helper's scope by design.)

v0.0.188

Choose a tag to compare

@aallan aallan released this 01 Jul 08:00
802b9f8

Added

  • Diagnostic-field discipline is now enforced (#682). spec/00-introduction.md §0.5.1 requires every diagnostic to carry a rationale, a fix, and a spec_ref, but the Diagnostic dataclass defaults all three to "" — so a partially-tagged diagnostic compiled and shipped silently, weakening the "diagnostics as instructions" guarantee (DESIGN.md §Checkability). New scripts/check_diagnostic_fields.py (wired into pre-commit and the CI lint job, mirroring the #597 walker-coverage gate) AST-parses every Diagnostic(...) constructor and self._error(...) / self._warning(...) call under vera/ and fails when one of the three fields is missing — or when a present spec_ref does not resolve to a real spec section/chapter with a matching title (the present-but-wrong case, e.g. citing §4.3 "Operators" when §4.3 is "Slot References"). The exemption surface is explicit and reasoned, never silently inferred (DESIGN.md §"Explicitness over convenience"): a warning is not required to carry a fix (it has no corrected-code template); the codegen _error/_warning helpers — internal-compiler (E699) and "function skipped" diagnostics with no user fix or spec section — are exempt via a documented registry in the script; and a one-off internal/defensive site carries a # diag-fields-exempt: <reason> marker (a dedicated token, deliberately not # noqa:-prefixed so it cannot collide with ruff's suppression namespace). A cheap emission-side check also requires every literal error_code under vera/ to be registered in ERROR_CODES (catching typos / unregistered codes); enforcing each code's uniqueness per concept and its presence is tracked in #828. The pre-commit hook also triggers on spec/**/*.md (not only vera/**/*.py), since the validity pass reads the spec section/chapter titles — a spec-only retitle can invalidate an otherwise-unchanged citation, and now re-runs the check locally rather than only in CI.

Fixed

  • Corrected the pre-existing wrong spec_ref citations and documented typed holes (#682). The adversarial audit behind the new validity gate found 30-plus diagnostics across the checker, the parser-error factories, the codegen path, and the verifier whose spec_ref cited the wrong section number or title (e.g. all arithmetic/comparison/logical operator errors cited §4.3 "Operators", but §4.3 is "Slot References" — arithmetic is §4.4, comparison §4.5, logical §4.6) — a misleading instruction is worse than a missing one. Every spec_ref under vera/ now resolves. Typed holes (?) had no spec section at allW001 (and the codegen E614) cited a fabricated §3.10 "Typed Holes" — so they are now documented in a new spec/04-expressions.md §4.17 "Typed Holes", which both diagnostics cite. Two further citations resolved to a real but semantically wrong section (which the validity gate, checking only that a ref resolves with a matching title, accepts — so they needed reviewer judgment): the unresolved-qualified-call warning E220 cited §7.4.1 "Ambiguous Operations" (the multi-match case) but the error is an unresolved op, so it now cites §7.4 "Performing Effects"; and the type-alias-cycle error E132 cited the unrelated slot-namespace section §3.8 "Type Alias and Reference Resolution" — the rule it actually enforces ("an alias must resolve to a concrete type; the alias chain must be acyclic") had no spec home, so a sentence stating it was added to spec/02-types.md §2.6.3, which E132 now cites. Two fix templates were also corrected for machine-actionability: the unresolved-bare-call diagnostics (E200 and the codegen cross-module backstop) replaced a literal-looking import the.module(f) placeholder with import <module>(f) plus a "replace <module> with that module's path" hint; and the ADT-invariant error (E120) — whose old fix suggested invariant(@Field.0 > 0), a form that cannot resolve (@Field is not a type, and data invariants are NYI, #686) — now points at the documented refinement-type workaround (type Positive = { @Int | @Int.0 > 0 };). Finally, the missing-contract-block error E001 moved from §5.4 "Contract Clauses" (which states the requires/ensures rule but not the effects one) to §5.2 "Function Declaration Syntax" — the one section that shows the complete mandatory block, matching the diagnostic's own fix and the sibling function-structure errors (E1xx in the checker) that already cite it; its three rendered mirrors (README.md, docs/index.html, spec/00-introduction.md) moved in lockstep, pinned by the TestErrorDisplaySync suite.

  • Four error_code collisions corrected (#682 review + audit). Each of E130/E210/E320/E600 was shared by two unrelated diagnostics, so a --json consumer keying on error_code would mislabel one of each pair: the slot-resolution E130 (expressions.py) was reused by the "Decimal takes no type arguments" type-application error (resolution.py), now E134 "Type does not take type arguments"; the unknown-constructor E210 (calls.py) by the empty-Tuple() error, now E216 "Empty tuple type"; the unknown-constructor-in-pattern E320 (control.py) by the empty-tuple-pattern error, now E323 "Empty tuple pattern"; and the "unsupported parameter type" E600 (functions.py) by codegen's "refinement base resolves to another refinement" error (contracts.py), now E618 "Nested refinement base unsupported". Each stable code now maps to exactly one diagnostic concept; the new codes are registered in ERROR_CODES, and the three reachable ones (E134, E216, E618) are pinned by collision-regression tests (E323's site is parser-unreachable).

  • Five further semantically-wrong spec_refs corrected (exhaustive audit) (#682). A final multi-agent audit of every diagnostic — each finding adversarially verified — caught five more of the resolves-but-wrong-section class the validity gate cannot detect (it checks resolution, not appropriateness): E122 (pure violation) and E002 (missing effects()) cited effect-chapter sections describing how effects work, not the rules they enforce, and now both cite §5.5 "Effect Declaration", which states "Every function MUST declare its effects" and "A function that declares effects(pure) MUST NOT perform any effects" verbatim; the alias-arity error E133 cited the slot-namespace §3.8 and moved to §2.6.3 alongside E132; the @Int@Nat narrowing obligation E503 cited §4.7 "Let Bindings" (it fires far beyond let) and now cites §2.2.1 "Int and Nat compatibility", where the obligation is stated; and the contract-testing skip warning E701 moved from the chapter-level Chapter 6, "Contracts" to the dedicated §0.5.6 "Contract-Driven Testing".

  • spec/04-expressions.md §4.17 "Typed Holes" gains a worked example (#682). The section now shows ? in context (@Int.0 + ?), demonstrating it is inferred to type @Int, type-checks with W001, and is rejected at compile with E614. Validating it exposed that scripts/check_spec_examples.py's check and verify stages treated warning-severity diagnostics as failures (unlike the CLI vera check, and unlike the verify stage's own error-only filter one line later) — both stages now filter to error severity, so a legitimately-checkable warning-emitting example is validated rather than mis-failed.

Changed

  • Backfilled 54 under-tagged checker diagnostics (#682). Every self._error(...) site in vera/checker/ (calls, control, expressions, core, resolution) — plus partial direct Diagnostic(...) constructions in the parser-error factories, the tester, the monomorphizer, and the verifier's obligation-fallback — now carries a concrete rationale, a fix template, and a spec_ref. The canonical example from the issue, vera check on a function mixing @Bool.0 + @Int.0 (E140), now emits a Fix: paragraph instead of stopping at the rationale; pinned by tests/test_checker.py::TestErrorCodes::test_E140_carries_a_fix_paragraph_682.

  • The TestHostHandleReclamation573 GC-reclamation suite is marked stress (#738). The class compiles and runs full reclamation programs at scale (~minutes locally), so it is now deselected from the default per-PR pytest run and runs under pytest -m stress (and nightly CI), reclaiming local inner-loop time.

  • CI hardening — CodeRabbit Pro+ configuration (no compiler or runtime change). .coderabbit.yaml gains three agentic pre-merge checks — changelog covers public-surface changes, spec and implementation move together, and diagnostics carry full metadata — all in warning (advisory) mode, so they surface drift during review without blocking merge. The deterministic floor (mypy, scripts/check_*.py, pre-commit, the conformance suite) remains the only hard gate; these checks complement it and target judgment-gaps it cannot cheaply cover (e.g. "the changelog describes the change", not merely "a bullet exists"). Also raised the GitHub-Checks ingestion timeout to 15 min so CodeRabbit waits for the full multi-OS test matrix before commenting; added AGENTS.md to the Code Guidelines set; linked aallan/vera-bench so a downstream-breaking CLI / exit-code / diagnostic-format change here is flagged during review; and extended the tests/** review guidance to call out the commutative-operations slot-ordering trap. The deterministic counterpart for asserts — ruff flake8-bandit for assert-as-guard ([#657](https://github.com/a...

Read more