Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [0.0.179] - 2026-06-26

### Fixed

- **Verifier soundness: signed division/modulo, body asserts, and contract-position divisions** (batch 1 of the [#392](https://github.com/aallan/vera/issues/392) `smt.py` soundness audit). Three cases where `vera verify` proved a contract the runtime then rejected — each an implementation-vs-spec divergence the audit surfaced:
- The verifier translated signed `/` and `%` to Z3's **Euclidean** `div`/`mod`, proving `(-7) / 2 == -4` and `(-7) % 2 == 1`, while the runtime (`i64.div_s` / `i64.rem_s`, truncating toward zero) computes `-3` and `-1`. `smt.py` now uses a sign-aware truncated encoding for integer operands ([#799](https://github.com/aallan/vera/issues/799)).
- A body `assert(P)` was ignored by the verifier — a provably-false assertion passed `vera verify` — contradicting spec §6.2.5 ("the compiler verifies this holds"). It now carries a Tier-1 obligation discharged by a two-check: prove `P` → verified; else prove `¬P` (always false) → the new loud `E507`; else Tier 3, guarded at runtime by the §11.14.1 `unreachable` trap ([#800](https://github.com/aallan/vera/issues/800)).
- The divide-by-zero obligation ([#680](https://github.com/aallan/vera/issues/680)) covered only body divisions; a division in a `requires`/`ensures` predicate — evaluated eagerly, with no short-circuit — was silently proved. The primitive-op walk now runs over contract predicates too, so an unguarded contract divisor that can be zero is a loud `E526` instead of a runtime trap on a "verified" program ([#801](https://github.com/aallan/vera/issues/801)).
- Adds the `E507` diagnostic ("Assertion verified false") and the `assert` obligation kind. The audit's three larger findings — Float64-as-Real ([#797](https://github.com/aallan/vera/issues/797)), integer overflow ([#798](https://github.com/aallan/vera/issues/798)), and `string_length` byte-vs-codepoint ([#802](https://github.com/aallan/vera/issues/802)) — remain open.
- **Verifier completeness: body `assert`/`assume` facts discharge later obligations** (batch 2 of the [#392](https://github.com/aallan/vera/issues/392) audit — the assume-half of [#800](https://github.com/aallan/vera/issues/800)). [#800](https://github.com/aallan/vera/issues/800) made a body `assert(P)` carry a Tier-1 *proof* obligation; this adds the *assume* half of the weakest-precondition rule (spec §6.4.1: `assert(P) | P && WP(rest)`, `assume(P) | P ==> WP(rest)`). After the statement, `P` joins the assumption context for every subsequent obligation in its block (including a later call's precondition, threaded through `SmtContext._translate_block`) — and, for a top-level `assert`/`assume`, for the postcondition and a refined return. This moves obligations from Tier 3 to Tier 1 and removes spurious `E503` / `E500` / `E505` errors where a prior assert provably guards the site: `{ assert(@Int.0 >= 0); let @Nat = @Int.0; ... }` no longer false-errors, since the §11.14.1 runtime trap makes the negative witness unreachable. Sound by construction — the fact is assumed only *forward* (never discharging an earlier obligation) and only *within its branch* (never leaking to a sibling), both pinned by regression guards ([#804](https://github.com/aallan/vera/issues/804)).

## [0.0.178] - 2026-06-25

Expand Down Expand Up @@ -2589,7 +2592,8 @@ Small docs sweep — closes six aging documentation issues in one PR. No code c
- Grammar: handler body simplified to avoid LALR reduce/reduce conflict
- `pyproject.toml`: corrected build backend, package discovery, PEP 639 compliance

[Unreleased]: https://github.com/aallan/vera/compare/v0.0.178...HEAD
[Unreleased]: https://github.com/aallan/vera/compare/v0.0.179...HEAD
[0.0.179]: https://github.com/aallan/vera/compare/v0.0.178...v0.0.179
[0.0.178]: https://github.com/aallan/vera/compare/v0.0.177...v0.0.178
[0.0.177]: https://github.com/aallan/vera/compare/v0.0.176...v0.0.177
[0.0.176]: https://github.com/aallan/vera/compare/v0.0.175...v0.0.176
Expand Down
3 changes: 2 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ Proof obligations became first-class records with a warm Z3 session, and the LSP
| v0.0.176 | 21 Jun | **A call's precondition is now checked even when the call's result is discarded** ([#730](https://github.com/aallan/vera/issues/730)). |
| v0.0.177 | 21 Jun | **Integer division/modulo by zero and array index bounds now carry auto-synthesised obligations (E526/E527), closing the last item in the Tier-0 silent-failures roadmap section; an op inside a closure/quantifier/handler body stays runtime-guarded rather than statically obligated** ([#680](https://github.com/aallan/vera/issues/680)). |
| v0.0.178 | 25 Jun | **`vera builtins/effects/errors --json` compiler introspection subcommands make the compiler the source of truth for its own built-ins, effects, and error codes, each with a best-effort `since` version** ([#539](https://github.com/aallan/vera/issues/539)). |
| v0.0.179 | 26 Jun | **The `smt.py` soundness-audit batch lands: signed `/` and `%` truncate toward zero, a body `assert(P)` carries a Tier-1 obligation (loud `E507`), contract-position divisions get a `div_zero` obligation, and a prior `assert`/`assume` now discharges later obligations plus the postcondition at Tier 1** ([#392](https://github.com/aallan/vera/issues/392)). |

---

Expand All @@ -404,4 +405,4 @@ Proof obligations became first-class records with a warm Z3 session, and the LSP
| Spec chapters | 7 | 10 | 11 | 12 | 13 | 13 | 13 | 13 |
| Code coverage | — | — | — | 90% | 91% | 96% | 96% | 95% |

Total: **1,400+ commits, 174 tagged releases, 77 active development days.**
Total: **1,400+ commits, 179 tagged releases, 77 active development days.**
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ cp /path/to/vera/SKILL.md ~/.claude/skills/vera-language/SKILL.md

## Project status

Vera is in **active development** at v0.0.178 — 1,400+ commits, 178 releases, 4,897 tests, 91% code coverage, 92 conformance programs, 35 examples, and a 13-chapter specification. See **[HISTORY.md](HISTORY.md)** for how the compiler was built.
Vera is in **active development** at v0.0.179 — 1,400+ commits, 179 releases, 4,939 tests, 91% code coverage, 92 conformance programs, 35 examples, and a 13-chapter specification. See **[HISTORY.md](HISTORY.md)** for how the compiler was built.

The reference compiler — parser, AST, type checker, contract verifier (Z3), WASM code generator, module system, browser runtime, and runtime contract insertion — is working. The language specification is in draft across [13 chapters](spec/).

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Priority lives in this file and nowhere else — issues carry kind and area labe

## Where we are

4,923 tests, 92 conformance programs, 35 examples, 13 spec chapters.
4,939 tests, 92 conformance programs, 35 examples, 13 spec chapters.

## The roadmap

Expand Down
4 changes: 2 additions & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This is the single source of truth for Vera's testing infrastructure, coverage d

| Metric | Value |
|--------|-------|
| **Tests** | 4,923 across 40 files (~62,000 lines of test code; 4,891 passed + 16 stress, 16 skipped) |
| **Tests** | 4,939 across 40 files (~62,000 lines of test code; 4,907 passed + 16 stress, 16 skipped) |
| **Compiler code coverage** | 95% Python, 61% JavaScript — 91% combined (CI minimum: 80%) |
| **Conformance programs** | 92 programs across 9 spec chapters, validating every language feature |
| **Example programs** | 35, all validated through `vera check` + `vera verify` |
Expand Down Expand Up @@ -60,7 +60,7 @@ python scripts/fix_allowlists.py --fix # auto-fix stale allowlists
| `test_checker.py` | 548 | 6,347 | Type synthesis, slot resolution, effects, effect subtyping, contracts, exhaustiveness, cross-module typing, visibility, error codes, string built-ins, generic rejection, IO operation types, Markdown types, Regex types, abilities, Map collection, Set collection, Decimal type, Json type, Html type, Http effect, Inference effect, removed legacy name regression |
| `test_obligations.py` | 282 | 1083 | Reified proof obligations + warm `VerificationSession` (#222 Phase A): full-corpus differential oracle (warm session == cold `verify()` on diagnostics, summary, and obligation stream, plus warm-twice determinism, across all 35 examples and every verify/run-level conformance program), summary↔obligation tier-bookkeeping consistency, per-kind unit tests (requires / ensures / decreases / nat_sub / call_pre statuses, counterexamples, error codes), content-key stability + same-text-two-sites span disambiguation, session solver reuse, type-error short-circuit, ADT-registry resync between programs; plus the Phase B incremental suite — identical-source full replay, callee-body-edit replays callers while callee-contract-edit invalidates them, span-shift and ADT-edit conservative invalidation, cross-program isolation, timeout-status never cached (monkeypatched solver), FIFO eviction bound; plus the #727 dedup pin — a violating call in a let RHS records exactly one E501 diagnostic and one call_pre obligation |
| `test_verifier.py` | 473 | 9,218 | Z3 verification, counterexamples, tier classification, call-site preconditions, branch-aware preconditions, pipe operator, cross-module contracts, match/ADT verification, decreases verification, mutual recursion, refined Bool/String/Float64 param sorts, **@Nat subtraction underflow obligation** (#520 — Path-A obligation discharge via requires/path-conditions/path-aware Z3 refutation, pure-literal exclusion, Int-Int and Nat-Int → Int exemptions), **@Nat binding-site narrowing obligation** (#552 — Tier-1 `value >= 0` at let/call-arg/effect-op-arg/ctor-field/match-bind/literal-destructure narrowing, #520 double-emit disjointness, E503 counterexample-witness pin; #747 — generic-instantiation, ADT sub-pattern, non-literal tuple-destructure, and cross-module imported-ctor narrowing, with concrete-site Tier-3 narrowings (incl. the if-expr-source SMT gap, recorded per-component) classified as codegen-guarded `tier3_runtime`, generic function-formal call-args guarded on the monomorphised callee (keyed on `call_target`, not the erased generic decl), and the two genuinely-unguarded residuals — effect-op-arg narrowings (no codegen guard yet, #754) and generic-instantiated constructor-field narrowings (constructors carry no per-field `@Nat` mono metadata) — surfacing the `E504` unguarded warning; #749 — IndexExpr/InterpolatedString walker-recursion pins, `_fresh_slot_var` nat-alias unit test, `_narrows_into_nat` verifier/codegen soundness parity), **refinement-predicate verification** (#746 — Tier-1 discharge at let/call-arg/ctor-field/effect-op-arg/match-bind/tuple-destructure narrowings and refined return positions, E505 violation with counterexample, E506 Tier-3 for untranslatable/non-primitive-base predicates, the R3 already-refined-source exemption via predicate-AST equality, the R5 violating-return pin, the refinement-over-`@Nat` `>= 0 && P` conjoin, and the **refined-ADT-sub-pattern arm-fact carry** — a `Some(@PosInt)` bind on `Option<PosInt>` carries the field's source-type predicate into the arm body so a downstream `@Nat` narrowing discharges instead of a false E503, and reaches call **preconditions** in the arm body via the SMT match-translation fact hook so `Some(@PosInt) -> needs_positive(@PosInt.0)` discharges instead of a false E501, while a genuine `Option<Int>` narrowing stays obligated (E505/E501), never silently assumed; an **alias-base refined return** (`{ @Age \| @Age.0 >= 18 }`) is assumed by the caller via the predicate binder name; and a **refined return from a match arm** (`Some(@PosInt) -> @PosInt.0`) discharges via a global `arm-matched => source-fact` SMT implication on both the normal and generic-fast paths, a violating `Option<Int>` payload still E505), **per-monomorphization generic verification** (#732 — an instantiated `forall<T>` body is verified per concrete instantiation: a generic-body `@Nat` underflow is caught (naming the instantiation, with a counterexample) or discharged when guarded, the never-instantiated residual stays Tier-3 `E520`, collapsed-type-var (`A=B=Int`) De Bruijn reindex soundness, one-diagnostic dedup across multiple instantiations, and discovery of a generic reached only through a `decreases(...)` measure, plus a mixed-`tier3`/`timeout` aggregate-label completeness pin and a recursive-generic-clone source-name pin so `decreases` resolves on the monomorphised instance), **primitive-operation safety obligations** (#680 — division/modulo by-zero `E526` and array-index-bounds `E527`, the in-bounds/out-of-bounds two-check with float-exemption, honest Tier-3 for opaque lengths, the off-by-one and `i >= 0` lower-bound pins, walker recursion through array-literal / assert / interpolated-string / let-destructure positions, the opaque-shadow Tier-3 routing that stops an untranslatable `let`/destructure from silently discharging a stale same-type-outer divisor, De Bruijn-correct `E526`/`E527` fix hints rendered from the actual operands, Tier-1 projection of literal-constructor destructure components (`let Tuple<@Int, @Int> = Tuple(10, 6); _ / @Int.1` discharges `10 != 0` rather than shadowing to Tier-3, while a non-literal source stays a tracked Tier-3 shadow), a tracked placeholder pushed for *every* destructured component so same-type De Bruijn positions don't collapse (`Tuple(10, <opaque>)` keeps `@Int.0` on the opaque second component, never silently discharging against the literal `10`), `_contains_opaque_shadow` recursion so a *compound* divisor / subtraction operand embedding a shadow (`shadow + 1`) falls to Tier-3 instead of a spurious `E526`/`E502` counterexample, and tracked shadowing of match-arm pattern slots bound over an untranslatable (effect-op) scrutinee so a primitive op in the arm falls to Tier-3 rather than silently discharging against a stale same-name outer (`match Source.next(()) { Some(@Int) -> 1 / @Int.0 }`); plus a **multi-agent shadow/projection audit battery** (`TestShadowAuditDivision680` / `TestShadowAuditSubtraction680` / `TestShadowAuditIndex680` / `TestDestructureDeBruijnAlignment680` / `TestShadowAuditInteractions680`) — 57 differential tests pinning the invariant trichotomy (safe → verified, opaque/shadow → Tier-3, provably-unsafe → loud) across compound shadows, modulo parity, multi-component De Bruijn alignment with value-distinct siblings, intervening-type namespaces, stacked / nested destructures, opaque match scrutinees, nested blocks, and intra-block scoping; **mutation-validated** — every shadow/projection test was confirmed to flip RED when its target machinery (`_is_opaque_shadow` / `_contains_opaque_shadow` / the De Bruijn placeholder push / the `let`-shadow / the match-arm `_fresh_pattern_env` / literal projection) is deliberately broken, so none is green-for-the-wrong-reason) |
| `test_soundness_392.py` | 20 | 302 | #392 audit batch 1 — verifier soundness fixes for three impl-vs-spec divergences: signed div/mod truncate toward zero (#799), body `assert(P)` carries a Tier-1 obligation (#800), and divisions in contract predicates carry a `div_zero` obligation (#801). Test-first: each fails on the pre-fix verifier |
| `test_soundness_392.py` | 36 | 584 | #392 audit batches 1–2 — verifier soundness/completeness fixes: signed div/mod truncate toward zero (#799), body `assert(P)` carries a Tier-1 obligation (#800), divisions in contract predicates carry a `div_zero` obligation (#801), and the #804 assume-half of #800's `assert` rule — a prior `assert`/`assume` discharges later obligations (including a later call's precondition) + the postcondition at Tier 1, removing false E501/E503/E500/E505. Test-first: each fails on the pre-fix verifier |
| `test_monomorphize_differential.py` | 13 | 682 | #732 differential soundness: the verifier's per-monomorphization instantiation discovery covers every instantiation codegen emits (name coverage + per-generic count), over real generic programs (conformance ch02/ch09, `examples/generics.vera`) plus inline cases for the soundness-critical scenarios — collapsed type vars, **prelude combinator emission** (`option_map`), transitive generics, a generic whose type arg is fixed only by a **where-helper's return** (a `Float64`-returning helper, so the unresolved-var `"Bool"` phantom default cannot mask a miss), a generic whose type arg is fixed only by an **imported constructor** (`id2(MkBox(7))` — the verifier's mono-context must include `_module_constructors`, else it phantom-defaults and misses codegen's `id2<Box>`), a generic whose type arg is fixed only by an **imported function's return** (`id_g(make_int(...))` — the verifier's mono-context must seed `fn_ret_types` from imported functions, else it phantom-defaults and misses codegen's `id_g<Int>`, plus a **private-shadow** case pinning the imported-fn seeding stays unfiltered like codegen since filtering would diverge into a false Tier-1), and a generic reached only through a **contract clause or `where` helper** (codegen must seed Pass 1.5 from the shared node-level walk, not just `decl.body`, or it skips the clone → `CodegenSkip` at run time) — so a missed instantiation (a false Tier-1) is caught. Guards against a vacuous pass when codegen emits nothing, plus a **determinism guard** (`vera compile --wat` is byte-stable across `PYTHONHASHSEED` — the mono worklist sorts its instantiation sets) |
| `test_codegen.py` | 1,213 | 21,093 | WASM compilation, arithmetic, Float64, Byte, arrays (incl. compound element types), ADTs, match (incl. nested patterns), generics, State\<T\>, Exn\<E\> handlers, control flow, strings, string escape sequences, IO (read\_line, read\_file, write\_file, args, exit, get\_env, sleep, time, stderr), bounds checking, quantifiers, assert/assume, refinement type aliases, pipe operator, string built-ins, built-in shadowing, parse\_nat Result, GC, Markdown host bindings, Regex host bindings, Map collection, Set collection, Decimal type, Json type, Html type, Http effect, Inference effect, Random effect, example round-trips, GC shadow stack overflow, **WASM tail-call optimization** (#517 — `return_call` emission for tail-position calls, 50K- and 1M-iteration stress, structural assertions on `return_call`/plain `call` boundary, **GC-aware TCO for allocating fns (#549 — `$gc_sp` restore before each `return_call`)**, postcondition-fallback regression (still reverts to plain `call`), analyzer unit tests covering Block-trailing / IfExpr-both-branches / MatchExpr-arm-bodies / let-value-NOT-marked / call-args-NOT-marked / ExprStmt-statement-NOT-marked / IfExpr-condition-NOT-marked / MatchExpr-scrutinee-NOT-marked), **@Nat narrowing runtime guards** (#552 let site; #747 extends to tuple-destructure / top-level match-bind / ADT sub-pattern / concrete ctor-field / call-arg sites — `i64.lt_s; unreachable` net, @Int targets exempt), **refinement-predicate runtime guards** (#746 — primitive-base (incl. `@Bool` / `@Float64`) refined params/returns traverse a `$vera.contract_fail` predicate guard at the function boundary: violating value traps, valid passes, call-args guarded transitively, generic refined returns guarded on the monomorphised instance; non-primitive `@Array` refinements (`array_length(...) > 0`) guarded too, the alias-aware `@Nat` base conjoins its implicit `>= 0`, and erased `@Unit` refinements emit no guard; **tuple-component boundary guards** decompose a `Tuple<PosInt, Int>` param/return at the FFI boundary — loading each refined / `@Nat` component from the heap value and guarding it recursively for nested tuples, so a violating component traps instead of laundering past the verifier's component assumption; a **generic tuple alias** (`type Box<T> = Tuple<T, Int>`) substitutes its argument so `Box<PosInt>` still guards its component, and a mutually-recursive (infinite) tuple alias **fails closed with E617** rather than silently emitting partial guards; a **refinement OVER a tuple** (`type Pair = { @Tuple<PosInt, Int> \| true }`) is unwrapped so its components are guarded too, recursively through nested `Tuple<Pair, Int>`) |
| `test_codegen_contracts.py` | 32 | 570 | Runtime pre/postconditions, contract fail messages, old/new state postconditions |
Expand Down
Loading