|
9 | 9 | Require Import String. |
10 | 10 | Require Import List. |
11 | 11 | Require Import Bool. |
| 12 | +Require Import Arith. |
12 | 13 | Import ListNotations. |
13 | 14 |
|
14 | 15 | Require Import filesystem_model. |
@@ -297,25 +298,127 @@ Definition well_formed (fs : Filesystem) : Prop := |
297 | 298 | forall p, path_exists p fs -> p <> root_path -> |
298 | 299 | path_exists (parent_path p) fs. |
299 | 300 |
|
| 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 | + |
300 | 375 | (** Well-formedness transitive closure: if a strict descendant exists, |
301 | 376 | all ancestors down to the prefix exist. |
302 | 377 |
|
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 : |
313 | 382 | forall fs p child, |
314 | 383 | well_formed fs -> |
315 | | - path_prefix p child -> |
| 384 | + path_prefix p child = true -> |
316 | 385 | child <> p -> |
317 | 386 | path_exists child fs -> |
318 | 387 | 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. |
319 | 422 |
|
320 | 423 | (** mkdir creates an empty directory (requires well-formedness) *) |
321 | 424 | Lemma mkdir_creates_empty_dir : |
@@ -384,22 +487,47 @@ Qed. |
384 | 487 | (** mkdir preserves well-formedness: adding a node whose parent exists |
385 | 488 | maintains the parent-existence invariant for all paths. |
386 | 489 |
|
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 : |
399 | 497 | forall p fs, |
400 | 498 | well_formed fs -> |
401 | 499 | mkdir_precondition p fs -> |
402 | 500 | 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. |
403 | 531 |
|
404 | 532 | (** Two-directory creation example. *) |
405 | 533 | Example mkdir_two_dirs_reversible : |
|
0 commit comments