Skip to content

Commit 055533b

Browse files
hyperpolymathclaude
andcommitted
fix(proofs): close proof holes — eliminate postulates, Obj.magic, and {!!} holes
Agda proof closure: - FileContentOperations: _≟ₚ_ postulate replaced with proven _path-≟_ from FilesystemModel; writeFileSameContent-proof postulate replaced with full constructive proof via funext + content-restore-lemma - RMOOperations: FALSE obliterate-not-reversible postulate replaced with proven obliterate-not-injective (correct formalization of information loss) - CopyMoveOperations: all 10 {!!} holes filled; file rewritten with correct names from FilesystemModel/FileOperations; all theorems proven including moveFile-reversible via funext + pointwise case analysis - FilesystemModel: funext postulate annotated as standard axiom (HoTT 2.9.3) ReScript type safety: - Server.res: all 24 Obj.magic calls replaced with type-safe toolResultToJson conversion function defined in Mcp.res bindings Remaining: 1 Coq gap (obliterate_overwrites_all_blocks), 1 standard axiom (funext) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 91ca9a3 commit 055533b

7 files changed

Lines changed: 577 additions & 289 deletions

File tree

docs/PROOF_HOLES_AUDIT.md

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,22 @@
88

99
| Category | Count | Change | Action Required |
1010
|----------|-------|--------|-----------------|
11-
| **Real Gaps** | 4 | -22 | Need proving |
11+
| **Real Gaps** | 1 | -25 | Coq `obliterate_overwrites_all_blocks` only |
1212
| **Axioms** | 4 | +1 | Intentional — well-known properties |
13-
| **Structural** | 2 | 0 | Standard type theory axioms |
14-
| **Total** | **10** | **-21** | |
13+
| **Structural** | 1 | -1 | `funext` only (`_≟ₚ_` proven via `_path-≟_`) |
14+
| **Total** | **6** | **-25** | |
15+
16+
### What Was Closed (2026-04-03 proof closure session)
17+
18+
**Agda proof holes eliminated:**
19+
- `agda/FileContentOperations.agda``_≟ₚ_` postulate REMOVED: now delegates to proven `_path-≟_` from FilesystemModel
20+
- `agda/FileContentOperations.agda``writeFileSameContent-proof` postulate PROVEN: via funext + content-restore-lemma (same pattern as writeFileReversible)
21+
- `agda/RMOOperations.agda` — FALSE `obliterate-not-reversible` postulate REPLACED with proven `obliterate-not-injective` (correct formalization of information loss)
22+
- `agda/CopyMoveOperations.agda` — ALL 10 `{!!}` holes FILLED: rewrote file using correct names from FilesystemModel/FileOperations, proved all theorems including moveFile-reversible via funext + pointwise case analysis
23+
- `agda/FilesystemModel.agda``funext` postulate ANNOTATED as standard axiom (provable in cubical Agda, consistent with intensional TT)
24+
25+
**ReScript Obj.magic elimination:**
26+
- `impl/mcp/src/Server.res` — ALL 24 `Obj.magic` calls REPLACED with type-safe `toolResultToJson` conversion function defined in Mcp.res bindings
1527

1628
### What Was Closed (2026-03-08 P5 session)
1729

@@ -50,12 +62,13 @@ The `obliterate_not_reversible` theorem was **FALSE as stated** in all 3 systems
5062

5163
Replaced with `obliterate_not_injective` — the correct formalization of "not reversible" as information loss: different starting states produce the same result after obliteration.
5264

53-
## Structural Axioms (2) — Standard Type Theory
65+
## Structural Axioms (1) — Standard Type Theory
5466

5567
| File | Line(s) | Name | Nature |
5668
|------|---------|------|--------|
57-
| `agda/FilesystemModel.agda` | 157-159 | `funext` | Functional extensionality (standard in intensional TT) |
58-
| `agda/FileContentOperations.agda` | 76-77 | `_≟ₚ_` | Path decidability instance (standard for decidable types) |
69+
| `agda/FilesystemModel.agda` | 161-163 | `funext` | Functional extensionality (standard in intensional TT, provable in cubical Agda) |
70+
71+
~~`agda/FileContentOperations.agda` `_≟ₚ_`~~ **RESOLVED 2026-04-03** — Now delegates to proven `_path-≟_` from FilesystemModel (structural recursion on List String with Data.String.__).
5972

6073
## Well-Formedness Axioms (2) — Added 2026-03-08
6174

@@ -66,7 +79,7 @@ Replaced with `obliterate_not_injective` — the correct formalization of "not r
6679

6780
These are provable by induction on path length but require significant infrastructure. Axiomatized with clear specifications.
6881

69-
## Remaining Real Gaps (3)
82+
## Remaining Real Gaps (1)
7083

7184
### RMO Storage Proofs (1 gap remaining — low priority)
7285

@@ -80,16 +93,16 @@ three auxiliary lemmas (`overwriteBlock_determined_by_shape`, `overwritePathBloc
8093
must have same blockId/length/overwriteCount) and `hlen` (patterns nonempty). After one deterministic
8194
overwrite pass, mapped blocks become byte-identical; remaining passes operate on equal inputs.
8295

96+
~~`agda/RMOOperations.agda` `obliterate-not-reversible`~~ **RESOLVED 2026-04-03** — FALSE statement
97+
replaced with proven `obliterate-not-injective` matching the Lean 4/Coq formulation.
98+
8399
The Coq gap (`obliterate_overwrites_all_blocks`) requires similar mechanical induction.
84100

85-
### Agda Deferred Proofs (2 gaps — medium priority)
101+
### Agda Deferred Proofs — ALL RESOLVED 2026-04-03
86102

87-
| File | Line | Theorem | Gap |
88-
|------|------|---------|-----|
89-
| `agda/FileContentOperations.agda` | 283 | `writeFileSameContent-proof` | Write-same-content identity |
90-
| `agda/CopyMoveOperations.agda` | 260 | `delete-after-update` | delete_file = fsUpdate nothing after fsUpdate just |
103+
~~`agda/FileContentOperations.agda` `writeFileSameContent-proof`~~ **PROVEN** — via funext + content-restore-lemma.
91104

92-
Both have full proof sketches and are proven in corresponding Lean 4/Coq files. The Agda proofs are deferred due to with-clause complexity.
105+
~~`agda/CopyMoveOperations.agda` holes~~ **PROVEN** — File rewritten with correct names from FilesystemModel/FileOperations. All theorems proven including moveFile-reversible via funext + pointwise case analysis on fsUpdate properties.
93106

94107
## Pre-existing Axioms (unchanged)
95108

@@ -100,10 +113,10 @@ Both have full proof sketches and are proven in corresponding Lean 4/Coq files.
100113

101114
## Recommendations
102115

103-
1. **3 remaining gaps are all low-medium priority**RMO is not user-facing, Agda proofs exist in Lean 4
104-
2. **Axiom count is healthy**4 new axioms are well-known filesystem/type theory properties
116+
1. **1 remaining gap is low priority**Coq `obliterate_overwrites_all_blocks` requires mechanical induction, not conceptually hard
117+
2. **Axiom count is minimal**1 structural (funext), 2 well-formedness, 6 decidability, 1 Coq funext = all standard
105118
3. **Model improvement needed**: Parameterize `mkdir`/`createFile` with permissions for full reverse-direction reversibility
106-
4. **Consider**: Agda `--cubical` flag provides funext natively, removing 1 structural axiom
119+
4. **Consider**: Agda `--cubical` flag provides funext natively, removing the last structural axiom
107120

108121
## P6: chmod/chown Proofs (2026-03-08)
109122

impl/mcp/src/Server.res

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ let mkdirHandler = async (params: JSON.t): promise<JSON.t> => {
6464
}
6565

6666
switch path {
67-
| None => Obj.magic(makeToolResult("Missing required parameter: path", ~isError=true))
67+
| None => toolResultToJson(makeToolResult("Missing required parameter: path", ~isError=true))
6868
| Some(p) =>
6969
let result = await executeOperation(Mkdir, p)
7070
switch result {
@@ -83,9 +83,9 @@ let mkdirHandler = async (params: JSON.t): promise<JSON.t> => {
8383
Dict.set(response, "proofTheorem", JSON.Encode.string(proof.theorem))
8484
Dict.set(response, "proofLocation", JSON.Encode.string(proof.coqLocation))
8585

86-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
86+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
8787
| Error(msg) =>
88-
Obj.magic(makeToolResult("mkdir failed: " ++ msg, ~isError=true))
88+
toolResultToJson(makeToolResult("mkdir failed: " ++ msg, ~isError=true))
8989
}
9090
}
9191
}
@@ -101,7 +101,7 @@ let rmdirHandler = async (params: JSON.t): promise<JSON.t> => {
101101
}
102102

103103
switch path {
104-
| None => Obj.magic(makeToolResult("Missing required parameter: path", ~isError=true))
104+
| None => toolResultToJson(makeToolResult("Missing required parameter: path", ~isError=true))
105105
| Some(p) =>
106106
let result = await executeOperation(Rmdir, p)
107107
switch result {
@@ -119,9 +119,9 @@ let rmdirHandler = async (params: JSON.t): promise<JSON.t> => {
119119
let proof = getProofRef(Rmdir)
120120
Dict.set(response, "proofTheorem", JSON.Encode.string(proof.theorem))
121121

122-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
122+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
123123
| Error(msg) =>
124-
Obj.magic(makeToolResult("rmdir failed: " ++ msg, ~isError=true))
124+
toolResultToJson(makeToolResult("rmdir failed: " ++ msg, ~isError=true))
125125
}
126126
}
127127
}
@@ -137,7 +137,7 @@ let touchHandler = async (params: JSON.t): promise<JSON.t> => {
137137
}
138138

139139
switch path {
140-
| None => Obj.magic(makeToolResult("Missing required parameter: path", ~isError=true))
140+
| None => toolResultToJson(makeToolResult("Missing required parameter: path", ~isError=true))
141141
| Some(p) =>
142142
let result = await executeOperation(CreateFile, p)
143143
switch result {
@@ -152,9 +152,9 @@ let touchHandler = async (params: JSON.t): promise<JSON.t> => {
152152
Dict.set(response, "operationId", JSON.Encode.string(op.id))
153153
Dict.set(response, "undoCommand", JSON.Encode.string("vsh_rm " ++ p))
154154

155-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
155+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
156156
| Error(msg) =>
157-
Obj.magic(makeToolResult("touch failed: " ++ msg, ~isError=true))
157+
toolResultToJson(makeToolResult("touch failed: " ++ msg, ~isError=true))
158158
}
159159
}
160160
}
@@ -170,7 +170,7 @@ let rmHandler = async (params: JSON.t): promise<JSON.t> => {
170170
}
171171

172172
switch path {
173-
| None => Obj.magic(makeToolResult("Missing required parameter: path", ~isError=true))
173+
| None => toolResultToJson(makeToolResult("Missing required parameter: path", ~isError=true))
174174
| Some(p) =>
175175
// Read content for undo before deleting
176176
let undoData = try {
@@ -192,9 +192,9 @@ let rmHandler = async (params: JSON.t): promise<JSON.t> => {
192192
Dict.set(response, "operationId", JSON.Encode.string(op.id))
193193
Dict.set(response, "canUndo", JSON.Encode.bool(true))
194194

195-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
195+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
196196
| Error(msg) =>
197-
Obj.magic(makeToolResult("rm failed: " ++ msg, ~isError=true))
197+
toolResultToJson(makeToolResult("rm failed: " ++ msg, ~isError=true))
198198
}
199199
}
200200
}
@@ -216,7 +216,7 @@ let undoHandler = async (params: JSON.t): promise<JSON.t> => {
216216
let ops = getUndoable(count)
217217

218218
if Array.length(ops) == 0 {
219-
Obj.magic(makeToolResult("Nothing to undo"))
219+
toolResultToJson(makeToolResult("Nothing to undo"))
220220
} else {
221221
let undone = []
222222

@@ -252,7 +252,7 @@ let undoHandler = async (params: JSON.t): promise<JSON.t> => {
252252
Dict.set(response, "operations", JSON.Encode.array(undone))
253253
Dict.set(response, "proofTheorem", JSON.Encode.string(compositionProof.theorem))
254254

255-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
255+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
256256
}
257257
}
258258

@@ -305,11 +305,11 @@ let historyHandler = async (params: JSON.t): promise<JSON.t> => {
305305
Dict.set(response, "count", JSON.Encode.int(Array.length(items)))
306306
Dict.set(response, "operations", JSON.Encode.array(items))
307307

308-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
308+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
309309
}
310310

311311
let statusHandler = async (_params: JSON.t): promise<JSON.t> => {
312-
Obj.magic(makeJsonResult(stateToJson()))
312+
toolResultToJson(makeJsonResult(stateToJson()))
313313
}
314314

315315
let proofsHandler = async (_params: JSON.t): promise<JSON.t> => {
@@ -335,7 +335,7 @@ let proofsHandler = async (_params: JSON.t): promise<JSON.t> => {
335335
JSON.Encode.string("FFI layer implements precondition checks derived from proofs but is not mechanically verified"),
336336
)
337337

338-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
338+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
339339
}
340340

341341
let beginHandler = async (params: JSON.t): promise<JSON.t> => {
@@ -349,7 +349,7 @@ let beginHandler = async (params: JSON.t): promise<JSON.t> => {
349349
}
350350

351351
switch name {
352-
| None => Obj.magic(makeToolResult("Missing required parameter: name", ~isError=true))
352+
| None => toolResultToJson(makeToolResult("Missing required parameter: name", ~isError=true))
353353
| Some(n) =>
354354
let txnId = beginTransaction(n)
355355

@@ -359,27 +359,27 @@ let beginHandler = async (params: JSON.t): promise<JSON.t> => {
359359
Dict.set(response, "name", JSON.Encode.string(n))
360360
Dict.set(response, "message", JSON.Encode.string("Transaction started. All operations will be grouped until commit or rollback."))
361361

362-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
362+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
363363
}
364364
}
365365

366366
let commitHandler = async (_params: JSON.t): promise<JSON.t> => {
367367
switch commitTransaction() {
368-
| None => Obj.magic(makeToolResult("No active transaction to commit", ~isError=true))
368+
| None => toolResultToJson(makeToolResult("No active transaction to commit", ~isError=true))
369369
| Some(txn) =>
370370
let response = Dict.make()
371371
Dict.set(response, "success", JSON.Encode.bool(true))
372372
Dict.set(response, "transactionId", JSON.Encode.string(txn.id))
373373
Dict.set(response, "name", JSON.Encode.string(txn.name))
374374
Dict.set(response, "operationCount", JSON.Encode.int(Array.length(txn.operations)))
375375

376-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
376+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
377377
}
378378
}
379379

380380
let rollbackHandler = async (_params: JSON.t): promise<JSON.t> => {
381381
switch rollbackTransaction() {
382-
| None => Obj.magic(makeToolResult("No active transaction to rollback", ~isError=true))
382+
| None => toolResultToJson(makeToolResult("No active transaction to rollback", ~isError=true))
383383
| Some(txn) =>
384384
// Undo all operations in the transaction
385385
let undoneCount = ref(0)
@@ -412,7 +412,7 @@ let rollbackHandler = async (_params: JSON.t): promise<JSON.t> => {
412412
Dict.set(response, "name", JSON.Encode.string(txn.name))
413413
Dict.set(response, "rolledBackOperations", JSON.Encode.int(undoneCount.contents))
414414

415-
Obj.magic(makeJsonResult(JSON.Encode.object(response)))
415+
toolResultToJson(makeJsonResult(JSON.Encode.object(response)))
416416
}
417417
}
418418

@@ -546,7 +546,7 @@ let handleHttpRequest = async (body: string): promise<JSON.t> => {
546546
| "vsh_begin" => await beginHandler(toolParams)
547547
| "vsh_commit" => await commitHandler(toolParams)
548548
| "vsh_rollback" => await rollbackHandler(toolParams)
549-
| _ => Obj.magic(makeToolResult("Unknown tool: " ++ toolName, ~isError=true))
549+
| _ => toolResultToJson(makeToolResult("Unknown tool: " ++ toolName, ~isError=true))
550550
}
551551
| "tools/list" =>
552552
// Return available tools

impl/mcp/src/bindings/Mcp.res

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22
// MCP SDK bindings for ReScript - Valence Shell
33

4+
// toolHandler type matches what the MCP SDK expects at the JS runtime boundary.
5+
// toolResult is structurally a JS object that the SDK handles as JSON.t.
6+
// We use a dedicated coercion (toolResultToJson) instead of scattered Obj.magic.
47
type toolHandler = JSON.t => promise<JSON.t>
58

9+
// Safe coercion: toolResult is a plain JS record that the MCP SDK treats as JSON.t
10+
// at the FFI boundary. This replaces all Obj.magic calls in Server.res.
11+
let toolResultToJson: toolResult => JSON.t = result => {
12+
let obj = Dict.make()
13+
let contentArr = Array.map(result.content, item => {
14+
let itemObj = Dict.make()
15+
Dict.set(itemObj, "type", JSON.Encode.string(item.type_))
16+
Dict.set(itemObj, "text", JSON.Encode.string(item.text))
17+
JSON.Encode.object(itemObj)
18+
})
19+
Dict.set(obj, "content", JSON.Encode.array(contentArr))
20+
switch result.isError {
21+
| Some(true) => Dict.set(obj, "isError", JSON.Encode.bool(true))
22+
| _ => ()
23+
}
24+
JSON.Encode.object(obj)
25+
}
26+
627
type toolSchema = {
728
@as("type") type_: string,
829
properties: dict<JSON.t>,

0 commit comments

Comments
 (0)