Skip to content

Commit dbff7e0

Browse files
fix: R-0442 — a locally bound callable is not a global name (bug 061)
`PExpr.call "f" args` meant BOTH "call the definition `f`" and "apply the fn-typed parameter `f`", and `eval` resolved both through the global `FnTable`. In the one place this project makes soundness claims, a parameter and a definition of the same spelling were the same thing. Bug 061 was filed as latent — "no reachable witness in std today". That was wrong, and the correction is the substance of this change: the witness was already in the repo. `pureCoreFns` bound the representative callback `f` as a GLOBAL, because the HOF specs applied their parameter as `.call "f"`. So option_map_correct / result_map_correct / result_map_err_correct were discharged by resolving a parameter application against a definition. The conflation was load-bearing in three shipped theorems. They remain true, and their scope was always recorded as proof_coverage(representative); the mechanism by which Lean accepted them was the defect. Two identities, two namespaces: PExpr.call fn args -> FnTable.globals only PExpr.applyVar b args -> FnTable.callables only `FnTable` became a structure carrying both, and kept its NAME so ~96 `(fns : FnTable)` annotations and ~166 `eval fns …` call sites keep their meaning; only the three places that APPLY a table had to choose a namespace, which is where the choice belongs. No `CoeFun FnTable` — an implicit application would resolve to `globals` and silently reinstate the conflation, the same reasoning that retired `Coe String SCallee` in R-0436. Carried through every consumer the task lists: extraction (both sites; indirect callees map to `.applyVar` rather than being refused — refusing cost three real proofs), evaluation, fingerprints (call vs callptr, now gated not inspected), preservation statements (eval_apply_var_reduces, and apply_var_ignores_globals proving an applied local is STUCK under any global table), reports (`&b(...)`, `.applyVar`, and both scaffold generators emitting a documented two-namespace table), proof dependencies (an applied parameter takes no edge), and per-form completeness: `pexprApplies`/`callableTableComplete` beside `pexprCalls`/`fnTableComplete`. Two collectors rather than one tagged list, because a single list is what let one predicate check a parameter against the global namespace. Kernel-checked `example`s assert the three HOF specs' callable namespace is complete AND their global namespace is empty — the latter is what makes a regression visible. Gate check_proofcore_callable_identity.sh (29 checks), including the source-level witness said not to exist: one program where `f` is both a definition and a parameter name, extracting to `.call "f"` and `.applyVar "f"`. Mutations #31-#33. The two that rebind resolution are killed by the Lean KERNEL, not the gate — the three map theorems reduce to `⊢ False` when the callback sits in the wrong namespace. The proofs are themselves evidence for the separation, which is a stronger outcome than the gate alone; the mutation comment records that a build-kill is weak only when a LINTER rejects the file. Frontier moves to R-0004, which required this: its subject digest and receipts must name an unambiguous application model.
1 parent 5629d65 commit dbff7e0

22 files changed

Lines changed: 809 additions & 131 deletions

File tree

.githooks/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ areas=""
8888
add() { case " $areas " in *" $1 "*) ;; *) areas="$areas $1" ;; esac; }
8989
while IFS= read -r f; do
9090
case "$f" in
91-
Concrete/Proof/*|Concrete/Report/*|proofs/*) add proof; add evidence; add contract ;;
91+
Concrete/Proof/*|Concrete/Report/*|proofs/*) add proof; add evidence; add contract; add callable ;;
9292
Concrete/IR/*|Concrete/Backend/*) add mono; add codegen; add ssa; add trap; add fnptr ;;
9393
# Semantics and the interpreter are where trap/overflow facts live, and the
9494
# interpreter is half of every differential. Without this line they matched

.github/workflows/lean_action_ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,9 @@ jobs:
574574
- name: "Pure-core proofs (slice 1 — kernel-verified stdlib proof link, mutation-sensitive)"
575575
run: bash scripts/tests/check_purecore_proofs.sh
576576

577+
- name: "ProofCore callable identity (R-0442 / bug 061 — a locally bound callable is not a global name; two namespaces)"
578+
run: bash scripts/tests/check_proofcore_callable_identity.sh
579+
577580
- name: "Workload 3: hexdump differential vs xxd + exit codes"
578581
run: bash scripts/tests/check_hexdump_differential.sh
579582

CHANGELOG.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,59 @@ stored/derived callback inference in R-0016. The unmerged H18 worktree and
104104
`.audit_me/` probes now have explicit salvage/promote-or-delete owners instead
105105
of being treated as ambient backlog.
106106

107+
### Completed Task R-0442
108+
109+
_Bug 061 — the proof model spelled a parameter application and a definition call
110+
the same way. 2026-07-28._
111+
112+
`PExpr.call "f" args` meant both "call the definition `f`" and "apply the
113+
fn-typed parameter `f`", and `eval` resolved both through the global `FnTable`.
114+
In the one place this project makes soundness claims, a parameter and a
115+
definition of the same spelling were the same thing.
116+
117+
The bug was filed as latent, with "no reachable witness in std today". That was
118+
wrong, and the correction is the interesting part: the witness was already in the
119+
repository. `pureCoreFns` bound the representative callback `f` as a **global**,
120+
because the HOF specs applied their parameter as `.call "f"` — so
121+
`option_map_correct`, `result_map_correct` and `result_map_err_correct` were
122+
discharged by resolving a parameter application against a definition. The
123+
conflation was load-bearing in three shipped theorems. They remain true, and
124+
their scope was always recorded as `proof_coverage(representative)`; the
125+
mechanism by which Lean accepted them was the defect.
126+
127+
Two identities, two namespaces: `PExpr.call fn` is answered by `FnTable.globals`
128+
only, `PExpr.applyVar binding` by `FnTable.callables` only. `FnTable` became a
129+
structure carrying both, and stayed *named* `FnTable` so ~96 annotations and ~166
130+
`eval fns …` call sites kept their meaning — only the three places that APPLY a
131+
table had to choose a namespace, which is where the choice belongs. No
132+
`CoeFun FnTable`: an implicit application would resolve to `globals`, silently
133+
reinstating the conflation. (The same reasoning retired `Coe String SCallee` in
134+
R-0436.)
135+
136+
Carried through the whole list the task named: extraction (both sites, mapping
137+
indirect callees to `.applyVar` rather than refusing them — refusing cost three
138+
real proofs), evaluation, fingerprints (`call` vs `callptr`, now gated rather
139+
than inspected), preservation statements (`eval_apply_var_reduces`, plus
140+
`apply_var_ignores_globals` proving an applied local is stuck under any global
141+
table), reports (`&binding(...)`, `.applyVar`, and both scaffold generators
142+
emitting a documented two-namespace table), proof dependencies (an applied
143+
parameter contributes no edge), and per-form table completeness
144+
(`pexprApplies`/`callableTableComplete` beside `pexprCalls`/`fnTableComplete`
145+
two collectors, because one tagged list is what let a single predicate check a
146+
parameter against the global namespace). Kernel-checked `example`s assert the
147+
three HOF specs' callable namespace is complete AND that their global namespace
148+
is empty; the latter is what makes a regression visible.
149+
150+
Evidence: `scripts/tests/check_proofcore_callable_identity.sh` (29 checks),
151+
including the source-level witness that was said not to exist — one program
152+
where `f` is both a definition and a parameter name, extracting to `.call "f"`
153+
and `.applyVar "f"` respectively.
154+
155+
Mutations #31-#33. The two that rebind resolution are killed by the Lean
156+
**kernel**, not the gate: the three map theorems reduce to `⊢ False` when the
157+
callback sits in the wrong namespace. The proofs are themselves evidence for the
158+
separation, which is a better outcome than the gate alone.
159+
107160
### Completed Task R-0436
108161

109162
_Bug 056 — a function reference was a register name, and a call target was a

0 commit comments

Comments
 (0)