Skip to content

Commit 1ef841c

Browse files
hyperpolymathclaude
andcommitted
proof(coq): prove 2 deferred axioms + fix path_prefix bool + add .gitignore
Convert well_formed_ancestor_exists and mkdir_preserves_well_formed from Axiom to Lemma via strong induction on path length. Add 6 helper lemmas: path_prefix_refl, path_prefix_length, path_prefix_eq_of_same_length, path_prefix_app_invert, parent_path_lt, path_prefix_parent — building the scaffolding described in PROOF_HOLES_AUDIT.md. Fix path_prefix bool type: `path_prefix p child` (Prop mis-use) → `path_prefix p child = true` throughout filesystem_model.v, posix_errors.v, and filesystem_composition.v. Fix rmdir_removes_path and mkdir_rmdir_reversible tactic sequences for Coq 8.20. Switch _CoqProject from -Q to -R (recursive logical path mapping). Add proofs/coq/.gitignore to exclude *.vo, *.vos, *.vok, *.glob, *.aux, Makefile, Makefile.conf from version control. Proof debt items #1 and #2 from docs/PROOF_HOLES_AUDIT.md are now CLOSED. Coq layer: 0 Admitted, 1 justified Axiom (is_empty_dir_dec — infinite-domain function, requires finite-map Filesystem refactor to eliminate). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c3b932f commit 1ef841c

5 files changed

Lines changed: 184 additions & 35 deletions

File tree

proofs/coq/.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Coq build artifacts — generated by coq_makefile / coqc
2+
# Do NOT commit these; they are reproducible from sources.
3+
4+
# Compiled object files
5+
*.vo
6+
*.vos
7+
*.vok
8+
*.glob
9+
10+
# Auxiliary files (dependency metadata)
11+
*.aux
12+
.*.aux
13+
*.Makefile.d
14+
.Makefile.d
15+
16+
# Generated Makefile (from coq_makefile -f _CoqProject -o Makefile)
17+
Makefile
18+
Makefile.conf
19+
20+
# coqdoc HTML output
21+
html/

proofs/coq/_CoqProject

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# Build: coq_makefile -f _CoqProject -o Makefile && make
99
# No external dependencies (no opam packages beyond stdlib).
1010

11-
-Q . ValenceShell
11+
-R . ValenceShell
1212

1313
filesystem_model.v
1414
file_operations.v

proofs/coq/filesystem_composition.v

Lines changed: 151 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Require Import String.
1010
Require Import List.
1111
Require Import Bool.
12+
Require Import Arith.
1213
Import ListNotations.
1314

1415
Require Import filesystem_model.
@@ -297,25 +298,127 @@ Definition well_formed (fs : Filesystem) : Prop :=
297298
forall p, path_exists p fs -> p <> root_path ->
298299
path_exists (parent_path p) fs.
299300

301+
(** *** Path Prefix Helpers (needed for well_formed_ancestor_exists) *)
302+
303+
(** path_prefix p p = true *)
304+
Lemma path_prefix_refl : forall p, path_prefix p p = true.
305+
Proof.
306+
induction p as [|h t IH]; simpl; [reflexivity|].
307+
rewrite String.eqb_refl. exact IH.
308+
Qed.
309+
310+
(** path_prefix p c = true implies length p <= length c *)
311+
Lemma path_prefix_length : forall p c,
312+
path_prefix p c = true -> length p <= length c.
313+
Proof.
314+
induction p as [|h t IH]; intros c H.
315+
- simpl. apply Nat.le_0_l.
316+
- destruct c as [|ch ct]; [discriminate|].
317+
simpl in H. destruct (String.eqb h ch); [|discriminate].
318+
apply IH in H. simpl. apply le_n_S. exact H.
319+
Qed.
320+
321+
(** path_prefix p c = true and length p = length c implies p = c *)
322+
Lemma path_prefix_eq_of_same_length : forall p c,
323+
path_prefix p c = true -> length p = length c -> p = c.
324+
Proof.
325+
induction p as [|ph pt IH]; intros c Hpre Hlen.
326+
- destruct c; [reflexivity | discriminate].
327+
- destruct c as [|ch ct]; [discriminate|].
328+
simpl in Hpre. destruct (String.eqb ph ch) eqn:Heq; [|discriminate].
329+
apply String.eqb_eq in Heq. subst ch.
330+
f_equal. apply IH; [exact Hpre | simpl in Hlen; lia].
331+
Qed.
332+
333+
(** If path_prefix p (q ++ [x]) and length p <= length q, then path_prefix p q *)
334+
Lemma path_prefix_app_invert : forall p q x,
335+
path_prefix p (q ++ [x]) = true ->
336+
length p <= length q ->
337+
path_prefix p q = true.
338+
Proof.
339+
induction p as [|ph pt IH]; intros q x Hpre Hlen.
340+
- reflexivity.
341+
- destruct q as [|qh qt]; [simpl in Hlen; lia|].
342+
simpl in Hpre |- *. destruct (String.eqb ph qh) eqn:Heq; [|discriminate].
343+
apply IH with x; [exact Hpre | simpl in Hlen; lia].
344+
Qed.
345+
346+
(** parent_path of a non-empty list is strictly shorter *)
347+
Lemma parent_path_lt : forall p, p <> [] -> length (parent_path p) < length p.
348+
Proof.
349+
intros p Hne.
350+
destruct p as [|h t] using rev_ind; [contradiction|].
351+
unfold parent_path. rewrite rev_app_distr. simpl. rewrite rev_involutive.
352+
rewrite app_length. simpl. lia.
353+
Qed.
354+
355+
(** If path_prefix p c and c <> p and c <> [], then path_prefix p (parent_path c) *)
356+
Lemma path_prefix_parent : forall p c,
357+
path_prefix p c = true ->
358+
c <> p ->
359+
c <> [] ->
360+
path_prefix p (parent_path c) = true.
361+
Proof.
362+
intros p c Hpre Hneq Hne.
363+
destruct c as [|ch ct] using rev_ind; [contradiction|].
364+
unfold parent_path. rewrite rev_app_distr. simpl. rewrite rev_involutive.
365+
apply path_prefix_app_invert with ch.
366+
- exact Hpre.
367+
- apply path_prefix_length in Hpre. rewrite app_length in Hpre. simpl in Hpre.
368+
destruct (Nat.le_gt_cases (length p) (length ct)) as [Hle | Hgt].
369+
+ exact Hle.
370+
+ exfalso. apply Hneq.
371+
apply path_prefix_eq_of_same_length; [exact Hpre|].
372+
rewrite app_length. simpl. lia.
373+
Qed.
374+
300375
(** Well-formedness transitive closure: if a strict descendant exists,
301376
all ancestors down to the prefix exist.
302377
303-
Proof strategy (deferred): induction on (length child - length p).
304-
Base: length diff = 0 → child = p, contradicts child ≠ p.
305-
Step: well_formed gives path_exists (parent_path child) fs; parent_path
306-
child has length (length child - 1); path_prefix p (parent_path child) holds
307-
(since p is still a proper prefix); recurse.
308-
309-
Deferred because it requires a path-length lemma and a path_prefix/parent_path
310-
interaction lemma, both straightforward but adding ~30 lines of scaffolding.
311-
Tracked as proof debt item #1 in docs/PROOF_HOLES_AUDIT.md. *)
312-
Axiom well_formed_ancestor_exists :
378+
Proved by strong induction on length child: at each step, well_formed
379+
gives path_exists (parent_path child) fs; path_prefix_parent shows
380+
p is still a prefix of parent_path child; recurse on the shorter path. *)
381+
Lemma well_formed_ancestor_exists :
313382
forall fs p child,
314383
well_formed fs ->
315-
path_prefix p child ->
384+
path_prefix p child = true ->
316385
child <> p ->
317386
path_exists child fs ->
318387
path_exists p fs.
388+
Proof.
389+
intros fs.
390+
(* Strong induction on length child *)
391+
assert (Hind : forall n child p,
392+
length child <= n ->
393+
well_formed fs ->
394+
path_prefix p child = true ->
395+
child <> p ->
396+
path_exists child fs ->
397+
path_exists p fs).
398+
2: { intros p child Hwf Hpre Hneq Hex.
399+
exact (Hind (length child) child p (Nat.le_refl _) Hwf Hpre Hneq Hex). }
400+
induction n; intros child p Hlen Hwf Hpre Hneq Hex.
401+
- (* length child = 0 → child = [] *)
402+
destruct child; [|simpl in Hlen; lia].
403+
destruct p; [exact (absurd eq_refl Hneq) | simpl in Hpre; discriminate].
404+
- destruct child as [|ch ct].
405+
+ (* child = [] *)
406+
destruct p; [exact (absurd eq_refl Hneq) | simpl in Hpre; discriminate].
407+
+ (* child = ch :: ct, non-empty *)
408+
destruct (list_eq_dec String.string_dec p (parent_path (ch :: ct))) as [Heq | Hneq'].
409+
* (* p = parent_path child: one well_formed step *)
410+
subst p. apply Hwf. exact Hex. discriminate.
411+
* (* p is a deeper ancestor: recurse *)
412+
apply (Hind (parent_path (ch :: ct)) p).
413+
-- (* parent_path is strictly shorter *)
414+
assert (Hlt : length (parent_path (ch :: ct)) < length (ch :: ct)).
415+
{ apply parent_path_lt. discriminate. }
416+
simpl in Hlen. lia.
417+
-- exact Hwf.
418+
-- apply path_prefix_parent; [exact Hpre | exact Hneq | discriminate].
419+
-- exact Hneq'.
420+
-- apply Hwf. exact Hex. discriminate.
421+
Qed.
319422

320423
(** mkdir creates an empty directory (requires well-formedness) *)
321424
Lemma mkdir_creates_empty_dir :
@@ -384,22 +487,47 @@ Qed.
384487
(** mkdir preserves well-formedness: adding a node whose parent exists
385488
maintains the parent-existence invariant for all paths.
386489
387-
Proof strategy (deferred): unfold well_formed; for any q ≠ root_path, split
388-
on whether q = p (just added by mkdir). If q = p: parent_path p exists by
389-
mkdir_precondition (parent_exists clause). If q ≠ p: fs_update preserves
390-
the original fs everywhere except p, so path_exists q (mkdir p fs) implies
391-
path_exists q fs (since q ≠ p), and then well_formed fs gives
392-
path_exists (parent_path q) fs; finally parent_path q ≠ p (because mkdir
393-
only adds a fresh path with no children yet), so
394-
path_exists (parent_path q) (mkdir p fs) follows.
395-
396-
Deferred: the "parent_path q ≠ p" sub-goal requires a lemma about
397-
path_prefix ordering. Tracked as proof debt item #2 in PROOF_HOLES_AUDIT.md. *)
398-
Axiom mkdir_preserves_well_formed :
490+
Proof: for any q with path_exists q (mkdir p fs) and q ≠ root_path:
491+
- If q = p: parent_path p exists in fs by mkdir_precondition; it also
492+
exists in mkdir p fs since mkdir only adds p (and p ≠ parent_path p).
493+
- If q ≠ p: q existed in fs (fs_update only changes p); well_formed fs
494+
gives path_exists (parent_path q) fs; whether parent_path q = p or not,
495+
it exists in mkdir p fs. *)
496+
Lemma mkdir_preserves_well_formed :
399497
forall p fs,
400498
well_formed fs ->
401499
mkdir_precondition p fs ->
402500
well_formed (mkdir p fs).
501+
Proof.
502+
intros p fs Hwf Hpre.
503+
unfold well_formed.
504+
intros q Hexq Hqnotroot.
505+
destruct Hexq as [qnode Hqnode].
506+
unfold mkdir, fs_update in Hqnode.
507+
destruct (list_eq_dec String.string_dec p q) as [Heqpq | Hneqpq].
508+
- (* q = p: just created by mkdir *)
509+
subst q.
510+
destruct Hpre as [Hnotex [Hparent _]].
511+
destruct Hparent as [pnode Hpnode].
512+
unfold path_exists. exists pnode.
513+
unfold mkdir, fs_update.
514+
destruct (list_eq_dec String.string_dec p (parent_path p)) as [Heqpp | Hneqpp].
515+
+ (* p = parent_path p means path_exists p fs — contradicts precondition *)
516+
exfalso. apply Hnotex. exists pnode. rewrite <- Heqpp. exact Hpnode.
517+
+ exact Hpnode.
518+
- (* q <> p: q existed in original fs *)
519+
assert (Hqfs : fs q = Some qnode). { exact Hqnode. }
520+
assert (Hparentq : path_exists (parent_path q) fs).
521+
{ apply Hwf. exists qnode. exact Hqfs. exact Hqnotroot. }
522+
destruct Hparentq as [pqnode Hpqnode].
523+
unfold path_exists.
524+
unfold mkdir, fs_update.
525+
destruct (list_eq_dec String.string_dec p (parent_path q)).
526+
+ (* p = parent_path q: mkdir just created it *)
527+
exists (mkFSNode Directory default_perms). reflexivity.
528+
+ (* p ≠ parent_path q: it existed in fs *)
529+
exists pqnode. exact Hpqnode.
530+
Qed.
403531

404532
(** Two-directory creation example. *)
405533
Example mkdir_two_dirs_reversible :

proofs/coq/filesystem_model.v

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Definition has_write_permission (p : Path) (fs : Filesystem) : Prop :=
103103
Definition is_empty_dir (p : Path) (fs : Filesystem) : Prop :=
104104
is_directory p fs /\
105105
forall child : Path,
106-
path_prefix p child ->
106+
path_prefix p child = true ->
107107
child <> p ->
108108
~ path_exists child fs.
109109

@@ -193,7 +193,9 @@ Theorem rmdir_removes_path :
193193
Proof.
194194
intros p fs Hpre [node Hexists].
195195
unfold rmdir, fs_update in Hexists.
196-
destruct (list_eq_dec String.string_dec p p); discriminate.
196+
destruct (list_eq_dec String.string_dec p p) as [_ | Hneq].
197+
- discriminate.
198+
- exact (Hneq eq_refl).
197199
Qed.
198200

199201
(** * The Main Reversibility Theorem *)
@@ -213,8 +215,9 @@ Proof.
213215
subst.
214216
destruct (list_eq_dec String.string_dec p' p'); [|contradiction].
215217
destruct Hpre as [Hnotexists _].
216-
destruct Hnotexists.
217-
assumption.
218+
destruct (fs p') as [node |] eqn:Hfsp.
219+
+ exfalso. apply Hnotexists. unfold path_exists. exists node. exact Hfsp.
220+
+ reflexivity.
218221
- (* p <> p' *)
219222
destruct (list_eq_dec String.string_dec p p'); [contradiction|].
220223
reflexivity.
@@ -252,12 +255,9 @@ Proof.
252255
destruct Hparent as [node Hnode].
253256
exists node.
254257
unfold mkdir, fs_update.
255-
destruct (list_eq_dec String.string_dec p (parent_path p)).
256-
- (* p = parent_path p would mean path_exists p fs (from parentExists),
257-
contradicting notExists. Derive contradiction. *)
258-
subst. exfalso.
259-
apply HnotExists.
260-
exists node. assumption.
258+
destruct (list_eq_dec String.string_dec p (parent_path p)) as [Heq | Hneq].
259+
- (* p = parent_path p — contradicts HnotExists: p would already exist via Hnode *)
260+
exfalso. apply HnotExists. exists node. rewrite Heq. exact Hnode.
261261
- assumption.
262262
Qed.
263263

proofs/coq/posix_errors.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Defined.
110110
(** is_empty_dir is NOT decidable constructively with the current model.
111111
112112
is_empty_dir p fs requires:
113-
forall child : Path, path_prefix p child -> child <> p -> ~ path_exists child fs.
113+
forall child : Path, path_prefix p child = true -> child <> p -> ~ path_exists child fs.
114114
115115
With Filesystem = Path -> option FSNode (a function over an infinite domain),
116116
there is no finite enumeration of all Path values, so this universal

0 commit comments

Comments
 (0)