Skip to content

feat(verify): VCR-VER-002 — trap-preservation gate over ordeal::trap (#166)#723

Open
avrabe wants to merge 7 commits into
mainfrom
feat/vcr-ver-002-trap-gate
Open

feat(verify): VCR-VER-002 — trap-preservation gate over ordeal::trap (#166)#723
avrabe wants to merge 7 commits into
mainfrom
feat/vcr-ver-002-trap-gate

Conversation

@avrabe

@avrabe avrabe commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

VCR-VER-002 — trap-preservation as an oracle-gated obligation

Consumes ordeal 0.9.0's new ordeal::trap module in synth-verify so a lowering that drops a WASM trap (div-by-zero/overflow guard, OOB bounds check, call_indirect bounds/null/type, unreachable) can no longer pass value-only equivalence. Systematic end of the per-op soundness whack-a-mole (#633/#666/#665/#642/#709).

Refs #166, #242, ordeal#59.

What landed, per step

  • Step 1 — bump. synth-verify ordeal 0.40.9 (API-compatible; all 53 unit tests unchanged). No ordeal pin in MODULE.bazel to touch.
  • Step 2 — synth_verify::trap wrapper. Thin synth-facing layer over ordeal::trap: div_op(WasmOp)->DivOp + trap_div, trap_mem_oob, trap_call_indirect (both TypeTrap::Runtime and StaticallyDischarged — synth's closed-world homogeneous tables use the latter, matching ArmOp::CallIndirect.type_check == None), trap_always. prove_trap_equivalence (full VC) / prove_trap_condition_equivalence (trap-clause only) return a TrapVerdict {Preserved | Dropped(model) | Unknown}; Preserved re-checks the LRAT certificate before returning. Added Bool::from_ordeal (pub(crate)).
  • Step 3 — RED-FIRST GATE (tests/trap_preservation.rs, 6 tests). Non-vacuity proof: for every op class a trap-DROPPING lowering is CAUGHT and a trap-PRESERVING lowering accepted.
  • Step 4 — validator method. TranslationValidator::verify_div_rem_trap_preservation — a dedicated always-checks method gating i32 div/rem on the full VC, with the ARM trap condition derived structurally from the Udf guard (sound in the reject direction). Kept as a separate method (folding into verify_equivalence would wrongly reject the legitimate value-only div rules, since the guard is an encoder-expansion artifact). See the live-path gap below.
  • Step 5 — rivet + claims. VCR-VER-002 roadmap entry (status in-progress). Pinned SYNTH-VCR-VER-002-STATUS in claims.yaml; claim_check 18/18.

Red-first gate output (Sat-on-drop / Unsat-on-preserve)

running 6 tests
test unreachable_elision_is_caught_and_preservation_accepted ... ok
test call_indirect_statically_discharged_drop_is_caught_and_preservation_accepted ... ok
test call_indirect_runtime_type_drop_is_caught_and_preservation_accepted ... ok
test load_store_oob_bounds_drop_is_caught_and_preservation_accepted ... ok
test signed_div_overflow_only_drop_is_caught ... ok
test div_rem_all_four_ops_catch_the_drop_and_accept_preservation ... ok
test result: ok. 6 passed; 0 failed

Op classes that catch the drop: div/rem (all four ops, full trap+value VC; drop caught with a divisor==0 counterexample; partial signed-overflow-only drop caught), load/store OOB (bounds-removal caught), call_indirect (both TypeTrap modes: runtime type-check drop #676-shape AND all-guards drop; closed-world bounds+null drop), unreachable (trap elision caught).

Validator method (--features arm): div_lowering_without_guard_is_rejected_as_trap_drop (bare UDIV — the #633/#666 shape — rejected with divisor==0), div_lowering_with_guard_preserves_the_trap, signed_div_guard_preserves_both_zero_and_overflow_traps, trap_preservation_gate_rejects_non_div_ops (incl. i64 rejection), all green.

Wired vs documented follow-on (honest scope)

Gates

cargo fmt --check (exit 0), cargo clippy --workspace --all-targets -D warnings (exit 0), cargo test --workspace (all green), claim_check.py 18/18 — all green.

DO NOT MERGE — maintainer gates and will re-run the red-first gate.

🤖 Generated with Claude Code

avrabe and others added 7 commits July 11, 2026 16:37
…#166)

ordeal 0.9.0 adds `ordeal::trap` — QF_BV trap-condition builders and
trap-preservation VCs (ordeal#59/TR-019). API-compatible bump: synth-verify
builds clean and all 53 unit tests pass unchanged.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#166)

New `synth_verify::trap` — a thin synth-facing layer over `ordeal::trap`:

- Maps WASM partial ops to trap conditions over synth's own `BV`/`Bool`:
  `div_op(WasmOp)->DivOp` + `trap_div` (÷0 for all four, +INT_MIN/-1 for
  signed), `trap_mem_oob` (load/store OOB), `trap_call_indirect` (bounds ∨
  null-slot ∨ type, both `TypeTrap::Runtime` and `StaticallyDischarged` —
  synth's closed-world homogeneous tables use the latter, matching
  `ArmOp::CallIndirect.type_check == None`), `trap_always` (unreachable).
- `prove_trap_equivalence` (full VC: trap + guarded value, for div/rem) and
  `prove_trap_condition_equivalence` (trap-clause only, for ops synth models
  no value/contents for) returning a synth-facing `TrapVerdict`
  {Preserved | Dropped(model) | Unknown}. `Preserved` re-checks the LRAT
  certificate before returning; a failed re-check downgrades to `Unknown`.

Adds `Bool::from_ordeal` (pub(crate)) so the sibling module can lift terms
built by `ordeal::trap::*` back into the crate's `Bool`.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-VER-002, #166)

Non-vacuity proof for the trap gate: for each partial-op class, a
trap-DROPPING lowering (value kept, guard removed — the #633/#666/#665/#642/
#709 bug shape) is CAUGHT (Dropped/Sat) and a trap-PRESERVING lowering is
accepted (Preserved/Unsat).

- div/rem (all four ops, full VC): drop of the guard is caught with a
  divisor==0 counterexample; a partial signed-overflow-only drop is caught too.
- load/store OOB (trap-clause-only): bounds-check removal caught.
- call_indirect (trap-clause-only): both TypeTrap modes — runtime type-check
  drop (#676 heterogeneous shape) AND all-guards drop caught; the closed-world
  StaticallyDischarged bounds+null drop caught.
- unreachable (trap-clause-only): trap elision caught.

6 tests, all green. Proves the gate actually rejects the trap-drop shapes
rather than trivially accepting.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…VER-002, #166)

`TranslationValidator::verify_div_rem_trap_preservation` gates a div/rem
lowering on preserving the WASM trap (÷0, plus INT_MIN/-1 for signed) AND the
value, via the full `trap::prove_trap_equivalence` VC. The WASM trap condition
comes from the operands; the ARM condition is derived STRUCTURALLY from the
sequence — a `Udf` ⇒ the guard is present (traps on the same condition), its
absence ⇒ the guard was dropped (the #633/#666/#642 shape) ⇒ `Invalid`.

Sound in the reject direction: an unguarded div/rem is rejected with a
divisor==0 counterexample, catching the whole trap-drop class. This is kept as
a DEDICATED method rather than folded into `verify_equivalence`, because the
guard is an encoder-expansion artifact absent from the value-only rule-level
`arm_ops` (folding it in would wrongly reject the legitimate value-only div
rules in comprehensive_verification.rs).

Documented `// VCR-VER-002 follow-on:` gap: fully auto-deriving `opt.may_trap`
from the shipped lowering (vs the Udf-presence proxy), and giving the other
partial-op classes (load/store, call_indirect, unreachable) an ARM trap term,
needs a `may_trap` flag threaded through the `ArmState` exec model or decoding
the emitted guard bytes in `expansion_validator`. Those classes remain gated at
the unit level (tests/trap_preservation.rs).

4 new tests: unguarded div rejected (÷0 counterexample), guarded div/signed-div
accepted, non-div op rejected as unsupported.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the VCR-VER-002 artifact to the verified-codegen roadmap (status
in-progress: div/rem wired mandatorily into the validator, the other partial
classes gated at the unit level pending the exec-model trap flag — documented
follow-on) and pins its status + deliverable files in claims.yaml
(SYNTH-VCR-VER-002-STATUS). claim_check: 18/18 hold.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-VER-002, #166)

- verify_div_rem_trap_preservation early-Errs on i64 div/rem (it hardcodes
  32-bit operands; div_op also maps the i64 variants). New i64-rejection
  assertion.
- Roadmap wording: the method is a DEDICATED always-checks + unit-tested path;
  the live validator entry points (verify_rule/verify_equivalence) do NOT yet
  invoke it — making it a live-path conjunct (needs guard-carrying div-rule
  fixtures) is part of the documented follow-on. Corrects the earlier
  "wired MANDATORILY into the validator" overstatement.

Refs #166, #242, ordeal#59

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s not a valid rivet status)

The agent set status: in-progress — which rivet rejects (allowed: draft/proposed/
approved/implemented/verified/released/...), and pinned a claim REQUIRING
in-progress, so the two gates were mutually unsatisfiable. Set status: proposed
(the full trap-preservation gate is proposed; the red-first VC + trap.rs increment
landed, proven by file-exists evidence), rewrote the title to stop implying the
live path is wired ("...catches dropped WASM traps ...; live-validator wiring in
progress"), and fixed method: formal -> formal-verification (valid enum).

Rivet: 0 non-xref errors. claim_check 18/18.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.55556% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-verify/src/translation_validator.rs 96.52% 4 Missing ⚠️
crates/synth-verify/src/trap.rs 93.54% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant