feat(verify): VCR-VER-002 — trap-preservation gate over ordeal::trap (#166)#723
Open
avrabe wants to merge 7 commits into
Open
feat(verify): VCR-VER-002 — trap-preservation gate over ordeal::trap (#166)#723avrabe wants to merge 7 commits into
avrabe wants to merge 7 commits into
Conversation
#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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
VCR-VER-002 — trap-preservation as an oracle-gated obligation
Consumes ordeal 0.9.0's new
ordeal::trapmodule insynth-verifyso a lowering that drops a WASM trap (div-by-zero/overflow guard, OOB bounds check,call_indirectbounds/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
synth-verifyordeal0.4→0.9(API-compatible; all 53 unit tests unchanged). No ordeal pin inMODULE.bazelto touch.synth_verify::trapwrapper. Thin synth-facing layer overordeal::trap:div_op(WasmOp)->DivOp+trap_div,trap_mem_oob,trap_call_indirect(bothTypeTrap::RuntimeandStaticallyDischarged— synth's closed-world homogeneous tables use the latter, matchingArmOp::CallIndirect.type_check == None),trap_always.prove_trap_equivalence(full VC) /prove_trap_condition_equivalence(trap-clause only) return aTrapVerdict {Preserved | Dropped(model) | Unknown};Preservedre-checks the LRAT certificate before returning. AddedBool::from_ordeal(pub(crate)).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.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 theUdfguard (sound in the reject direction). Kept as a separate method (folding intoverify_equivalencewould wrongly reject the legitimate value-only div rules, since the guard is an encoder-expansion artifact). See the live-path gap below.SYNTH-VCR-VER-002-STATUSinclaims.yaml;claim_check18/18.Red-first gate output (Sat-on-drop / Unsat-on-preserve)
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)
verify_div_rem_trap_preservationthat ALWAYS checks the trap conjunct for i32 div/rem; sound in the reject direction (noUdfguard ⇒ Invalid, catching the thumb-2: i64.div_s(INT64_MIN, -1) silently returns INT64_MIN instead of trapping (overflow guard missing on i64 path; i32 path has it) #633/rv32: i32.rem_s(INT_MIN,-1) spuriously traps — div_s overflow ebreak guard wrongly copied into rem_s (irem_s must return 0) #666/thumb-2: call_indirect emits no bounds-check and no type-check — OOB/wrong-type index does an uncontrolled indirect branch instead of trapping (WASM 4.4.8) #642 class).verify_rule → verify_equivalence → verify_equivalence_parameterized) remain value-only and do not yet invoke the new method — soverify_ruleon an unguarded div does not yet reject. Making it a live-path conjunct requires updating the value-only div-rule fixtures (comprehensive_verification.rs) to carry the guard; deferred.ArmStatehas no trap flag) and the div guard is an encoder-expansion artifact, so div/rem uses a structuralUdf-presence proxy (not a full proof the guard fires on exactly div0∨overflow), i32-only; and load/store/call_indirect/unreachable have no ARM trap term on this path — they stay unit-gated. Closing the gap needs amay_trapflag threaded throughArmState, or decoding the emitted guard bytes inexpansion_validator.Gates
cargo fmt --check(exit 0),cargo clippy --workspace --all-targets -D warnings(exit 0),cargo test --workspace(all green),claim_check.py18/18 — all green.DO NOT MERGE — maintainer gates and will re-run the red-first gate.
🤖 Generated with Claude Code