Skip to content

Add Lean 4 formalization of Value lattice and RVM core semantics - #819

Open
Anand Krishnamoorthi (anakrish) wants to merge 4 commits into
microsoft:mainfrom
anakrish:formal-lean
Open

Anand Krishnamoorthi (anakrish) wants to merge 4 commits into
microsoft:mainfrom
anakrish:formal-lean

Conversation

@anakrish

Copy link
Copy Markdown
Collaborator

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-exact Int64/UInt64/BigInt/F64Bits model mirroring src/number.rs, including Rust's non-lawful equality/ordering (NaN, lossy BigInt/f64 comparisons) — consistent with src/verify/number_proofs.rs, which already documents that Number::eq/cmp do not satisfy obeys_eq_spec/obeys_cmp_spec.
  • RegoNumber.lean — a separate, lawful exact-rational number model (LinearOrder, DecidableEq) used for the mathematical Value model and all proofs.
  • RawValue.lean / Value.lean — representation-level and canonical Value types, with proved-terminating mutually recursive beq/compareValue, matching Rust's derived Ord (Null < Bool < Number < String < Array < Set < Object < Undefined, with Undefined sorted last despite being semantically bottom).
  • Canonical.lean — proves Set/Object are 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 to Nodup keys, 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 into Value, proved to always be defined.

RVM phase 1 (formal/Regorus/RVM/)

  • Bytecode.lean / Program.lean — instruction set and program representation mirroring src/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/VMError with bounds-checked register access.
  • Step.lean — executable step and relational Step small-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/admit anywhere in formal/.

CI

Adds .github/workflows/lean.yml, modeled on the conventions of .github/workflows/verus.yml (pinned action SHAs, minimal permissions: contents: read, manual toolchain install, set -euxo pipefail):

  • Installs elan pinned to formal/lean-toolchain.
  • Caches formal/.lake keyed on the toolchain + lake-manifest.json hash.
  • Fetches precompiled Mathlib .olean files via lake exe cache get (tolerant of cache misses; 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.

Scoped to trigger only on changes under formal/** or the workflow file itself.

Why this is useful

  • A machine-checked, independent reference semantics for Value and the RVM core step relation, usable as a differential-testing oracle against the Rust implementation (à la Cedar's DRT pipeline).
  • A foundation for future compiler-correctness proofs (Rego/Azure Policy AST → RVM bytecode) and RVM verifier soundness proofs.
  • Explicitly documents and formalizes subtle invariants that are easy to get wrong in the Rust code: non-lawful Number comparison, Undefined's dual role as both semantic bottom and the greatest element in deterministic sort order, and the unordered-collection semantics of Set/Object despite their sorted BTreeSet/BTreeMap backing representation.

Scope / non-goals of this PR

This does not touch src/ (no behavior change to the Rust engine). It is purely an additive formal/ 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.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 false succeeds 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 > Null currently returns true instead 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 ≥ Null produce a Boolean result rather than a type error.
    .ok (.Bool (decide (Value.compareValue left right ≠ .lt)))

formal/Regorus/RVM/Step.lean:119

  • RVM Or is not general truthiness: String "x" Or Bool false evaluates to Bool true here, 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.

Comment thread .github/workflows/lean.yml Outdated
Comment thread formal/Regorus/RVM/Step.lean Outdated
Comment thread formal/Regorus/RVM/Step.lean Outdated
Comment thread formal/Regorus/Value/RawNumber.lean Outdated
Comment thread formal/Regorus/Value/SourceValue.lean Outdated
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 the Regorus library, so a bare lake build does not build the regorusFormal executable and Main.lean is never typechecked in CI. Build the executable target explicitly; it imports Regorus, so this still checks the full library as well.
  • Files reviewed: 19/19 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@anakrish

Copy link
Copy Markdown
Collaborator Author

Re the latest review ("CI command skips the newly introduced executable target, leaving Main.lean unchecked"): confirmed valid — @[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). Fixed by having CI run lake build regorusFormal explicitly, which still builds/checks the full Regorus library since the executable imports it.

@[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
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.

2 participants