diff --git a/PROOF-STATUS.adoc b/PROOF-STATUS.adoc
index 2c24f7e..01269af 100644
--- a/PROOF-STATUS.adoc
+++ b/PROOF-STATUS.adoc
@@ -184,17 +184,27 @@ capstone) remains open by design — see `docs/OND-ROADMAP.adoc`.
signature, the three occupancy predicates in full, the #125 derivations as
negative controls, and `#print axioms` for every theorem (no `sorryAx` anywhere).
`just verify-lean-core` runs the same script locally.
-* **FilesystemCNO ported from Coq (issue #167):**
- `FilesystemCNO.lean` defines `Filesystem` as a concrete list structure and operations
- as executable functions (`mkdir`, `rmdir`, `create`, `unlink`, `readFile`, `writeFile`,
- `stat`, `chmod`, `chown`, `rename`, `snapshot`, `restore`). All 21 former axioms
- (`mkdir_rmdir_inverse`, `create_unlink_inverse`, `rename_inverse`, `read_write_identity`,
- `chmod_identity`, `rename_identity`, `mkdir_not_identity`, `mkdir_idempotent`,
- `snapshot_restore_identity`, and the 12 primitive op declarations) are fully discharged
- to concrete definitions and proved theorems. `AxiomAudit.lean` verifies that all
- FilesystemCNO theorems depend on zero axioms.
- Lean axiom count: `FilesystemCNO` 21 → **0**, `LambdaCNO` 3 → 1
+* **Issue #125 fixed — the Lean axioms no longer derive `False`:**
+ `FilesystemCNO.lean` stated `mkdir_rmdir_inverse`, `create_unlink_inverse` and
+ `rename_inverse` without the occupancy preconditions of the Coq lemmas they mirror;
+ with `mkdir_idempotent` and `mkdir_not_identity` that proved `False`. They now
+ carry `noDirAt` / `noFileAt` / `noEntryAt`, mirrored verbatim from
+ `proofs/coq/filesystem/FilesystemCNO.v`, and the unconditional statement is refuted
+ in-file (`unconditional_mkdir_rmdir_inverse_is_false`). `LambdaCNO.lean` carried the
+ unrestricted `eta_equivalence` axiom (false as stated, the same `LVar 5`
+ counterexample the Coq side documents above); it is now the proved
+ `noLambda`-guarded theorem, `subst_closed_term` is proved, and the unrestricted
+ claim is refuted (`unrestricted_eta_equivalence_is_false`). Lean axiom count:
+ `FilesystemCNO` 21 → 21 (same names, three strengthened), `LambdaCNO` 3 → 1
(`y_combinator_not_identity`, the Lean twin of Coq's class-A `y_not_cno`).
+* **#167 port reverted pending completion (issue #176):** #174 attempted to port
+ `FilesystemCNO.lean`'s 21 axioms to concrete definitions and proved theorems, but
+ the port did not compile in the six-module job (unresolved `Directory`/`Symlink`
+ alternatives, unknown identifiers, failed rewrites). Per #176's ruling, a half-port
+ must not sit red on `main`: `FilesystemCNO.lean` and its paired `AxiomAudit.lean`
+ guards are restored here to the revision above (their last state that compiled,
+ identical to the pre-#174 commit and to the last green `main` run before #174).
+ #167 stays open with finishing the port as its own acceptance criterion.
== Z3 — VERIFIED (this environment)
@@ -240,7 +250,13 @@ capstone) remains open by design — see `docs/OND-ROADMAP.adoc`.
of 182 top-level theorems across the 14 theories, exactly **109 are closed under
the global context** (zero axioms), and 73 rest on Coq stdlib classical axioms
(`ClassicalDedekindReals.*`, `FunctionalExtensionality.*`, `Classical_Prop.classic`,
- `ProofIrrelevance.*`) and/or the tagged project parameters.
+ `ProofIrrelevance.*`) and/or the tagged project parameters. Each theory's logical
+ root is read from `_CoqProject`'s own `-R
` bindings rather than
+ hardcoded (issue #176: the census previously assumed every theory lived under
+ `CNO.`, which is false for `malbolge/` — bound to `Malbolge` — and made the
+ generated driver fail outright instead of censusing it; the table now shows
+ `Malbolge.MalbolgeCore | 7 | 7 | 0`). A directory with no `-R` binding is a hard
+ census failure, never a silent skip.
* All top-level declarations are verified by `proofs/coq/check-axiom-tags.sh` to carry
a unified tag grammar: `(* AXIOM: [METAL-BOUNDARY] ... *)` for physical constants
and laws, or `(* AXIOM: [CLASS-A] ... *)` for provable-in-principle mathematics.
diff --git a/proofs/coq/census-assumptions.sh b/proofs/coq/census-assumptions.sh
index 6f2051f..f948dc7 100755
--- a/proofs/coq/census-assumptions.sh
+++ b/proofs/coq/census-assumptions.sh
@@ -17,12 +17,37 @@ set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
AUDIT_FILE="${1:-}"
-# Find all 14 theories under proofs/coq
+# Logical root for each theory directory, read from _CoqProject (`-R `)
+# rather than hardcoded — issue #176: the census used to assume every theory lived
+# under `CNO.`, which is false for malbolge/ (`-R malbolge Malbolge`) and made the
+# generated driver fail outright instead of censusing it.
+declare -A dir_root
+while read -r flag dir ns || [ -n "$flag" ]; do
+ [ "$flag" = "-R" ] && dir_root["$dir"]="$ns"
+done < "$HERE/_CoqProject"
+
+# Find all 14 theories under proofs/coq, resolving each theory's logical root.
+# A directory with no `-R` binding in _CoqProject is a hard error, never a silent
+# skip — a skip here would turn a missing binding into a vacuous pass.
theories=()
+declare -A base_root
for d in common category quantum lambda filesystem physics ond malbolge; do
if [ -d "$HERE/$d" ]; then
+ root="${dir_root[$d]:-}"
+ if [ -z "$root" ]; then
+ echo "CENSUS FAILED: directory '$d' has no -R binding in _CoqProject" >&2
+ exit 1
+ fi
while IFS= read -r f; do
- [ -f "$f" ] && theories+=("$f")
+ if [ -f "$f" ]; then
+ base="$(basename "${f%.v}")"
+ if [ -n "${base_root[$base]+x}" ]; then
+ echo "CENSUS FAILED: duplicate theory basename '$base' in '$f'" >&2
+ exit 1
+ fi
+ theories+=("$f")
+ base_root["$base"]="$root"
+ fi
done < <(find "$HERE/$d" -maxdepth 1 -name "*.v" | sort)
fi
done
@@ -48,20 +73,22 @@ if command -v coqc >/dev/null 2>&1 && [ -f "$HERE/common/CNO.vo" ]; then
tmp="$(mktemp -d "${TMPDIR:-/tmp}/az-census.XXXXXX")"
trap 'rm -rf "$tmp"' EXIT
- # Generate Census.v
+ # Generate Census.v — each theory is Required under its own logical root
+ # (from _CoqProject), not a hardcoded `CNO.`.
{
echo "(* Auto-generated by census-assumptions.sh *)"
for f in "${theories[@]}"; do
base="$(basename "${f%.v}")"
- echo "Require CNO.$base."
+ echo "Require ${base_root[$base]}.$base."
done
for t in "${thm_lines[@]}"; do
- echo "Print Assumptions CNO.$t."
+ base="${t%%.*}"
+ echo "Print Assumptions ${base_root[$base]}.$t."
done
} > "$tmp/Census.v"
RFLAGS=()
- while read -r flag dir ns; do
+ while read -r flag dir ns || [ -n "$flag" ]; do
[ "$flag" = "-R" ] && RFLAGS+=("-R" "$HERE/$dir" "$ns")
done < "$HERE/_CoqProject"
@@ -73,29 +100,42 @@ if command -v coqc >/dev/null 2>&1 && [ -f "$HERE/common/CNO.vo" ]; then
exit 1
fi
- # Parse census output
+ # `coqc` does NOT echo the `Print Assumptions X.` command back in its output —
+ # it prints only the verdict ("Closed under the global context" or "Axioms:"
+ # + the axiom list). So the theorem each verdict belongs to cannot be read off
+ # the coqc output; it is read off the ORDER the Print Assumptions calls were
+ # emitted into Census.v instead, which is exactly thm_lines' order. The order
+ # file lists each theorem as "root.base.thm" so the table can key rows by
+ # "root.base" (issue #176 criterion: malbolge/ theorems shown under `Malbolge.`,
+ # not folded into a bare basename column that hides the root entirely).
+ for t in "${thm_lines[@]}"; do
+ base="${t%%.*}"
+ echo "${base_root[$base]}.$t"
+ done > "$tmp/order.txt"
+
+ # Parse census output against the order file. END asserts every emitted verdict
+ # was consumed exactly once and the closed/axiom-dependent split accounts for
+ # every theorem — a positive control that the block-to-theorem mapping is 1:1,
+ # not a hopeful zip. A mismatch fails the gate (script exits with awk's rc, not
+ # an unconditional 0).
printf '%s\n' "$out" | awk -v total="$total_theorems" '
- BEGIN {
- current_thm = ""
- in_axioms = 0
- }
- /^Print Assumptions/ {
- current_thm = $3
- sub(/\.$/, "", current_thm)
- split(current_thm, p, ".")
- theory = p[2]
- thm = p[3]
- thms_per_theory[theory]++
- in_axioms = 0
- next
- }
+ NR == FNR { order[++n] = $0; next }
+ FNR == 1 { idx = 0; in_axioms = 0 }
/^Closed under the global context/ {
+ idx++
+ split(order[idx], p, ".")
+ theory = p[1] "." p[2]
+ thms_per_theory[theory]++
closed_per_theory[theory]++
total_closed++
in_axioms = 0
next
}
/^Axioms:/ {
+ idx++
+ split(order[idx], p, ".")
+ theory = p[1] "." p[2]
+ thms_per_theory[theory]++
axiom_dep_per_theory[theory]++
total_dep++
in_axioms = 1
@@ -108,21 +148,39 @@ if command -v coqc >/dev/null 2>&1 && [ -f "$HERE/common/CNO.vo" ]; then
next
}
END {
+ if (idx != total) {
+ printf "CENSUS FAILED: %d verdict blocks parsed, expected %d theorems\n", idx, total > "/dev/stderr"
+ exit 1
+ }
+ if (total_closed + total_dep != total) {
+ printf "CENSUS FAILED: closed(%d) + axiom-dependent(%d) != total(%d)\n", total_closed, total_dep, total > "/dev/stderr"
+ exit 1
+ }
printf "\n== Census: %d top-level theorems across 14 theories ==\n", total
printf "Closed under global context: %d\nAxiom-dependent: %d\n\n", total_closed, total_dep
- printf "| %-22s | %-8s | %-6s | %-15s |\n", "Theory", "Theorems", "Closed", "Axiom-Dependent"
- printf "|------------------------|----------|--------|-----------------|\n"
- for (t in thms_per_theory) {
+ printf "| %-24s | %-8s | %-6s | %-15s |\n", "Theory", "Theorems", "Closed", "Axiom-Dependent"
+ printf "|--------------------------|----------|--------|-----------------|\n"
+ # Manual sort (no asorti — that is a gawk extension, and the CI runner is
+ # not guaranteed to alias /usr/bin/awk to gawk).
+ n_rows = 0
+ for (t in thms_per_theory) sorted[++n_rows] = t
+ for (i = 1; i <= n_rows; i++)
+ for (j = i + 1; j <= n_rows; j++)
+ if (sorted[j] < sorted[i]) { tmp = sorted[i]; sorted[i] = sorted[j]; sorted[j] = tmp }
+ for (i = 1; i <= n_rows; i++) {
+ t = sorted[i]
c = closed_per_theory[t] + 0
d = axiom_dep_per_theory[t] + 0
- printf "| %-22s | %-8d | %-6d | %-15d |\n", t, thms_per_theory[t], c, d
+ printf "| %-24s | %-8d | %-6d | %-15d |\n", t, thms_per_theory[t], c, d
}
printf "\n== Axioms in non-closed blocks ==\n"
for (ax in axiom_counts) {
printf " %-50s : %d\n", ax, axiom_counts[ax]
}
}
- '
+ ' "$tmp/order.txt" -
+ rc=$?
+ exit "$rc"
else
# Static / pre-computed census display when coqc / .vo is not yet built
echo "== Census: $total_theorems top-level theorems across 14 theories =="
diff --git a/proofs/lean4/AxiomAudit.lean b/proofs/lean4/AxiomAudit.lean
index c3f1700..d4c4929 100644
--- a/proofs/lean4/AxiomAudit.lean
+++ b/proofs/lean4/AxiomAudit.lean
@@ -519,27 +519,35 @@ info: 'FilesystemCNO.fs_nop_is_cno' does not depend on any axioms
#guard_msgs (whitespace := lax) in #print axioms fs_nop_is_cno
/--
-info: 'FilesystemCNO.mkdir_rmdir_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.mkdir_rmdir_is_cno' depends on axioms: [FilesystemCNO.mkdir,
+ FilesystemCNO.mkdir_rmdir_inverse,
+ FilesystemCNO.rmdir]
-/
#guard_msgs (whitespace := lax) in #print axioms mkdir_rmdir_is_cno
/--
-info: 'FilesystemCNO.create_unlink_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.create_unlink_is_cno' depends on axioms: [FilesystemCNO.create,
+ FilesystemCNO.create_unlink_inverse,
+ FilesystemCNO.unlink]
-/
#guard_msgs (whitespace := lax) in #print axioms create_unlink_is_cno
/--
-info: 'FilesystemCNO.read_write_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.read_write_is_cno' depends on axioms: [FilesystemCNO.readFile,
+ FilesystemCNO.read_write_identity,
+ FilesystemCNO.writeFile]
-/
#guard_msgs (whitespace := lax) in #print axioms read_write_is_cno
/--
-info: 'FilesystemCNO.chmod_nop_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.chmod_nop_is_cno' depends on axioms: [FilesystemCNO.chmod,
+ FilesystemCNO.chmod_identity,
+ FilesystemCNO.stat]
-/
#guard_msgs (whitespace := lax) in #print axioms chmod_nop_is_cno
/--
-info: 'FilesystemCNO.rename_nop_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.rename_nop_is_cno' depends on axioms: [FilesystemCNO.rename, FilesystemCNO.rename_identity]
-/
#guard_msgs (whitespace := lax) in #print axioms rename_nop_is_cno
@@ -549,7 +557,7 @@ info: 'FilesystemCNO.fs_cno_composition' does not depend on any axioms
#guard_msgs (whitespace := lax) in #print axioms fs_cno_composition
/--
-info: 'FilesystemCNO.mkdir_alone_not_cno' does not depend on any axioms
+info: 'FilesystemCNO.mkdir_alone_not_cno' depends on axioms: [FilesystemCNO.mkdir, FilesystemCNO.mkdir_not_identity]
-/
#guard_msgs (whitespace := lax) in #print axioms mkdir_alone_not_cno
@@ -559,12 +567,17 @@ info: 'FilesystemCNO.valence_reversible_pair_is_cno' does not depend on any axio
#guard_msgs (whitespace := lax) in #print axioms valence_reversible_pair_is_cno
/--
-info: 'FilesystemCNO.snapshot_restore_is_cno' does not depend on any axioms
+info: 'FilesystemCNO.snapshot_restore_is_cno' depends on axioms: [FilesystemCNO.restore,
+ FilesystemCNO.snapshot,
+ FilesystemCNO.snapshot_restore_identity]
-/
#guard_msgs (whitespace := lax) in #print axioms snapshot_restore_is_cno
/--
-info: 'FilesystemCNO.unconditional_mkdir_rmdir_inverse_is_false' does not depend on any axioms
+info: 'FilesystemCNO.unconditional_mkdir_rmdir_inverse_is_false' depends on axioms: [FilesystemCNO.mkdir,
+ FilesystemCNO.mkdir_idempotent,
+ FilesystemCNO.mkdir_not_identity,
+ FilesystemCNO.rmdir]
-/
#guard_msgs (whitespace := lax) in #print axioms unconditional_mkdir_rmdir_inverse_is_false
end
@@ -650,6 +663,14 @@ run_cmd do
let modules : Array Name := #[`CNO, `OND, `CNOCategory, `CNOBridge, `FilesystemCNO, `LambdaCNO]
let allowed : Array Name := #[
`propext, `Quot.sound,
+ `FilesystemCNO.mkdir, `FilesystemCNO.rmdir, `FilesystemCNO.create,
+ `FilesystemCNO.unlink, `FilesystemCNO.readFile, `FilesystemCNO.writeFile,
+ `FilesystemCNO.chmod, `FilesystemCNO.stat, `FilesystemCNO.rename,
+ `FilesystemCNO.mkdir_rmdir_inverse, `FilesystemCNO.create_unlink_inverse,
+ `FilesystemCNO.read_write_identity, `FilesystemCNO.chmod_identity,
+ `FilesystemCNO.rename_identity, `FilesystemCNO.mkdir_not_identity,
+ `FilesystemCNO.snapshot, `FilesystemCNO.restore,
+ `FilesystemCNO.snapshot_restore_identity, `FilesystemCNO.mkdir_idempotent,
`LambdaCNO.y_combinator_not_identity
]
let mut checked := 0
diff --git a/proofs/lean4/FilesystemCNO.lean b/proofs/lean4/FilesystemCNO.lean
index a706bcc..014435e 100644
--- a/proofs/lean4/FilesystemCNO.lean
+++ b/proofs/lean4/FilesystemCNO.lean
@@ -75,347 +75,90 @@ def noEntryAt (p : Path) (fs : Filesystem) : Prop :=
| FileEntry.Directory p' _ _ => p ≠ p'
| FileEntry.Symlink p' _ _ => p ≠ p'
-/-! ## Helper Definitions for Concrete Filesystem Operations -/
-
-/-- Default metadata used when fresh entries are created. -/
-def default_meta : FileMetadata :=
- { permissions := [], owner := 0, size := 0, mtime := 0 }
-
-/-- The path of an entry regardless of its kind. -/
-def path_of : FileEntry → Path
- | FileEntry.File p _ _ => p
- | FileEntry.Directory p _ _ => p
- | FileEntry.Symlink p _ _ => p
-
-/-- Rename an entry's own path, preserving all other components. -/
-def set_path (np : Path) : FileEntry → FileEntry
- | FileEntry.File _ c m => FileEntry.File np c m
- | FileEntry.Directory _ es m => FileEntry.Directory np es m
- | FileEntry.Symlink _ t m => FileEntry.Symlink np t m
-
-/-- Set permissions on a metadata record. -/
-def set_perms (perms : PermSet) (m : FileMetadata) : FileMetadata :=
- { permissions := perms, owner := m.owner, size := m.size, mtime := m.mtime }
-
-/-- Set owner on a metadata record. -/
-def set_owner (o : Nat) (m : FileMetadata) : FileMetadata :=
- { permissions := m.permissions, owner := o, size := m.size, mtime := m.mtime }
-
-/-- Check if a directory with the given path exists in the filesystem. -/
-def dir_exists (p : Path) : Filesystem → Bool
- | [] => false
- | e :: rest => match e with
- | FileEntry.Directory p' _ _ => (p == p') || dir_exists p rest
- | _ => dir_exists p rest
-
-@[simp] lemma path_of_set_path (p : Path) (e : FileEntry) :
- path_of (set_path p e) = p := by
- cases e <;> rfl
-
-@[simp] lemma set_path_id (e : FileEntry) :
- set_path (path_of e) e = e := by
- cases e <;> rfl
-
-@[simp] lemma set_path_set_path (p1 p2 : Path) (e : FileEntry) :
- set_path p2 (set_path p1 e) = set_path p2 e := by
- cases e <;> rfl
-
-@[simp] lemma set_perms_id (m : FileMetadata) :
- set_perms m.permissions m = m := by
- cases m; rfl
-
-/-! ## Filesystem Operations (Concrete, executable definitions) -/
-
-/-- Create directory: prepends fresh directory if absent, no-op if exists. -/
-def mkdir (p : Path) (fs : Filesystem) : Filesystem :=
- if dir_exists p fs then fs else FileEntry.Directory p [] default_meta :: fs
-
-/-- Remove empty directory: drops first matching empty directory. -/
-def rmdir (p : Path) : Filesystem → Filesystem
- | [] => []
- | e :: rest => match e with
- | FileEntry.Directory p' [] _ => if p == p' then rest else e :: rmdir p rest
- | _ => e :: rmdir p rest
-
-/-- Create file: prepends fresh empty file. -/
-def create (p : Path) (fs : Filesystem) : Filesystem :=
- FileEntry.File p [] default_meta :: fs
-
-/-- Delete file: drops first matching file. -/
-def unlink (p : Path) : Filesystem → Filesystem
- | [] => []
- | e :: rest => match e with
- | FileEntry.File p' _ _ => if p == p' then rest else e :: unlink p rest
- | _ => e :: unlink p rest
-
-/-- Read file content: content of first matching file, if any. -/
-def readFile (p : Path) : Filesystem → Option FileContent
- | [] => none
- | e :: rest => match e with
- | FileEntry.File p' c _ => if p == p' then some c else readFile p rest
- | _ => readFile p rest
-
-/-- Write file content: update content of first matching file. -/
-def writeFile (p : Path) (content : FileContent) : Filesystem → Filesystem
- | [] => []
- | e :: rest => match e with
- | FileEntry.File p' c m =>
- if p == p' then FileEntry.File p' content m :: rest
- else FileEntry.File p' c m :: writeFile p content rest
- | _ => e :: writeFile p content rest
-
-/-- Get file metadata: metadata of first matching entry. -/
-def stat (p : Path) : Filesystem → Option FileMetadata
- | [] => none
- | e :: rest => match e with
- | FileEntry.File p' _ m => if p == p' then some m else stat p rest
- | FileEntry.Directory p' _ m => if p == p' then some m else stat p rest
- | FileEntry.Symlink p' _ m => if p == p' then some m else stat p rest
-
-/-- Change permissions of first matching entry. -/
-def chmod (p : Path) (perms : PermSet) : Filesystem → Filesystem
- | [] => []
- | e :: rest => match e with
- | FileEntry.File p' c m =>
- if p == p' then FileEntry.File p' c (set_perms perms m) :: rest
- else FileEntry.File p' c m :: chmod p perms rest
- | FileEntry.Directory p' es m =>
- if p == p' then FileEntry.Directory p' es (set_perms perms m) :: rest
- else FileEntry.Directory p' es m :: chmod p perms rest
- | FileEntry.Symlink p' t m =>
- if p == p' then FileEntry.Symlink p' t (set_perms perms m) :: rest
- else FileEntry.Symlink p' t m :: chmod p perms rest
-
-/-- Change owner of first matching entry. -/
-def chown (p : Path) (o : Nat) : Filesystem → Filesystem
- | [] => []
- | e :: rest => match e with
- | FileEntry.File p' c m =>
- if p == p' then FileEntry.File p' c (set_owner o m) :: rest
- else FileEntry.File p' c m :: chown p o rest
- | FileEntry.Directory p' es m =>
- if p == p' then FileEntry.Directory p' es (set_owner o m) :: rest
- else FileEntry.Directory p' es m :: chown p o rest
- | FileEntry.Symlink p' t m =>
- if p == p' then FileEntry.Symlink p' t (set_owner o m) :: rest
- else FileEntry.Symlink p' t m :: chown p o rest
-
-/-- Rename/move file: retargets path of first matching entry. -/
-def rename (p1 p2 : Path) : Filesystem → Filesystem
- | [] => []
- | e :: rest =>
- if p1 == path_of e then set_path p2 e :: rest
- else e :: rename p1 p2 rest
-
-/-- Snapshot operation: capture current filesystem state. -/
-def snapshot (fs : Filesystem) : Filesystem := fs
-
-/-- Restore operation: restore from snapshot. -/
-def restore (snap : Filesystem) (_current : Filesystem) : Filesystem := snap
-
-/-! ## Helper lemmas -/
-
-lemma no_dir_dir_exists_false (p : Path) (fs : Filesystem) (h : noDirAt p fs) :
- dir_exists p fs = false := by
- induction fs with
- | nil => rfl
- | cons e rest ih =>
- cases e with
- | Directory p' es m =>
- have h_not : p ≠ p' := h (FileEntry.Directory p' es m) (List.Mem.head _)
- have h_beq : (p == p') = false := beq_false_of_ne h_not
- have h_rest : noDirAt p rest := fun e' he' => h e' (List.Mem.tail _ he')
- show ((p == p') || dir_exists p rest) = false
- rw [h_beq, ih h_rest]
- rfl
- | File p' c m =>
- have h_rest : noDirAt p rest := fun e' he' => h e' (List.Mem.tail _ he')
- show dir_exists p rest = false
- exact ih h_rest
- | Symlink p' t m =>
- have h_rest : noDirAt p rest := fun e' he' => h e' (List.Mem.tail _ he')
- show dir_exists p rest = false
- exact ih h_rest
-
-/-! ## Operation Theorems (Ported from Coq FilesystemCNO.v) -/
-
-/-- mkdir followed by rmdir is identity — on a filesystem with no directory at `p`. -/
-theorem mkdir_rmdir_inverse (p : Path) (fs : Filesystem) (h : noDirAt p fs) :
- rmdir p (mkdir p fs) = fs := by
- unfold mkdir
- have h_false : dir_exists p fs = false := no_dir_dir_exists_false p fs h
- rw [if_neg (by rw [h_false]; decide)]
- show (if p == p then fs else _) = fs
- rw [beq_self_eq_true p]
- rfl
-
-/-- create followed by unlink is identity — on a filesystem with no file at `p`. -/
-theorem create_unlink_inverse (p : Path) (fs : Filesystem) (_h : noFileAt p fs) :
- unlink p (create p fs) = fs := by
- unfold create unlink
- show (if p == p then fs else _) = fs
- rw [beq_self_eq_true p]
- rfl
+/-! ## Filesystem Operations -/
+
+/-- Create directory -/
+-- AXIOM: mkdir; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom mkdir : Path → Filesystem → Filesystem
+
+/-- Remove directory -/
+-- AXIOM: rmdir; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom rmdir : Path → Filesystem → Filesystem
+
+/-- Create file -/
+-- AXIOM: create; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom create : Path → Filesystem → Filesystem
+
+/-- Delete file -/
+-- AXIOM: unlink; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom unlink : Path → Filesystem → Filesystem
+
+/-- Read file content -/
+-- AXIOM: readFile; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom readFile : Path → Filesystem → Option FileContent
+
+/-- Write file content -/
+-- AXIOM: writeFile; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom writeFile : Path → FileContent → Filesystem → Filesystem
+
+/-- Get file metadata -/
+-- AXIOM: stat; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom stat : Path → Filesystem → Option FileMetadata
+
+/-- Change permissions -/
+-- AXIOM: chmod; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom chmod : Path → PermSet → Filesystem → Filesystem
+
+/-- Change owner -/
+-- AXIOM: chown; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom chown : Path → Nat → Filesystem → Filesystem
+
+/-- Rename/move file -/
+-- AXIOM: rename; opaque POSIX primitive op; §(c) per docs/proof-debt.md.
+axiom rename : Path → Path → Filesystem → Filesystem
+
+/-! ## Operation Axioms -/
+
+/-- mkdir followed by rmdir is identity — on a filesystem with no directory
+ at `p`. Without the precondition the law is false (mkdir on an existing
+ directory is a no-op, so rmdir then removes it) and, together with
+ `mkdir_idempotent` and `mkdir_not_identity`, derived `False` (#125). -/
+-- AXIOM: mkdir_rmdir_inverse; POSIX-semantics specification (mirrors Coq Lemma, same precondition); §(c) per docs/proof-debt.md.
+axiom mkdir_rmdir_inverse (p : Path) (fs : Filesystem) :
+ noDirAt p fs →
+ rmdir p (mkdir p fs) = fs
+
+/-- create followed by unlink is identity — on a filesystem with no file at
+ `p` (the precondition Coq's `create_unlink_inverse` states). -/
+-- AXIOM: create_unlink_inverse; POSIX-semantics specification (mirrors Coq Lemma, same precondition); §(c) per docs/proof-debt.md.
+axiom create_unlink_inverse (p : Path) (fs : Filesystem) :
+ noFileAt p fs →
+ unlink p (create p fs) = fs
/-- read followed by write is identity -/
-theorem read_write_identity (p : Path) (fs : Filesystem) (content : FileContent)
- (h : readFile p fs = some content) :
- writeFile p content fs = fs := by
- induction fs with
- | nil => contradiction
- | cons e rest ih =>
- cases e with
- | File p' c m =>
- by_cases hp : p == p'
- · have hp_eq : p = p' := eq_of_beq hp
- subst hp_eq
- show (if p' == p' then FileEntry.File p' content m :: rest else _) = FileEntry.File p' c m :: rest
- rw [beq_self_eq_true p']
- have h_content : c = content := by
- revert h
- show (if p' == p' then some c else readFile p' rest) = some content → c = content
- rw [beq_self_eq_true p']
- intro h_eq; injection h_eq
- subst h_content
- rfl
- · have h_read : readFile p rest = some content := by
- revert h
- show (if p == p' then some c else readFile p rest) = some content → readFile p rest = some content
- rw [if_neg hp]
- exact id
- show (if p == p' then _ else FileEntry.File p' c m :: writeFile p content rest) = FileEntry.File p' c m :: rest
- rw [if_neg hp]
- rw [ih h_read]
- | Directory p' es m =>
- have h_read : readFile p rest = some content := h
- show FileEntry.Directory p' es m :: writeFile p content rest = FileEntry.Directory p' es m :: rest
- rw [ih h_read]
- | Symlink p' t m =>
- have h_read : readFile p rest = some content := h
- show FileEntry.Symlink p' t m :: writeFile p content rest = FileEntry.Symlink p' t m :: rest
- rw [ih h_read]
+-- AXIOM: read_write_identity; POSIX-semantics specification (mirrors Coq); §(c) per docs/proof-debt.md.
+axiom read_write_identity (p : Path) (fs : Filesystem) (content : FileContent) :
+ readFile p fs = some content →
+ writeFile p content fs = fs
/-- chmod to current permissions is identity -/
-theorem chmod_identity (p : Path) (fs : Filesystem) (meta : FileMetadata)
- (h : stat p fs = some meta) :
- chmod p meta.permissions fs = fs := by
- induction fs with
- | nil => contradiction
- | cons e rest ih =>
- cases e with
- | File p' c m =>
- by_cases hp : p == p'
- · have hp_eq : p = p' := eq_of_beq hp
- subst hp_eq
- show (if p' == p' then FileEntry.File p' c (set_perms meta.permissions m) :: rest else _) = FileEntry.File p' c m :: rest
- rw [beq_self_eq_true p']
- have h_meta : m = meta := by
- revert h
- show (if p' == p' then some m else stat p' rest) = some meta → m = meta
- rw [beq_self_eq_true p']
- intro h_eq; injection h_eq
- subst h_meta
- rw [set_perms_id]
- · have h_stat : stat p rest = some meta := by
- revert h
- show (if p == p' then some m else stat p rest) = some meta → stat p rest = some meta
- rw [if_neg hp]
- exact id
- show (if p == p' then _ else FileEntry.File p' c m :: chmod p meta.permissions rest) = FileEntry.File p' c m :: rest
- rw [if_neg hp]
- rw [ih h_stat]
- | Directory p' es m =>
- by_cases hp : p == p'
- · have hp_eq : p = p' := eq_of_beq hp
- subst hp_eq
- show (if p' == p' then FileEntry.Directory p' es (set_perms meta.permissions m) :: rest else _) = FileEntry.Directory p' es m :: rest
- rw [beq_self_eq_true p']
- have h_meta : m = meta := by
- revert h
- show (if p' == p' then some m else stat p' rest) = some meta → m = meta
- rw [beq_self_eq_true p']
- intro h_eq; injection h_eq
- subst h_meta
- rw [set_perms_id]
- · have h_stat : stat p rest = some meta := by
- revert h
- show (if p == p' then some m else stat p rest) = some meta → stat p rest = some meta
- rw [if_neg hp]
- exact id
- show (if p == p' then _ else FileEntry.Directory p' es m :: chmod p meta.permissions rest) = FileEntry.Directory p' es m :: rest
- rw [if_neg hp]
- rw [ih h_stat]
- | Symlink p' t m =>
- by_cases hp : p == p'
- · have hp_eq : p = p' := eq_of_beq hp
- subst hp_eq
- show (if p' == p' then FileEntry.Symlink p' t (set_perms meta.permissions m) :: rest else _) = FileEntry.Symlink p' t m :: rest
- rw [beq_self_eq_true p']
- have h_meta : m = meta := by
- revert h
- show (if p' == p' then some m else stat p' rest) = some meta → m = meta
- rw [beq_self_eq_true p']
- intro h_eq; injection h_eq
- subst h_meta
- rw [set_perms_id]
- · have h_stat : stat p rest = some meta := by
- revert h
- show (if p == p' then some m else stat p rest) = some meta → stat p rest = some meta
- rw [if_neg hp]
- exact id
- show (if p == p' then _ else FileEntry.Symlink p' t m :: chmod p meta.permissions rest) = FileEntry.Symlink p' t m :: rest
- rw [if_neg hp]
- rw [ih h_stat]
+-- AXIOM: chmod_identity; POSIX-semantics specification (mirrors Coq); §(c) per docs/proof-debt.md.
+axiom chmod_identity (p : Path) (fs : Filesystem) (meta : FileMetadata) :
+ stat p fs = some meta →
+ chmod p meta.permissions fs = fs
/-- rename to same path is identity -/
-theorem rename_identity (p : Path) (fs : Filesystem) :
- rename p p fs = fs := by
- induction fs with
- | nil => rfl
- | cons e rest ih =>
- show (if p == path_of e then set_path p e :: rest else e :: rename p p rest) = e :: rest
- by_cases hp : p == path_of e
- · rw [if_pos hp]
- have hp_eq : p = path_of e := eq_of_beq hp
- subst hp_eq
- rw [set_path_id]
- · rw [if_neg hp]
- rw [ih]
-
-/-- rename A to B followed by rename B to A is identity -/
-theorem rename_inverse (p1 p2 : Path) (fs : Filesystem) (hneq : p1 ≠ p2) (hno : noEntryAt p2 fs) :
- rename p2 p1 (rename p1 p2 fs) = fs := by
- induction fs with
- | nil => rfl
- | cons e rest ih =>
- show rename p2 p1 (if p1 == path_of e then set_path p2 e :: rest else e :: rename p1 p2 rest) = e :: rest
- by_cases hp1 : p1 == path_of e
- · rw [if_pos hp1]
- show (if p2 == path_of (set_path p2 e) then set_path p1 (set_path p2 e) :: rest else _) = e :: rest
- rw [path_of_set_path]
- rw [beq_self_eq_true p2]
- rw [if_pos rfl]
- rw [set_path_set_path]
- have hp1_eq : p1 = path_of e := eq_of_beq hp1
- subst hp1_eq
- rw [set_path_id]
- · rw [if_neg hp1]
- show (if p2 == path_of e then _ else e :: rename p2 p1 (rename p1 p2 rest)) = e :: rest
- have hp2_ne : p2 ≠ path_of e := by
- intro h_contra
- specialize hno e (List.Mem.head _)
- cases e with
- | File p' _ _ => exact hno h_contra
- | Directory p' _ _ => exact hno h_contra
- | Symlink p' _ _ => exact hno h_contra
- have hp2_beq : (p2 == path_of e) = false := beq_false_of_ne hp2_ne
- rw [if_neg (by rw [hp2_beq]; decide)]
- have hno_rest : noEntryAt p2 rest := fun e' he' => hno e' (List.Mem.tail _ he')
- rw [ih hno_rest]
-
-/-- snapshot followed by restore is identity -/
-theorem snapshot_restore_identity (fs : Filesystem) :
- restore (snapshot fs) fs = fs := rfl
+-- AXIOM: rename_identity; POSIX-semantics specification (mirrors Coq); §(c) per docs/proof-debt.md.
+axiom rename_identity (p : Path) (fs : Filesystem) :
+ rename p p fs = fs
+
+/-- rename A to B followed by rename B to A is identity — when `p1 ≠ p2` and
+ nothing lives at `p2` (both preconditions Coq's `rename_inverse` states). -/
+-- AXIOM: rename_inverse; POSIX-semantics specification (mirrors Coq Lemma, same preconditions); §(c) per docs/proof-debt.md.
+axiom rename_inverse (p1 p2 : Path) (fs : Filesystem) :
+ p1 ≠ p2 →
+ noEntryAt p2 fs →
+ rename p2 p1 (rename p1 p2 fs) = fs
/-! ## Filesystem CNO Definition -/
@@ -436,28 +179,32 @@ theorem fs_nop_is_cno : isFsCNO fs_nop := by
intro fs
rfl
-/-- mkdir followed by rmdir. -/
-def mkdirRmdirOp (p : Path) : FsOp :=
+/-- mkdir followed by rmdir. `noncomputable` — calls axioms `mkdir`/`rmdir`. -/
+noncomputable def mkdirRmdirOp (p : Path) : FsOp :=
fun fs => rmdir p (mkdir p fs)
-/-- mkdir;rmdir is the identity on every filesystem with no directory at `p`. -/
+/-- mkdir;rmdir is the identity on every filesystem with no directory at `p`
+ (Coq `mkdir_rmdir_is_cno`, same statement). The unconditional
+ `isFsCNO (mkdirRmdirOp p)` is not provable and is false on the Coq model. -/
theorem mkdir_rmdir_is_cno (p : Path) (fs : Filesystem) (h : noDirAt p fs) :
mkdirRmdirOp p fs = fs := by
unfold mkdirRmdirOp
exact mkdir_rmdir_inverse p fs h
-/-- create followed by unlink. -/
-def createUnlinkOp (p : Path) : FsOp :=
+/-- create followed by unlink. `noncomputable` — wraps axioms. -/
+noncomputable def createUnlinkOp (p : Path) : FsOp :=
fun fs => unlink p (create p fs)
-/-- create;unlink is the identity on every filesystem with no file at `p`. -/
+/-- create;unlink is the identity on every filesystem with no file at `p`
+ (Coq `create_unlink_is_cno`, same statement). -/
theorem create_unlink_is_cno (p : Path) (fs : Filesystem) (h : noFileAt p fs) :
createUnlinkOp p fs = fs := by
unfold createUnlinkOp
exact create_unlink_inverse p fs h
-/-- read followed by write. -/
-def readWriteOp (p : Path) : FsOp :=
+/-- Write the content returned by `readFile` back to `p`, leaving the filesystem
+ unchanged if `readFile` returns `none`. `noncomputable` — wraps axioms. -/
+noncomputable def readWriteOp (p : Path) : FsOp :=
fun fs =>
match readFile p fs with
| some content => writeFile p content fs
@@ -472,8 +219,9 @@ theorem read_write_is_cno (p : Path) :
| some content =>
exact read_write_identity p fs content h
-/-- chmod to current permissions. -/
-def chmodNopOp (p : Path) : FsOp :=
+/-- Set permissions at `p` to those returned by `stat`, leaving the filesystem
+ unchanged if `stat` returns `none`. `noncomputable` — wraps axioms. -/
+noncomputable def chmodNopOp (p : Path) : FsOp :=
fun fs =>
match stat p fs with
| some meta => chmod p meta.permissions fs
@@ -488,8 +236,8 @@ theorem chmod_nop_is_cno (p : Path) :
| some meta =>
exact chmod_identity p fs meta h
-/-- rename to same path. -/
-def renameNopOp (p : Path) : FsOp :=
+/-- rename to same path. `noncomputable` — wraps axiom. -/
+noncomputable def renameNopOp (p : Path) : FsOp :=
fun fs => rename p p fs
theorem rename_nop_is_cno (p : Path) :
@@ -519,13 +267,10 @@ theorem fs_cno_composition (op1 op2 : FsOp) :
/-! ## Non-CNO Operations -/
-/-- mkdir alone is NOT a CNO. -/
-theorem mkdir_not_identity : ∃ (p : Path) (fs : Filesystem), mkdir p fs ≠ fs := by
- exists ""
- exists []
- unfold mkdir dir_exists
- intro h
- contradiction
+/-- mkdir alone is NOT a CNO. Coq proves this Lemma on its concrete model
+ (`exists "" nil`); over opaque operations it has to be assumed. -/
+-- AXIOM: mkdir_not_identity; mirrors the Coq Lemma (proved on the concrete model); §(c) per docs/proof-debt.md.
+axiom mkdir_not_identity : ∃ (p : Path) (fs : Filesystem), mkdir p fs ≠ fs
theorem mkdir_alone_not_cno :
¬ (∀ p, isFsCNO (fun fs => mkdir p fs)) := by
@@ -565,7 +310,24 @@ example (p : Path) (fs : Filesystem) (h : noFileAt p fs) :
/-! ## Snapshot and Restore -/
-def snapshotRestoreOp : FsOp :=
+/-- Snapshot operation -/
+-- AXIOM: snapshot; opaque snapshot primitive; §(c) per docs/proof-debt.md.
+axiom snapshot : Filesystem → Filesystem
+
+/-- Restore from snapshot -/
+-- AXIOM: restore; opaque restore primitive; §(c) per docs/proof-debt.md.
+axiom restore : Filesystem → Filesystem → Filesystem
+
+/-- snapshot followed by restore is identity -/
+-- AXIOM: snapshot_restore_identity; snapshot/restore specification (mirrors Coq); §(c) per docs/proof-debt.md.
+axiom snapshot_restore_identity (fs : Filesystem) :
+ restore (snapshot fs) fs = fs
+
+-- `noncomputable` because `restore` and `snapshot` are axioms with no
+-- executable body; without this Lean 4.16 refuses to emit code for `def`.
+/-- Restore a snapshot of the input filesystem onto that same filesystem.
+ Returns the input filesystem by `snapshot_restore_identity`. -/
+noncomputable def snapshotRestoreOp : FsOp :=
fun fs => restore (snapshot fs) fs
theorem snapshot_restore_is_cno :
@@ -580,22 +342,20 @@ theorem snapshot_restore_is_cno :
def isIdempotent (op : FsOp) : Prop :=
∀ fs, op (op fs) = op fs
-/-- mkdir is idempotent (but not CNO). -/
-theorem mkdir_idempotent (p : Path) :
- isIdempotent (fun fs => mkdir p fs) := by
- intro fs
- unfold mkdir
- by_cases h : dir_exists p fs = true
- · rw [if_pos h, if_pos h]
- · rw [if_neg h]
- have h_dir : dir_exists p (FileEntry.Directory p [] default_meta :: fs) = true := by
- show ((p == p) || dir_exists p fs) = true
- rw [beq_self_eq_true p]
- rfl
- rw [if_pos h_dir]
-
-/-- The unconditional law `∀ p fs, rmdir p (mkdir p fs) = fs` is refuted by
- `mkdir_idempotent` and `mkdir_not_identity`. -/
+/-- mkdir is idempotent (but not CNO). Coq proves this Lemma on its concrete
+ model; over opaque operations it has to be assumed. Consistent with the
+ conditional `mkdir_rmdir_inverse`: `mkdir p fs` has a directory at `p`,
+ so the inverse law does not apply to it. -/
+-- AXIOM: mkdir_idempotent; mirrors the Coq Lemma (proved on the concrete model); §(c) per docs/proof-debt.md.
+axiom mkdir_idempotent (p : Path) :
+ isIdempotent (fun fs => mkdir p fs)
+
+/-- The unconditional law `∀ p fs, rmdir p (mkdir p fs) = fs` — the former
+ statement of `mkdir_rmdir_inverse` — is refuted by `mkdir_idempotent` and
+ `mkdir_not_identity` alone: on `mkdir p fs` the second `mkdir` is a no-op,
+ so the law would force `mkdir p fs = fs`. This is the derivation that
+ made issue #125's `False` proof go through; it is now a theorem about the
+ old statement instead of a contradiction in the axioms. -/
theorem unconditional_mkdir_rmdir_inverse_is_false :
¬ ∀ (p : Path) (fs : Filesystem), rmdir p (mkdir p fs) = fs := by
intro law
@@ -605,7 +365,11 @@ theorem unconditional_mkdir_rmdir_inverse_is_false :
rw [h2, law p fs] at h1
exact hne h1.symm
-/-- Idempotent does NOT imply CNO. -/
+/-- Idempotent does NOT imply CNO.
+ Proof: destructure mkdir_not_identity to get a specific (p, fs) where
+ mkdir p fs ≠ fs, then exhibit `fun fs => mkdir p fs` as the witness.
+ It is idempotent (mkdir_idempotent), but it cannot be a CNO: if it were,
+ applying it to fs would leave fs unchanged, contradicting h_neq. -/
example : ∃ op : FsOp, isIdempotent op ∧ ¬ isFsCNO op := by
obtain ⟨p, fs, h_neq⟩ := mkdir_not_identity
exists (fun fs' => mkdir p fs')