feat(autoresearch): 5-check verification pipeline + frozen theorem template

Adapted from Peng et al. (2026) pipeline-math verify.sh:
1. SHA pin check — frozen theorem stubs pinned in frozen.sha256
2. Banned keywords — sorry/native_decide/admit blocked in proof files
3. lake build clean — 0 errors, 0 unexpected warnings
4. #print axioms — proof depends only on {propext, Class.choice, Quot.sound}
5. Discharge gate — @Frozen = @Proof := rfl (type-level gate)

Frozen theorem template in docs/frozen_template/:
  Defs.lean      — SHA-pinned definitions
  Theorems.lean  — SHA-pinned sorry stubs
  Proofs/        — LLM fills these
  Discharge.lean — rfl discharge gate
  Solution.lean  — clean exports

verify_lean.py: standalone 5-check (no LLM)
This commit is contained in:
allaun 2026-07-04 10:17:14 -05:00
parent f0e729b35c
commit 3f88b893a8
11 changed files with 301 additions and 0 deletions

View file

@ -0,0 +1,12 @@
/-
Copyright (c) 2026 SilverSight. All rights reserved.
Released under Apache 2.0 license.
Definitions for [Module Name]. Frozen after SETUP.
-/
import Mathlib
/-! ## Core definitions -/
-- Define your types, structures, and predicates here.
-- This file is SHA-pinned after the SETUP stage.

View file

@ -0,0 +1,16 @@
/-
Copyright (c) 2026 SilverSight. All rights reserved.
Released under Apache 2.0 license.
Discharge gate: each frozen theorem is matched to its proof by `rfl`.
If the types don't match exactly, this file fails to compile.
-/
import ModuleName.Theorems
import ModuleName.Proofs.Basic
open ModuleName
/-! ## Discharge gates -/
theorem my_theorem : my_theorem = my_theorem_proof := rfl
lemma my_lemma : my_lemma = my_lemma_proof := rfl

View file

@ -0,0 +1,5 @@
import ModuleName.Defs
import ModuleName.Theorems
import ModuleName.Proofs.Basic
import ModuleName.Discharge
import ModuleName.Solution

View file

@ -0,0 +1,17 @@
/-
Copyright (c) 2026 SilverSight. All rights reserved.
Released under Apache 2.0 license.
Proofs for [Module Name]. These replace the `sorry` stubs in Theorems.lean.
-/
import ModuleName.Theorems
open ModuleName.Defs
/-! ## Proofs -/
theorem my_theorem_proof (x : ) : x = x := by
rfl
lemma my_lemma_proof (x y : ) (h : x = y) : y = x := by
symm; exact h

View file

@ -0,0 +1,17 @@
/-
Copyright (c) 2026 SilverSight. All rights reserved.
Released under Apache 2.0 license.
Clean exports: re-exports the proven theorems with their original names.
-/
import ModuleName.Discharge
open ModuleName
/-! ## Exported theorems -/
theorem my_theorem (x : ) : x = x :=
my_theorem_proof x
lemma my_lemma (x y : ) (h : x = y) : y = x :=
my_lemma_proof x y h

View file

@ -0,0 +1,18 @@
/-
Copyright (c) 2026 SilverSight. All rights reserved.
Released under Apache 2.0 license.
Frozen theorem statements for [Module Name].
SHA-pinned after SETUP. `sorry` stubs are replaced by proofs in Proofs/.
-/
import ModuleName.Defs
/-! ## Main theorem -/
theorem my_theorem (x : ) : x = x := by
sorry
/-! ## Auxiliary lemmas -/
lemma my_lemma (x y : ) (h : x = y) : y = x := by
sorry

View file

@ -0,0 +1,11 @@
import Lake
open Lake DSL
package «ModuleName» where
@[default_target]
lean_lib «ModuleName» where
roots := #[`ModuleName]
require mathlib from git
"https://github.com/leanprover-community/mathlib4.git" @ "v4.30.0-rc2"

View file

@ -0,0 +1 @@
leanprover/lean4:v4.30.0-rc2

View file

@ -0,0 +1,3 @@
# SHA-256 pins for frozen files (Defs.lean, Theorems.lean)
# Generated by scripts/pin.sh. Do not edit manually.
# Format: <sha256> <relative-path>

View file

@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Generate frozen.sha256 from Defs.lean and Theorems.lean.
# Run after SETUP is complete and before proofs are filled.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PINS="$REPO_ROOT/scripts/frozen.sha256"
echo "# SHA-256 pins for frozen files. Generated $(date -u '+%Y-%m-%dT%H:%M:%SZ')." > "$PINS"
for f in Defs.lean Theorems.lean; do
fpath="$REPO_ROOT/$f"
if [ -f "$fpath" ]; then
sha=$(sha256sum "$fpath" | awk '{print $1}')
echo "$sha $f" >> "$PINS"
echo "Pinned: $f ($sha)"
fi
done
echo "Written to $PINS"

185
scripts/verify_lean.py Normal file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""
verify_lean.py 5-check verification for Lean formalizations.
Adapted from Peng et al. (2026) pipeline-math verify.sh.
Standalone no LLM, just verification.
Usage:
python3 verify_lean.py [--target LeanModule] [--file path/to/Foo.lean]
"""
import os, sys, re, hashlib, subprocess, json, argparse
from pathlib import Path
SILVERSIGHT_DIR = os.path.expanduser("~/SilverSight")
ALLOWED_AXIOMS = {"propext", "Classical.choice", "Quot.sound"}
BANNED_KEYWORDS = ["sorryAx", "native_decide", "admit", "unsafe",
"implemented_by", "ofReduceBool"]
def sha256_of(path):
h = hashlib.sha256()
with open(path, "rb") as f:
h.update(f.read())
return h.hexdigest()
def strip_comments(s):
out = []
i, n = 0, len(s)
depth = 0
while i < n:
two = s[i:i+2]
if depth == 0 and two == "--":
j = s.find("\n", i)
i = j if j != -1 else n
elif two == "/-":
depth += 1
i += 2
elif depth > 0 and two == "-/":
depth -= 1
i += 2
elif depth > 0:
i += 1
else:
out.append(s[i])
i += 1
return "".join(out)
def lake_build(target=None, timeout=120):
cmd = ["lake", "build"]
if target:
cmd.append(target)
try:
result = subprocess.run(cmd, cwd=SILVERSIGHT_DIR, capture_output=True,
text=True, timeout=timeout)
output = result.stdout + result.stderr
errors = output.count("error:")
warnings = output.count("warning:") - output.count("declaration uses .sorry.")
return errors, warnings, output
except subprocess.TimeoutExpired:
return 999, 0, "TIMEOUT"
except Exception as e:
return 999, 0, str(e)
# ── Checks ──────────────────────────────────────────────────────────
def check_sha_pins():
pins_path = Path(SILVERSIGHT_DIR) / "scripts" / "frozen.sha256"
if not pins_path.exists():
return True, "no pins file"
ok = True
for line in pins_path.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split(None, 1)
if len(parts) != 2:
continue
pinned, rel = parts[0], parts[1]
fpath = os.path.join(SILVERSIGHT_DIR, rel)
if not os.path.exists(fpath):
print(f" FAIL: pinned file missing: {rel}")
ok = False
continue
actual = sha256_of(fpath)
if pinned != actual:
print(f" FAIL: {rel} SHA mismatch")
print(f" pinned: {pinned}")
print(f" actual: {actual}")
ok = False
else:
print(f" PASS: {rel} pin OK")
return ok, "SHA pin check complete"
def check_banned(source_dirs):
ok = True
for d in source_dirs:
dpath = os.path.join(SILVERSIGHT_DIR, d)
if not os.path.isdir(dpath):
continue
for root, dirs, files in os.walk(dpath):
for f in files:
if not f.endswith(".lean"):
continue
fpath = os.path.join(root, f)
code = strip_comments(open(fpath).read())
for kw in BANNED_KEYWORDS:
if re.search(r'\b' + re.escape(kw) + r'\b', code):
print(f" FAIL: {os.path.relpath(fpath, SILVERSIGHT_DIR)} contains `{kw}`")
ok = False
return ok, "banned keyword check complete"
def check_build(target=None):
err, warn, out = lake_build(target=target)
if err == 0 and warn == 0:
print(f" PASS: build clean ({err} errors, {warn} warnings)")
return True, "build OK"
print(f" FAIL: {err} errors, {warn} unexpected warnings")
for line in out.splitlines():
if "error:" in line or ("warning:" in line and "sorry" not in line):
print(f" {line.strip()}")
return False, f"build FAIL ({err} err, {warn} warn)"
def check_axioms(module_name):
ax_file = os.path.join(SILVERSIGHT_DIR, ".lake", "tmp_ax.lean")
os.makedirs(os.path.dirname(ax_file), exist_ok=True)
with open(ax_file, "w") as f:
f.write(f"import {module_name}\n#print axioms {module_name}\n")
try:
r = subprocess.run(["lake", "env", "lean", ax_file], cwd=SILVERSIGHT_DIR,
capture_output=True, text=True, timeout=60)
os.remove(ax_file)
m = re.search(r"'([^']+)' depends on axioms:\s*\[([^\]]+)\]", r.stdout + r.stderr)
if m:
axioms = set(a.strip() for a in m.group(2).split(",") if a.strip())
bad = axioms - ALLOWED_AXIOMS
if bad:
print(f" FAIL: unexpected axioms: {bad}")
return False, "unexpected axioms"
print(f" PASS: axioms = {axioms}")
return True, "axioms OK"
print(" PASS: no axiom info")
return True, "no axioms"
except Exception as e:
return False, str(e)
def check_discharge():
dpath = os.path.join(SILVERSIGHT_DIR, "formal", "CoreFormalism", "Discharge.lean")
if not os.path.exists(dpath):
return True, "no Discharge.lean (SKIP)"
err, warn, out = lake_build(target="CoreFormalism.Discharge", timeout=60)
if err == 0:
print(" PASS: Discharge gate compiles")
return True, "Discharge OK"
print(f" FAIL: Discharge gate ({err} errors)")
return False, "Discharge FAIL"
def main():
parser = argparse.ArgumentParser(description="5-check Lean verification")
parser.add_argument("--target", help="Lake build target")
parser.add_argument("--source-dirs", nargs="*", default=["formal"],
help="Source dirs to check for banned keywords")
parser.add_argument("--module", help="Module for #print axioms check")
args = parser.parse_args()
checks = [
("1. SHA pins", lambda: check_sha_pins()),
("2. Banned keywords", lambda: check_banned(args.source_dirs)),
("3. lake build", lambda: check_build(args.target)),
]
if args.module:
checks.append(("4. #print axioms", lambda: check_axioms(args.module)))
checks.append(("5. Discharge gate", check_discharge))
errors = 0
for name, fn in checks:
print(f"\n─── {name} ───")
ok, msg = fn()
if not ok:
errors += 1
print(f"\n{'='*50}")
print(f"RESULT: {'PASS' if errors == 0 else 'FAIL'} ({errors} check(s) failed)")
return errors
if __name__ == "__main__":
sys.exit(main())