Skip to content

Commit 794c568

Browse files
hyperpolymathclaude
andcommitted
proof(coq): replace 5 decidability axioms with constructive proofs
posix_errors.v had two structural bugs: 1. Decision procedures declared as Axiom AFTER safe_* definitions that used them (forward-reference error in Coq's sequential processing). 2. safe_* definitions used `negb (sumbool_val)` which is a type error — negb : bool -> bool, but the sumbool {P} + {~P} is not a bool. Fix: - Moved decision procedures to BEFORE safe_* definitions. - Proved path_exists_dec, is_directory_dec, is_file_dec, has_write_permission_dec, path_eq_dec constructively by case-splitting on the Filesystem function (Path -> option FSNode) and the FSNode fields. - Kept is_empty_dir_dec as Axiom with full justification: universal quantification over an infinite-domain function cannot be decided constructively; finite-map refactor removes this axiom. - Restructured safe_* definitions to use nested `if` on sumbool (correct) instead of `negb (sumbool_val)` (type error). All existing theorems and proofs preserved with minimal changes. filesystem_composition.v: added detailed proof-strategy comments to well_formed_ancestor_exists and mkdir_preserves_well_formed axioms, referencing PROOF_HOLES_AUDIT.md items #1 and #2. functional_extensionality: already correctly imported in filesystem_model.v; all other files get it transitively via Require Import filesystem_model. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e3d1315 commit 794c568

2 files changed

Lines changed: 154 additions & 56 deletions

File tree

proofs/coq/filesystem_composition.v

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,17 @@ Definition well_formed (fs : Filesystem) : Prop :=
298298
path_exists (parent_path p) fs.
299299

300300
(** Well-formedness transitive closure: if a strict descendant exists,
301-
all ancestors down to the prefix exist. Provable by induction on
302-
path length difference, using well_formed at each step. *)
301+
all ancestors down to the prefix exist.
302+
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. *)
303312
Axiom well_formed_ancestor_exists :
304313
forall fs p child,
305314
well_formed fs ->
@@ -373,7 +382,19 @@ Proof.
373382
Qed.
374383

375384
(** mkdir preserves well-formedness: adding a node whose parent exists
376-
maintains the parent-existence invariant for all paths. *)
385+
maintains the parent-existence invariant for all paths.
386+
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. *)
377398
Axiom mkdir_preserves_well_formed :
378399
forall p fs,
379400
well_formed fs ->

proofs/coq/posix_errors.v

Lines changed: 130 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
Proves error handling correctness and error-free operation equivalence.
55
66
This extends the pure functional model to handle real-world error cases.
7+
8+
Proof status:
9+
- 5 of 6 decision procedures proved constructively (from Filesystem semantics).
10+
- is_empty_dir_dec remains axiomatic: requires Classical or finite-map refactor.
11+
- safe_* definitions use nested `if` on sumbool (not negb, which requires bool).
12+
- functional_extensionality from filesystem_model transitive import.
713
*)
814

915
Require Import String.
@@ -38,68 +44,142 @@ Inductive OperationResult (A : Type) : Type :=
3844
Arguments Success {A}.
3945
Arguments Error {A}.
4046

41-
(** * Safe Operations with Error Handling *)
47+
(** * Decision Procedures
48+
49+
Must appear before the safe_* definitions that use them.
50+
51+
Four decision procedures are proved constructively by inspecting the
52+
Filesystem function (Path -> option FSNode) directly.
53+
path_eq_dec follows from list_eq_dec + String.string_dec.
54+
is_empty_dir_dec is axiomatic — see justification below. *)
55+
56+
(** path_exists is decidable by case analysis on (fs p). *)
57+
Lemma path_exists_dec : forall p fs, {path_exists p fs} + {~ path_exists p fs}.
58+
Proof.
59+
intros p fs.
60+
unfold path_exists.
61+
destruct (fs p) as [node|].
62+
- left. exists node. reflexivity.
63+
- right. intros [node H]. discriminate.
64+
Defined.
65+
66+
(** is_directory is decidable by inspecting the node_type field. *)
67+
Lemma is_directory_dec : forall p fs, {is_directory p fs} + {~ is_directory p fs}.
68+
Proof.
69+
intros p fs.
70+
unfold is_directory.
71+
destruct (fs p) as [[ntype perms]|].
72+
- destruct ntype.
73+
+ (* File *) right. intros [perms' H]. inversion H.
74+
+ (* Directory *) left. exists perms. reflexivity.
75+
- right. intros [perms H]. discriminate.
76+
Defined.
77+
78+
(** is_file is decidable by inspecting the node_type field. *)
79+
Lemma is_file_dec : forall p fs, {is_file p fs} + {~ is_file p fs}.
80+
Proof.
81+
intros p fs.
82+
unfold is_file.
83+
destruct (fs p) as [[ntype perms]|].
84+
- destruct ntype.
85+
+ (* File *) left. exists perms. reflexivity.
86+
+ (* Directory *) right. intros [perms' H]. inversion H.
87+
- right. intros [perms H]. discriminate.
88+
Defined.
89+
90+
(** has_write_permission is decidable by inspecting the node and its writable flag. *)
91+
Lemma has_write_permission_dec : forall p fs, {has_write_permission p fs} + {~ has_write_permission p fs}.
92+
Proof.
93+
intros p fs.
94+
unfold has_write_permission.
95+
destruct (fs p) as [node|].
96+
- destruct (writable (permissions node)) eqn:Hw.
97+
+ left. exists node. split; [reflexivity | assumption].
98+
+ right. intros [node' [Heq Hwr]].
99+
inversion Heq. subst. rewrite Hw in Hwr. discriminate.
100+
- right. intros [node [H _]]. discriminate.
101+
Defined.
102+
103+
(** Path equality is decidable: Path = list string, decided by list_eq_dec + string_dec. *)
104+
Lemma path_eq_dec : forall p1 p2 : Path, {p1 = p2} + {p1 <> p2}.
105+
Proof.
106+
apply list_eq_dec.
107+
apply String.string_dec.
108+
Defined.
109+
110+
(** is_empty_dir is NOT decidable constructively with the current model.
111+
112+
is_empty_dir p fs requires:
113+
forall child : Path, path_prefix p child -> child <> p -> ~ path_exists child fs.
114+
115+
With Filesystem = Path -> option FSNode (a function over an infinite domain),
116+
there is no finite enumeration of all Path values, so this universal
117+
quantification cannot be discharged constructively.
118+
119+
Justification: in the operational model, every reachable filesystem is
120+
produced by a finite sequence of mkdir / rmdir / create_file / delete_file
121+
operations, so the set of inhabited paths is always finite in practice.
122+
Constructive decidability requires changing the Filesystem representation
123+
to a finite map (e.g., list (Path * FSNode) or MSetAVL), at which point
124+
is_empty_dir can be decided by scanning the entries.
125+
126+
Migration path: switch Filesystem to FMaps.t FSNode and reprove. *)
127+
Axiom is_empty_dir_dec : forall p fs, {is_empty_dir p fs} + {~ is_empty_dir p fs}.
128+
129+
(** * Safe Operations with Error Handling
130+
131+
safe_* definitions use nested `if` on sumbool values.
132+
Using `negb (sumbool_val)` is a type error (negb : bool -> bool, not sumbool)
133+
so we invert the condition by nesting the positive branch first. *)
42134

43135
(** Safe mkdir - returns error codes instead of requiring preconditions *)
44136
Definition safe_mkdir (p : Path) (fs : Filesystem) : OperationResult Filesystem :=
45137
if path_exists_dec p fs then
46-
Error EEXIST
47-
else if negb (path_exists_dec (parent_path p) fs) then
48-
Error ENOENT
49-
else if negb (is_directory_dec (parent_path p) fs) then
50-
Error ENOTDIR
51-
else if negb (has_write_permission_dec (parent_path p) fs) then
52-
Error EACCES
53-
else
54-
Success (mkdir p fs).
138+
Error EEXIST (* p already exists *)
139+
else if path_exists_dec (parent_path p) fs then
140+
if is_directory_dec (parent_path p) fs then
141+
if has_write_permission_dec (parent_path p) fs then
142+
Success (mkdir p fs)
143+
else Error EACCES
144+
else Error ENOTDIR
145+
else Error ENOENT. (* parent not found *)
55146

56147
(** Safe rmdir - returns error codes *)
57148
Definition safe_rmdir (p : Path) (fs : Filesystem) : OperationResult Filesystem :=
58-
if negb (path_exists_dec p fs) then
59-
Error ENOENT
60-
else if negb (is_directory_dec p fs) then
61-
Error ENOTDIR
62-
else if negb (is_empty_dir_dec p fs) then
63-
Error ENOTEMPTY
64-
else if negb (has_write_permission_dec (parent_path p) fs) then
65-
Error EACCES
66-
else if path_eq_dec p root_path then
67-
Error EACCES (* Cannot remove root *)
68-
else
69-
Success (rmdir p fs).
149+
if path_exists_dec p fs then
150+
if is_directory_dec p fs then
151+
if is_empty_dir_dec p fs then
152+
if has_write_permission_dec (parent_path p) fs then
153+
if path_eq_dec p root_path then
154+
Error EACCES (* cannot remove root *)
155+
else
156+
Success (rmdir p fs)
157+
else Error EACCES
158+
else Error ENOTEMPTY
159+
else Error ENOTDIR
160+
else Error ENOENT.
70161

71162
(** Safe create_file - returns error codes *)
72163
Definition safe_create_file (p : Path) (fs : Filesystem) : OperationResult Filesystem :=
73164
if path_exists_dec p fs then
74165
Error EEXIST
75-
else if negb (path_exists_dec (parent_path p) fs) then
76-
Error ENOENT
77-
else if negb (is_directory_dec (parent_path p) fs) then
78-
Error ENOTDIR
79-
else if negb (has_write_permission_dec (parent_path p) fs) then
80-
Error EACCES
81-
else
82-
Success (create_file p fs).
166+
else if path_exists_dec (parent_path p) fs then
167+
if is_directory_dec (parent_path p) fs then
168+
if has_write_permission_dec (parent_path p) fs then
169+
Success (create_file p fs)
170+
else Error EACCES
171+
else Error ENOTDIR
172+
else Error ENOENT.
83173

84174
(** Safe delete_file - returns error codes *)
85175
Definition safe_delete_file (p : Path) (fs : Filesystem) : OperationResult Filesystem :=
86-
if negb (path_exists_dec p fs) then
87-
Error ENOENT
88-
else if negb (is_file_dec p fs) then
89-
Error EISDIR
90-
else if negb (has_write_permission_dec (parent_path p) fs) then
91-
Error EACCES
92-
else
93-
Success (delete_file p fs).
94-
95-
(** * Decision Procedures (axiomatized for now) *)
96-
97-
Axiom path_exists_dec : forall p fs, {path_exists p fs} + {~ path_exists p fs}.
98-
Axiom is_directory_dec : forall p fs, {is_directory p fs} + {~ is_directory p fs}.
99-
Axiom is_file_dec : forall p fs, {is_file p fs} + {~ is_file p fs}.
100-
Axiom has_write_permission_dec : forall p fs, {has_write_permission p fs} + {~ has_write_permission p fs}.
101-
Axiom is_empty_dir_dec : forall p fs, {is_empty_dir p fs} + {~ is_empty_dir p fs}.
102-
Axiom path_eq_dec : forall p1 p2 : Path, {p1 = p2} + {p1 <> p2}.
176+
if path_exists_dec p fs then
177+
if is_file_dec p fs then
178+
if has_write_permission_dec (parent_path p) fs then
179+
Success (delete_file p fs)
180+
else Error EACCES
181+
else Error EISDIR
182+
else Error ENOENT.
103183

104184
(** * Error Handling Correctness Theorems *)
105185

@@ -113,7 +193,6 @@ Proof.
113193
- (* -> *)
114194
unfold safe_mkdir.
115195
intros H.
116-
(* Check each condition *)
117196
destruct (path_exists_dec p fs); [discriminate|].
118197
destruct (path_exists_dec (parent_path p) fs); [|discriminate].
119198
destruct (is_directory_dec (parent_path p) fs); [|discriminate].
@@ -193,7 +272,6 @@ Proof.
193272
intros p fs Hnoexist Hnoparent.
194273
unfold safe_mkdir.
195274
destruct (path_exists_dec p fs); [contradiction|].
196-
simpl.
197275
unfold parent_exists in Hnoparent.
198276
destruct (path_exists_dec (parent_path p) fs); [contradiction|].
199277
reflexivity.
@@ -219,7 +297,6 @@ Proof.
219297
intros p fs Hexists Hnotdir.
220298
unfold safe_rmdir.
221299
destruct (path_exists_dec p fs); [|contradiction].
222-
simpl.
223300
destruct (is_directory_dec p fs); [contradiction|].
224301
reflexivity.
225302
Qed.
@@ -233,9 +310,7 @@ Proof.
233310
intros p fs Hisdir Hnotempty.
234311
unfold safe_rmdir.
235312
destruct (path_exists_dec p fs).
236-
- simpl.
237-
destruct (is_directory_dec p fs); [|contradiction].
238-
simpl.
313+
- destruct (is_directory_dec p fs); [|contradiction].
239314
destruct (is_empty_dir_dec p fs); [contradiction|].
240315
reflexivity.
241316
- exfalso.
@@ -276,6 +351,8 @@ Qed.
276351
✓ Correctness: Success iff preconditions hold
277352
✓ Error code correctness: Each error matches specific violation
278353
✓ Reversibility preserved under error handling
354+
✓ 5/6 decision procedures proved constructively (Lemma, not Axiom)
355+
✗ is_empty_dir_dec: justified axiom, requires finite-map Filesystem for proof
279356
280357
This enables:
281358
- Realistic implementation with proper error reporting

0 commit comments

Comments
 (0)