Skip to content

Commit f12bc7c

Browse files
fix: R-0004 slice 4 (part) — kernel replay resolves its workspace from the input
`--report check-proofs` invoked `lake env lean` with no `cwd`, and `lake` finds its workspace by walking up from wherever it is invoked. So the verdict depended on where the caller stood. Same file, same compiler, absolute path: from the repo root : 3 verified, 0 failed from /tmp : 0 verified, 3 failed [E0806] ... failure=theorem_lookup The second answer is wrong twice over — the proofs do verify, and the reported cause sends the reader to look for missing theorems that are not missing. The workspace was simply never found. `findLakeWorkspace` now walks up from the input file for a lakefile and passes that as `cwd`, so the answer is a property of what is being checked rather than of the caller's shell. It returns `none` rather than guessing a default: a wrong workspace would replay against the wrong library and report confident nonsense. That case is now named ("cannot locate a Lake workspace for ...") and exits non-zero — replay fails closed. Gated in check_proof_freshness.sh (now 17 checks): identical verdict from two directories, the missing-workspace case named, no theorem blamed for it, and a non-zero exit. Verified load-bearing by reverting the fix — the gate reports the exact 3-verified/0-verified split. Note on that check: the first reversion attempt left `ws` unused, Lean's linter rejected the file, and the gate then ran against a STALE BINARY and passed. A sensitivity check has to compile, or it measures the previous build.
1 parent c397efc commit f12bc7c

3 files changed

Lines changed: 89 additions & 4 deletions

File tree

Main.lean

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,29 @@ def renderContracts (parsedModules : List Concrete.Module) (registry : Concrete.
854854
++ Report.renderDiv divObls omegaProved
855855
++ Report.renderOverflow ovfObls omegaProved bvProved
856856

857+
/-- Walk up from `path` for the directory holding a Lake workspace.
858+
859+
Kernel replay used to invoke `lake` with no `cwd`, so the workspace came from the
860+
PROCESS working directory and the verdict depended on where the caller stood
861+
(R-0004 slice 4). Resolving from the input makes the answer a property of what is
862+
being checked. Returns `none` rather than guessing a default: a wrong workspace
863+
would replay against the wrong library and report confident nonsense. -/
864+
partial def findLakeWorkspace (path : String) : IO (Option System.FilePath) := do
865+
let abs ← IO.FS.realPath (System.FilePath.mk path)
866+
let rec up (dir : System.FilePath) (fuel : Nat) : IO (Option System.FilePath) := do
867+
match fuel with
868+
| 0 => return none
869+
| fuel + 1 =>
870+
if (← (dir / "lakefile.toml").pathExists) || (← (dir / "lakefile.lean").pathExists) then
871+
return some dir
872+
match dir.parent with
873+
| some p => if p == dir then return none else up p fuel
874+
| none => return none
875+
-- Start at the file's directory; a file path has a parent, a directory is its
876+
-- own starting point.
877+
let start := if (← abs.isDir) then abs else (abs.parent.getD abs)
878+
up start 64
879+
857880
/-- Run pipeline and check a profile constraint.
858881
If the input file lives inside a `Concrete.toml` project, route
859882
through project mode so std and other dependencies resolve. -/
@@ -1292,10 +1315,30 @@ def compileAndReport (inputPath : String) (reportType : String)
12921315
for (fn, thm) in ensuresThms do
12931316
leanSrc := leanSrc ++ s!"-- {fn} (ensures)\n#check @{thm}\n\n"
12941317
IO.FS.writeFile ⟨tmpPath⟩ leanSrc
1295-
-- Invoke lake env lean to check the file
1318+
-- Resolve the proof workspace from the INPUT, not the process working
1319+
-- directory (R-0004 slice 4). `lake` finds its workspace by walking up
1320+
-- from wherever it is invoked, so with no `cwd` set this verdict depended
1321+
-- on where the user happened to stand: the same file by absolute path gave
1322+
-- "3 verified, 0 failed" from the repo root and "0 verified, 3 failed"
1323+
-- from /tmp. Worse, it blamed each theorem with `theorem_lookup` — the
1324+
-- theorems were fine; the workspace was never found.
1325+
let wsRoot ← findLakeWorkspace inputPath
1326+
match wsRoot with
1327+
| none =>
1328+
-- Say what is actually wrong. Reporting N missing theorems for one
1329+
-- missing workspace sends the reader to look for the wrong thing.
1330+
let msg := s!"cannot locate a Lake workspace for '{inputPath}' (no lakefile.toml or lakefile.lean in any parent directory), so the Lean theorems it references cannot be replayed"
1331+
if reportJson then
1332+
IO.println s!"\{\n \"schema_version\": \"1\",\n \"all_checked\": false,\n \"checks\": [],\n \"lean_error\": \"{msg}\",\n \"summary\": \{\"verified\": 0, \"failed\": 0, \"total\": 0}\n}"
1333+
else
1334+
IO.println s!"=== Lean Proof Kernel Check ===\n\nerror: {msg}"
1335+
return 1
1336+
| some ws =>
1337+
-- Invoke lake env lean to check the file, from the resolved workspace.
12961338
let result ← IO.Process.output {
12971339
cmd := "lake"
12981340
args := #["env", "lean", tmpPath]
1341+
cwd := ws
12991342
env := #[("LAKE_TERM_ANSI", "0")]
13001343
}
13011344
-- Parse results: Lean may put errors on stdout or stderr

ROADMAP.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -932,9 +932,16 @@ Land this task in seven explicit slices:
932932
`proved_by_lean_modulo_trusted`, never unqualified `proved_by_lean`
933933
(`composition_trusted_helper`). The receipt must carry the same distinction.
934934

935-
Make `--report check-proofs`
936-
resolve the proof workspace from the input project/repository rather than
937-
the process working directory. Define a versioned `ProofEvidenceReceipt`
935+
The workspace-resolution half has LANDED: `--report check-proofs` resolves the
936+
Lake workspace by walking up from the INPUT rather than using the process
937+
working directory. It previously invoked `lake` with no `cwd`, so the verdict
938+
depended on where the caller stood — the same file by absolute path gave
939+
"3 verified, 0 failed" from the repo root and "0 verified, 3 failed" from
940+
/tmp, blaming each theorem with `theorem_lookup` when the theorems were fine
941+
and the workspace was simply never found. An input with no workspace above it
942+
now says so and exits non-zero. Gated in `check_proof_freshness.sh`.
943+
944+
What remains in this slice: define a versioned `ProofEvidenceReceipt`
938945
envelope and deterministic workspace/import/toolchain identities. This
939946
slice may establish replay and serialization plumbing, but it cannot upgrade
940947
a legacy body hash to a complete proof claim.

scripts/tests/check_proof_freshness.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,41 @@ grep -q "(stale)" <<<"$DEPS0" \
204204
&& no "the UNEDITED chain already reports a stale edge — the 062 witness proves nothing" \
205205
|| ok "the unedited chain has no stale edge"
206206

207+
echo
208+
echo "=== R-0004 slice 4: the replay verdict does not depend on the caller's cwd ==="
209+
# `lake` finds its workspace by walking up from where it is invoked, so kernel
210+
# replay with no `cwd` answered according to where the user happened to stand:
211+
# the same file by absolute path gave "3 verified, 0 failed" from the repo root
212+
# and "0 verified, 3 failed" from /tmp — and blamed each theorem with
213+
# `theorem_lookup`, sending the reader after the wrong thing entirely.
214+
CDIR="$ROOT_DIR/examples/proof_patterns/composition/src/main.con"
215+
ABS_COMPILER="$(cd "$(dirname "$COMPILER")" && pwd)/$(basename "$COMPILER")"
216+
from_root="$("$ABS_COMPILER" "$CDIR" --report check-proofs 2>&1 | grep -oE '[0-9]+ verified, [0-9]+ failed' | tail -1)"
217+
from_tmp="$(cd "$TMP" && "$ABS_COMPILER" "$CDIR" --report check-proofs 2>&1 | grep -oE '[0-9]+ verified, [0-9]+ failed' | tail -1)"
218+
if [ -n "$from_root" ] && [ "$from_root" = "$from_tmp" ]; then
219+
ok "same verdict from the repo root and from elsewhere ($from_root)"
220+
else
221+
no "the replay verdict moved with the working directory: root='$from_root' elsewhere='$from_tmp'"
222+
fi
223+
224+
# An input with no workspace above it must SAY SO and fail closed, not report a
225+
# pile of missing theorems that are not missing.
226+
NOWS="$TMP/nows"; mkdir -p "$NOWS"; cp "$CDIR" "$NOWS/main.con"
227+
nows_out="$(cd "$NOWS" && "$ABS_COMPILER" "$NOWS/main.con" --report check-proofs 2>&1)"
228+
nows_rc=0; (cd "$NOWS" && "$ABS_COMPILER" "$NOWS/main.con" --report check-proofs >/dev/null 2>&1) || nows_rc=$?
229+
if grep -q "cannot locate a Lake workspace" <<<"$nows_out"; then
230+
ok "a missing workspace is reported as a missing workspace"
231+
else
232+
no "a missing workspace is not named; got: $(printf '%s' "$nows_out" | tr '\n' ' ' | head -c 200)"
233+
fi
234+
if grep -qi "theorem_lookup" <<<"$nows_out"; then
235+
no "a missing workspace is still blamed on the theorems (theorem_lookup)"
236+
else
237+
ok "no theorem is blamed for a workspace that was never found"
238+
fi
239+
[ "$nows_rc" -ne 0 ] && ok "unreplayable input fails closed (rc=$nows_rc)" \
240+
|| no "unreplayable input exited 0 — replay must fail closed"
241+
207242
echo
208243
echo "PROOF-FRESHNESS: PASS=$PASS FAIL=$FAIL"
209244
[ "$FAIL" -eq 0 ]

0 commit comments

Comments
 (0)