Add Lean 4 formalization of Value lattice and RVM core semantics - #819
Anand Krishnamoorthi (anakrish) wants to merge 4 commits into
Conversation
Introduces a formal/ Lake package with a mechanized Lean 4 model of: - Value lattice: bit-exact RawNumber/RawValue mirroring src/number.rs and src/value/mod.rs, a lawful RegoNumber/Value model with proven DecidableEq/heterogeneous LinearOrder (Undefined ordered last), canonical unordered Set/Object semantics (permutation-invariant canonicalization via mkSet/mkObject), asymmetric 3-valued truthiness/guards/lift2 bottom propagation, and a bottom-free SourceValue embedding. - RVM phase 1: Instruction/Program bytecode model mirroring src/rvm/instructions and src/rvm/program, VMState/VMError, and an executable step function with an equivalent relational Step semantics, covering load/move, arithmetic/comparison/logical/guard, simple collection construction, index/contains/count, and return/halt instructions. Loops, comprehensions, rule calls/caching, virtual-data lookup, and suspendable/HostAwait execution are deferred to later phases. All modules build cleanly under Lean 4.13.0 (formal/lake build) with zero sorry/admit placeholders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb05efcf-456e-4c41-8bd6-f266cbf31daf
Mirrors the conventions of .github/workflows/verus.yml: actions pinned to full commit SHAs, minimal read-only permissions, and manual toolchain setup (no third-party setup actions). - Installs elan pinned to the version in formal/lean-toolchain. - Caches formal/.lake keyed on the toolchain + lake-manifest.json hash. - Runs `lake exe cache get` to fetch precompiled Mathlib oleans (tolerant of failure; `lake build` falls back to compiling anything missing). - Runs `lake build`. - Explicitly greps for `sorry`/`admit` and fails the job if found, since `lake build` only warns (does not error) on `sorry`. Triggers on push/PR to main, scoped to formal/** and the workflow file itself. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb05efcf-456e-4c41-8bd6-f266cbf31daf
There was a problem hiding this comment.
🟡 Changes recommended
Several formal semantics diverge from Rust behavior, and the CI installer executes mutable unverified upstream code.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds an independent Lean 4 formalization of Regorus value semantics and core RVM execution.
Changes:
- Models numbers, values, canonical collections, truthiness, and source values.
- Defines core RVM bytecode, state, execution, and proofs.
- Adds pinned Lean dependencies and CI validation.
File summaries
| File | Description |
|---|---|
formal/Regorus/Value/Value.lean |
Defines canonical semantic values and ordering. |
formal/Regorus/Value/Truth.lean |
Models truthiness and undefined propagation. |
formal/Regorus/Value/SourceValue.lean |
Defines bottom-free source values. |
formal/Regorus/Value/RegoNumber.lean |
Adds lawful rational numbers. |
formal/Regorus/Value/RawValue.lean |
Models representation-level values. |
formal/Regorus/Value/RawNumber.lean |
Models Rust-compatible numeric representations. |
formal/Regorus/Value/Canonical.lean |
Canonicalizes sets and objects. |
formal/Regorus/RVM/Step.lean |
Implements core RVM transitions and proofs. |
formal/Regorus/RVM/State.lean |
Defines VM state and errors. |
formal/Regorus/RVM/Program.lean |
Defines formal RVM programs. |
formal/Regorus/RVM/Bytecode.lean |
Mirrors the RVM instruction set. |
formal/Regorus/RVM.lean |
Aggregates RVM modules. |
formal/Regorus.lean |
Aggregates the formalization. |
formal/Main.lean |
Adds a minimal executable entry point. |
formal/lean-toolchain |
Pins Lean 4.13.0. |
formal/lakefile.lean |
Configures the Lake package. |
formal/lake-manifest.json |
Locks Lean dependencies. |
formal/.gitignore |
Excludes generated Lean artifacts. |
.github/workflows/lean.yml |
Builds and validates proofs in CI. |
Review details
Suppressed comments (4)
formal/Regorus/RVM/Step.lean:99
- This also applies heterogeneous ordering even though the modeled strict RVM rejects comparisons between different value variants. Add the same kind check used for the other ordered comparisons; otherwise
Null ≤ Bool falsesucceeds instead of producing a type error.
.ok (.Bool (decide (Value.compareValue left right ≠ .gt)))
formal/Regorus/RVM/Step.lean:103
- This also applies heterogeneous ordering even though the modeled strict RVM rejects comparisons between different value variants. Add a value-kind check; for example,
Bool true > Nullcurrently returnstrueinstead of a type error.
.ok (.Bool (decide (Value.compareValue left right = .gt)))
formal/Regorus/RVM/Step.lean:107
- This also applies heterogeneous ordering even though the modeled strict RVM rejects comparisons between different value variants. Add a value-kind check; otherwise cross-type inputs such as
Bool true ≥ Nullproduce a Boolean result rather than a type error.
.ok (.Bool (decide (Value.compareValue left right ≠ .lt)))
formal/Regorus/RVM/Step.lean:119
- RVM
Oris not general truthiness:String "x" Or Bool falseevaluates toBool truehere, but Rust rejects the non-Boolean operand in both strict and non-strict modes. Restrict this operation to the modeled mode's supported operand types.
.ok (Regorus.lift₂
(fun x y => .Bool (Regorus.isTruthy x || Regorus.isTruthy y))
left right)
- Files reviewed: 19/19 changed files
- Comments generated: 5
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Fixes 5 issues identified by Copilot's automated review of PR microsoft#819: - Step.lean: Lt/Le/Gt/Ge now reject cross-kind operands with a TypeMismatch error, matching the Rust VM's strict-mode discriminant check (dispatch.rs), instead of silently falling back to heterogeneous ordering. - Step.lean: And/Or now only accept Boolean operands (erroring otherwise), matching the Rust `to_bool` helper's strict-mode semantics, instead of using general Rego truthiness (isTruthy) which would wrongly accept e.g. numbers/strings as operands. - RawNumber.lean: `div` no longer returns NaN for `infinity / (finite zero)`; per IEEE-754 this is signed infinity, not an invalid operation (only 0/0, inf/inf, and 0*inf are NaN-producing). - SourceValue.lean: `embed` now canonicalizes Set/Object payloads via `Value.mkSet`/`Value.mkObject` instead of embedding raw lists directly, so e.g. `SourceValue.Set [Null, Null]` embeds as a one-element set, preserving the Value representation invariant. Updated `embed_bottomFree` and added forall-mem/membership helper lemmas (`Value.ListBottomFree.forall_mem`, `Value.ObjectBottomFree.forall_mem`, `Value.mem_canonicalizeObject`) to reprove bottom-freedom through the canonicalizing constructors. - lean.yml: elan is now installed from a specific pinned release binary (v4.2.4) downloaded over HTTPS and verified by SHA-256 before execution, instead of piping the mutable `elan/master` install script into bash. Mirrors the pin-and-verify pattern already used by verus.yml for the Verus binary. All theorems still fully proved (zero sorry/admit); full `lake build` verified locally after these changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb05efcf-456e-4c41-8bd6-f266cbf31daf
There was a problem hiding this comment.
🔵 Needs a closer look
The CI command skips the newly introduced executable target, leaving Main.lean unchecked.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
.github/workflows/lean.yml:90
- The explicit
@[default_target]is theRegoruslibrary, so a barelake builddoes not build theregorusFormalexecutable andMain.leanis never typechecked in CI. Build the executable target explicitly; it importsRegorus, so this still checks the full library as well.
- Files reviewed: 19/19 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Re the latest review ("CI command skips the newly introduced executable target, leaving |
@[default_target] in formal/lakefile.lean is only on lean_lib Regorus, so a bare `lake build` never builds/typechecks the regorusFormal executable (root Main.lean). Build it explicitly with `lake build regorusFormal`, which still builds/checks the full Regorus library since the executable imports it. Addresses PR review feedback on microsoft#819. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb05efcf-456e-4c41-8bd6-f266cbf31daf
Summary
Introduces a
formal/Lean 4 (Lake) package with a mechanized formalization of Regorus, following the precedent set by AWS Cedar's Lean formal spec / differential-random-testing approach. This is an independent, additive layer alongside the existing Verus proofs (src/verify/) — it targets whole-language semantics and compiler/VM correctness properties that are a better fit for Lean's tactic/proof-term style than Verus's SMT-based approach.What's formalized so far
Value lattice (
formal/Regorus/Value/)RawNumber.lean— bit-exactInt64/UInt64/BigInt/F64Bitsmodel mirroringsrc/number.rs, including Rust's non-lawful equality/ordering (NaN, lossy BigInt/f64 comparisons) — consistent withsrc/verify/number_proofs.rs, which already documents thatNumber::eq/cmpdo not satisfyobeys_eq_spec/obeys_cmp_spec.RegoNumber.lean— a separate, lawful exact-rational number model (LinearOrder,DecidableEq) used for the mathematicalValuemodel and all proofs.RawValue.lean/Value.lean— representation-level and canonicalValuetypes, with proved-terminating mutually recursivebeq/compareValue, matching Rust's derivedOrd(Null < Bool < Number < String < Array < Set < Object < Undefined, withUndefinedsorted last despite being semantically bottom).Canonical.lean— provesSet/Objectare semantically unordered collections:canonicalizeSet/canonicalizeObject(sort + last-write-wins dedup for objects) with proved permutation-invariance (canonicalizeSet_perm,mkSet_perm,canonicalizeObject_perm/mkObject_perm, the latter honestly scoped toNodupkeys, since duplicate-key permutation invariance is false in general under last-write-wins).Truth.lean—isTruthy/notRvm/guard conditions and strict-bottom-propagation lifting (lift₂), with proved algebraic properties.SourceValue.lean— a bottom-free value subtype with an embedding intoValue, proved to always be defined.RVM phase 1 (
formal/Regorus/RVM/)Bytecode.lean/Program.lean— instruction set and program representation mirroringsrc/rvm/, with in-scope core instructions (load/move, arithmetic/comparison/logical, guards, simple collection construction, index/contains/count, return/halt) fully modeled; out-of-scope instructions (loops, comprehensions, rule calls, virtual-data,HostAwait, Azure Policy opcodes) present as explicit placeholders.State.lean—VMState/VMStatus/VMErrorwith bounds-checked register access.Step.lean— executablestepand relationalStepsmall-step semantics with proved equivalence (step_iff_Step), determinism (Step.deterministic), register-framing lemmas, and strict-bottom-propagation corollaries for arithmetic/comparison/logical instructions.All proofs are complete — zero
sorry/admitanywhere informal/.CI
Adds
.github/workflows/lean.yml, modeled on the conventions of.github/workflows/verus.yml(pinned action SHAs, minimalpermissions: contents: read, manual toolchain install,set -euxo pipefail):elanpinned toformal/lean-toolchain.formal/.lakekeyed on the toolchain +lake-manifest.jsonhash..oleanfiles vialake exe cache get(tolerant of cache misses;lake buildfalls back to compiling anything missing).lake build.sorry/admitand fails the job if found, sincelake buildonly warns (does not error) onsorry.Scoped to trigger only on changes under
formal/**or the workflow file itself.Why this is useful
Valueand the RVM core step relation, usable as a differential-testing oracle against the Rust implementation (à la Cedar's DRT pipeline).Numbercomparison,Undefined's dual role as both semantic bottom and the greatest element in deterministic sort order, and the unordered-collection semantics ofSet/Objectdespite their sortedBTreeSet/BTreeMapbacking representation.Scope / non-goals of this PR
This does not touch
src/(no behavior change to the Rust engine). It is purely an additiveformal/Lean package plus its CI workflow. Loops, comprehensions, rule evaluation/caching, suspendable execution (HostAwait), the static bytecode verifier, and AST-level compiler correctness are left for follow-up work.