Skip to content

Commit a608cf4

Browse files
hyperpolymathclaude
andcommitted
fix(proofs): resolve Lean4 sorry in RMOOperations
Replace the sorry in obliterate_not_injective with a complete proof. The theorem was strengthened with two additional hypotheses: - hmapped: mapped blocks have same blockId, blockData.length, overwriteCount - hlen: patterns list is nonempty (required — 0 passes cannot erase data) Three auxiliary lemmas provide the proof infrastructure: - overwriteBlock_determined_by_shape: same shape → same overwrite result - overwritePathBlocks_storage_eq: one pass makes storage identical - multiPassOverwrite_congr: equal inputs → equal outputs Proof strategy: after one deterministic overwrite pass, mapped blocks become byte-identical (pattern output depends only on shape, not data). Remaining passes operate on equal StorageFS values, producing equal results. Proof holes audit updated: 4 → 3 remaining gaps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 375bc31 commit a608cf4

2 files changed

Lines changed: 176 additions & 61 deletions

File tree

docs/PROOF_HOLES_AUDIT.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,21 @@ Replaced with `obliterate_not_injective` — the correct formalization of "not r
6666

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

69-
## Remaining Real Gaps (4)
69+
## Remaining Real Gaps (3)
7070

71-
### RMO Storage Proofs (2 gaps — low priority)
71+
### RMO Storage Proofs (1 gap remaining — low priority)
7272

7373
| File | Line | Theorem | Gap |
7474
|------|------|---------|-----|
75-
| `lean4/RMOOperations.lean` | 197 | `obliterate_not_injective` | Storage equality under multiPassOverwrite |
7675
| `coq/rmo_operations.v` | 214 | `obliterate_overwrites_all_blocks` | Induction over overwrite passes |
7776

78-
Both require showing that `multiPassOverwrite` is determined by the mapping and patterns, not by original block data. Non-trivial but purely mechanical induction.
77+
~~`lean4/RMOOperations.lean` `obliterate_not_injective`~~ **RESOLVED 2026-03-22** — Proved via
78+
three auxiliary lemmas (`overwriteBlock_determined_by_shape`, `overwritePathBlocks_storage_eq`,
79+
`multiPassOverwrite_congr`). The theorem was strengthened with a `hmapped` hypothesis (mapped blocks
80+
must have same blockId/length/overwriteCount) and `hlen` (patterns nonempty). After one deterministic
81+
overwrite pass, mapped blocks become byte-identical; remaining passes operate on equal inputs.
82+
83+
The Coq gap (`obliterate_overwrites_all_blocks`) requires similar mechanical induction.
7984

8085
### Agda Deferred Proofs (2 gaps — medium priority)
8186

@@ -95,7 +100,7 @@ Both have full proof sketches and are proven in corresponding Lean 4/Coq files.
95100

96101
## Recommendations
97102

98-
1. **4 remaining gaps are all low-medium priority** — RMO is not user-facing, Agda proofs exist in Lean 4
103+
1. **3 remaining gaps are all low-medium priority** — RMO is not user-facing, Agda proofs exist in Lean 4
99104
2. **Axiom count is healthy** — 4 new axioms are well-known filesystem/type theory properties
100105
3. **Model improvement needed**: Parameterize `mkdir`/`createFile` with permissions for full reverse-direction reversibility
101106
4. **Consider**: Agda `--cubical` flag provides funext natively, removing 1 structural axiom

proofs/lean4/RMOOperations.lean

Lines changed: 166 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,64 @@ def noTraceRemains (p : Path) (sfs : StorageFS) : Prop :=
130130
-- Helper Lemmas
131131
-- ============================================================================
132132

133+
/-- Two blocks with equal id, data length, and overwrite count produce
134+
identical results after overwriteBlock, regardless of original data. -/
135+
theorem overwriteBlock_determined_by_shape (blk1 blk2 : StorageBlock)
136+
(pattern : OverwritePattern)
137+
(hid : blk1.blockId = blk2.blockId)
138+
(hlen : blk1.blockData.length = blk2.blockData.length)
139+
(hcount : blk1.overwriteCount = blk2.overwriteCount) :
140+
overwriteBlock blk1 pattern = overwriteBlock blk2 pattern := by
141+
simp [overwriteBlock, hid, hlen, hcount]
142+
143+
/-- overwritePathBlocks does not change the mapping -/
144+
theorem overwritePathBlocks_preserves_mapping (sfs : StorageFS) (p : Path)
145+
(pattern : OverwritePattern) :
146+
(overwritePathBlocks sfs p pattern).mapping = sfs.mapping := by
147+
simp [overwritePathBlocks]
148+
149+
/-- multiPassOverwrite does not change the mapping -/
150+
theorem multiPassOverwrite_preserves_mapping (sfs : StorageFS) (p : Path)
151+
(patterns : List OverwritePattern) :
152+
(multiPassOverwrite sfs p patterns).mapping = sfs.mapping := by
153+
induction patterns generalizing sfs with
154+
| nil => simp [multiPassOverwrite]
155+
| cons pat rest ih =>
156+
simp [multiPassOverwrite]
157+
rw [ih]
158+
exact overwritePathBlocks_preserves_mapping sfs p pat
159+
160+
/-- For a block NOT in the mapping for path p, overwritePathBlocks
161+
preserves it unchanged. -/
162+
theorem overwritePathBlocks_non_mapped (sfs : StorageFS) (p : Path)
163+
(pattern : OverwritePattern) (bid : Nat)
164+
(hnotmapped : bid ∉ sfs.mapping p) :
165+
(overwritePathBlocks sfs p pattern).storage bid = sfs.storage bid := by
166+
simp [overwritePathBlocks]
167+
match h : sfs.storage bid with
168+
| none => simp [h]
169+
| some blk =>
170+
simp [h]
171+
have : (sfs.mapping p).contains bid = false := by
172+
simp [List.contains] at hnotmapped ⊢
173+
exact hnotmapped
174+
simp [this]
175+
176+
/-- For a block IN the mapping for path p, overwritePathBlocks overwrites
177+
it with the pattern. The result depends only on blockId, blockData.length,
178+
overwriteCount, and the pattern — not on the original data. -/
179+
theorem overwritePathBlocks_mapped (sfs : StorageFS) (p : Path)
180+
(pattern : OverwritePattern) (bid : Nat) (blk : StorageBlock)
181+
(hmapped : bid ∈ sfs.mapping p)
182+
(hblk : sfs.storage bid = some blk) :
183+
(overwritePathBlocks sfs p pattern).storage bid =
184+
some (overwriteBlock blk pattern) := by
185+
simp [overwritePathBlocks, hblk]
186+
have : (sfs.mapping p).contains bid = true := by
187+
simp [List.contains] at hmapped ⊢
188+
exact hmapped
189+
simp [this]
190+
133191
/-- overwritePathBlocks preserves the filesystem tree -/
134192
theorem overwritePathBlocks_preserves_tree (sfs : StorageFS) (p : Path)
135193
(pattern : OverwritePattern) :
@@ -147,6 +205,62 @@ theorem multiPassOverwrite_preserves_tree (sfs : StorageFS) (p : Path)
147205
rw [ih]
148206
exact overwritePathBlocks_preserves_tree sfs p pat
149207

208+
-- ============================================================================
209+
-- Storage Equality Lemmas for Non-Injectivity Proof
210+
-- ============================================================================
211+
212+
/-- After one overwritePathBlocks pass, two filesystems with the same mapping,
213+
identical non-mapped blocks, and shape-compatible mapped blocks produce
214+
fully identical storage. This is the key lemma: after one pass of a
215+
deterministic pattern, the mapped blocks become byte-for-byte identical
216+
because overwriteBlock output depends only on shape (blockId, length,
217+
overwriteCount) and the pattern, not on original data. -/
218+
theorem overwritePathBlocks_storage_eq (sfs1 sfs2 : StorageFS) (p : Path)
219+
(pattern : OverwritePattern)
220+
(hpre1 : obliteratePrecondition p sfs1)
221+
(hpre2 : obliteratePrecondition p sfs2)
222+
(hmap : sfs1.mapping = sfs2.mapping)
223+
(hother : ∀ bid, bid ∉ sfs1.mapping p →
224+
sfs1.storage bid = sfs2.storage bid)
225+
(hmapped : ∀ bid, bid ∈ sfs1.mapping p →
226+
∀ blk1 blk2, sfs1.storage bid = some blk1 → sfs2.storage bid = some blk2 →
227+
blk1.blockId = blk2.blockId ∧
228+
blk1.blockData.length = blk2.blockData.length ∧
229+
blk1.overwriteCount = blk2.overwriteCount) :
230+
(overwritePathBlocks sfs1 p pattern).storage =
231+
(overwritePathBlocks sfs2 p pattern).storage := by
232+
funext bid
233+
by_cases hmem : bid ∈ sfs1.mapping p
234+
· -- Mapped block: both get overwriteBlock applied with same pattern.
235+
-- The results are identical because the blocks have the same shape.
236+
obtain ⟨blk1, hblk1⟩ := hpre1.2.2 bid hmem
237+
have hmem2 : bid ∈ sfs2.mapping p := by rw [← hmap]; exact hmem
238+
obtain ⟨blk2, hblk2⟩ := hpre2.2.2 bid hmem2
239+
rw [overwritePathBlocks_mapped sfs1 p pattern bid blk1 hmem hblk1]
240+
rw [overwritePathBlocks_mapped sfs2 p pattern bid blk2 hmem2 hblk2]
241+
obtain ⟨hid, hlen, hcount⟩ := hmapped bid hmem blk1 blk2 hblk1 hblk2
242+
exact congrArg some (overwriteBlock_determined_by_shape blk1 blk2 pattern hid hlen hcount)
243+
· -- Non-mapped block: unchanged by overwrite, already equal by hother
244+
have hmem2 : bid ∉ sfs2.mapping p := by rw [← hmap]; exact hmem
245+
rw [overwritePathBlocks_non_mapped sfs1 p pattern bid hmem]
246+
rw [overwritePathBlocks_non_mapped sfs2 p pattern bid hmem2]
247+
exact hother bid hmem
248+
249+
/-- When two StorageFS values have identical tree, storage, and mapping,
250+
multiPassOverwrite produces identical results. This is trivially true
251+
because equal inputs produce equal outputs. -/
252+
theorem multiPassOverwrite_congr (sfs1 sfs2 : StorageFS) (p : Path)
253+
(patterns : List OverwritePattern)
254+
(htree : sfs1.tree = sfs2.tree)
255+
(hstorage : sfs1.storage = sfs2.storage)
256+
(hmap : sfs1.mapping = sfs2.mapping) :
257+
multiPassOverwrite sfs1 p patterns = multiPassOverwrite sfs2 p patterns := by
258+
have : sfs1 = sfs2 := by
259+
cases sfs1; cases sfs2
260+
simp only [StorageFS.mk.injEq] at htree hstorage hmap ⊢
261+
exact ⟨htree, hstorage, hmap⟩
262+
rw [this]
263+
150264
-- ============================================================================
151265
-- Theorems
152266
-- ============================================================================
@@ -175,9 +289,15 @@ theorem obliterate_removes_mapping (p : Path) (sfs : StorageFS)
175289
/-- Theorem: RMO is not injective — different starting states produce the
176290
same result. This means no uniform recovery function exists.
177291
178-
Specifically: two filesystems that differ only in block data for path p
179-
produce identical results after obliteration, because the block data is
180-
overwritten with fixed patterns. -/
292+
Specifically: two filesystems that differ only in block data content for
293+
path p produce identical results after obliteration, because the block data
294+
is overwritten with deterministic patterns. Requires at least one overwrite
295+
pass (patterns nonempty) so the deterministic overwrite erases differences
296+
in block content.
297+
298+
The `hmapped` hypothesis formalizes "differ only in block data": mapped
299+
blocks must have the same blockId, blockData.length, and overwriteCount.
300+
Only the actual byte content may differ. -/
181301
theorem obliterate_not_injective (p : Path)
182302
(sfs1 sfs2 : StorageFS)
183303
(patterns : List OverwritePattern)
@@ -186,55 +306,45 @@ theorem obliterate_not_injective (p : Path)
186306
(htree : sfs1.tree = sfs2.tree)
187307
(hmap : sfs1.mapping = sfs2.mapping)
188308
(hother : ∀ bid, bid ∉ sfs1.mapping p →
189-
sfs1.storage bid = sfs2.storage bid) :
309+
sfs1.storage bid = sfs2.storage bid)
310+
(hmapped : ∀ bid, bid ∈ sfs1.mapping p →
311+
∀ blk1 blk2, sfs1.storage bid = some blk1 → sfs2.storage bid = some blk2 →
312+
blk1.blockId = blk2.blockId ∧
313+
blk1.blockData.length = blk2.blockData.length ∧
314+
blk1.overwriteCount = blk2.overwriteCount)
315+
(hlen : patterns.length > 0) :
190316
obliterate p sfs1 patterns = obliterate p sfs2 patterns := by
191-
simp [obliterate, removeBlockMapping]
192-
constructor
193-
· -- tree: deleteFile on same tree (after multiPassOverwrite preserves tree)
194-
rw [multiPassOverwrite_preserves_tree, multiPassOverwrite_preserves_tree, htree]
195-
constructor
196-
· -- storage: both overwrite the same blocks with same patterns, other blocks same
197-
-- PROOF OBLIGATION: multiPassOverwrite produces identical storage given:
198-
-- (a) same block mapping (hmap), (b) same overwrite patterns, and
199-
-- (c) non-mapped blocks are identical (hother).
200-
--
201-
-- WHY DEFERRED: Requires an auxiliary lemma:
202-
-- multiPassOverwrite_storage_determined :
203-
-- ∀ (sfs1 sfs2 : StorageFS) (p : Path) (patterns : List OverwritePattern),
204-
-- sfs1.mapping = sfs2.mapping →
205-
-- (∀ bid, bid ∉ sfs1.mapping p → sfs1.storage bid = sfs2.storage bid) →
206-
-- (multiPassOverwrite sfs1 p patterns).storage =
207-
-- (multiPassOverwrite sfs2 p patterns).storage
208-
--
209-
-- PROOF SKETCH:
210-
-- Induction on patterns.
211-
-- Base case: trivial (multiPassOverwrite [] = identity).
212-
-- Inductive case: After one overwritePathBlocks pass, blocks in mapping p
213-
-- are overwritten with the same pattern data (determined solely by pattern
214-
-- and block size). Blocks NOT in mapping p are unchanged. Since block sizes
215-
-- in the mapping are determined by storage (and non-mapped blocks are same
216-
-- by hother), the result after one pass has the same property: non-mapped
217-
-- blocks still equal, mapped blocks now identical. Apply IH.
218-
--
219-
-- The key sub-lemma is that overwriteBlock depends only on blockData.length
220-
-- and the pattern, not on the original data. Two blocks with the same length
221-
-- produce identical results after overwriteBlock.
222-
--
223-
-- ADDITIONAL LEMMAS NEEDED:
224-
-- 1. overwriteBlock_length_determined: block sizes equal → overwriteBlock equal
225-
-- 2. overwritePathBlocks_non_mapped_preserved: non-mapped blocks unchanged
226-
-- 3. overwritePathBlocks_mapped_determined: mapped blocks depend only on size+pattern
227-
--
228-
-- BLOCKED: Proving functional extensionality on Storage (Nat → Option StorageBlock)
229-
-- after multi-pass overwrite requires the 3 auxiliary lemmas above, plus induction
230-
-- on the pattern list. The proof is structurally sound (sketch verified) but
231-
-- formalizing it requires ~50 lines of auxiliary lemmas about List.map, List.range,
232-
-- and overwriteBlock determinism. This is a genuine proof obligation, not a gap
233-
-- in the theorem statement.
234-
sorry -- BLOCKED: requires 3 auxiliary lemmas (see comments above)
235-
· -- mapping: same since hmap
236-
funext p'
237-
simp [hmap]
317+
-- Decompose patterns into pat :: rest (guaranteed nonempty by hlen)
318+
match patterns, hlen with
319+
| pat :: rest, _ =>
320+
simp [obliterate, removeBlockMapping]
321+
constructor
322+
· -- tree: deleteFile on same tree (multiPassOverwrite preserves tree)
323+
rw [multiPassOverwrite_preserves_tree, multiPassOverwrite_preserves_tree, htree]
324+
constructor
325+
· -- storage: After one pass of overwritePathBlocks, both filesystems have
326+
-- identical storage (by overwritePathBlocks_storage_eq). The remaining
327+
-- passes (multiPassOverwrite on rest) then operate on identical inputs
328+
-- and produce identical outputs.
329+
simp [multiPassOverwrite]
330+
-- Step 1: After one pass, storage is identical
331+
have hstorage_eq := overwritePathBlocks_storage_eq sfs1 sfs2 p pat
332+
hpre1 hpre2 hmap hother hmapped
333+
-- Step 2: Tree and mapping are also preserved identically
334+
have htree_eq : (overwritePathBlocks sfs1 p pat).tree =
335+
(overwritePathBlocks sfs2 p pat).tree := by
336+
rw [overwritePathBlocks_preserves_tree, overwritePathBlocks_preserves_tree, htree]
337+
have hmap_eq : (overwritePathBlocks sfs1 p pat).mapping =
338+
(overwritePathBlocks sfs2 p pat).mapping := by
339+
rw [overwritePathBlocks_preserves_mapping, overwritePathBlocks_preserves_mapping, hmap]
340+
-- Step 3: Equal inputs → equal outputs for remaining passes
341+
have h := multiPassOverwrite_congr
342+
(overwritePathBlocks sfs1 p pat) (overwritePathBlocks sfs2 p pat)
343+
p rest htree_eq hstorage_eq hmap_eq
344+
exact congrArg StorageFS.storage h
345+
· -- mapping: same since hmap
346+
funext p'
347+
simp [hmap]
238348

239349
/-- Theorem: Obliteration preserves unrelated paths -/
240350
theorem obliterate_preserves_other_paths (p p' : Path) (sfs : StorageFS)
@@ -260,11 +370,11 @@ theorem obliterate_leaves_no_trace (p : Path) (sfs : StorageFS)
260370
/-
261371
Summary of Proven Claims:
262372
263-
obliterate_removes_path - RMO removes path from filesystem
264-
obliterate_removes_mapping - RMO removes block mappings
265-
✓ obliterate_not_reversible - RMO is NOT reversible (key distinction from RMR)
266-
obliterate_preserves_other_paths - RMO preserves unrelated paths
267-
obliterate_leaves_no_trace - GDPR Article 17 compliance
373+
obliterate_removes_path - RMO removes path from filesystem
374+
obliterate_removes_mapping - RMO removes block mappings
375+
obliterate_not_injective - RMO is NOT injective (different data, same result)
376+
obliterate_preserves_other_paths - RMO preserves unrelated paths
377+
obliterate_leaves_no_trace - GDPR Article 17 compliance
268378
-/
269379

270380
end ValenceShell.RMO

0 commit comments

Comments
 (0)