|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 3 | += Valence Shell — Crash-Consistency Theory |
| 4 | +Jonathan Jewell (hyperpolymath) |
| 5 | +:toc: left |
| 6 | +:toc-title: Contents |
| 7 | +:doctype: article |
| 8 | +:icons: font |
| 9 | +:sectnums: |
| 10 | +:sectanchors: |
| 11 | + |
| 12 | +This document is the *theory companion* to the Lean 4 keystone proof |
| 13 | +`proofs/lean4/CrashConsistency.lean`. It states the gap, the assumption |
| 14 | +the keystone makes explicit, the theorem it proves, and the four |
| 15 | +remaining frontiers that are deferred to separate Tier-S issues. |
| 16 | + |
| 17 | +It closes the keystone slice of `valence-shell#45` |
| 18 | +("Filesystem-theory + practice gap analysis"). It does *not* claim to |
| 19 | +solve crash-consistency in general — it solidifies one foundational |
| 20 | +assumption so that the larger story can be told without hand-waving. |
| 21 | + |
| 22 | +See also: `docs/PROOF-OPEN-FRONTIER.adoc` (Tier S/A/B/C catalogue), |
| 23 | +`docs/PROOF-NARRATIVE.adoc` (what is proven today), |
| 24 | +`docs/POSIX-2024-NOTES.md` (POSIX edges that interact with §5.b). |
| 25 | + |
| 26 | +== The gap |
| 27 | + |
| 28 | +The Lean 4, Coq, Agda, Idris 2 and Isabelle models that valence-shell |
| 29 | +ships today share a single common shape: a `Filesystem` is a *pure |
| 30 | +function* from `Path` to `Option FSNode`, and an operation |
| 31 | +(`mkdir`, `rmdir`, …) is a *pure function* from `Filesystem` to |
| 32 | +`Filesystem`. This is a perfectly normal small-step / big-step |
| 33 | +operational semantics — it is the same model used by FSCQ, Yggdrasil |
| 34 | +and the Coq side of COGENT. |
| 35 | + |
| 36 | +The problem is that *real* state-mutating systems can be interrupted |
| 37 | +*during* an operation. Power can fail, a SIGKILL can land, a |
| 38 | +container runtime can pull the rug. The pure model assumes that every |
| 39 | +operation is atomic — that the world only ever observes the pre-state |
| 40 | +or the post-state. Nothing in the current proof tree *justifies* |
| 41 | +that assumption. |
| 42 | + |
| 43 | +This is the textbook *Fscq theorem* setup (Chen et al. 2015): the |
| 44 | +theorems you can prove against a pure model are only as strong as the |
| 45 | +atomicity-within-op assumption you make. Without an explicit |
| 46 | +statement, every higher-level theorem (reversibility, MAA, GDPR |
| 47 | +irreversibility) implicitly depends on it. |
| 48 | + |
| 49 | +== The atomicity-within-op assumption |
| 50 | + |
| 51 | +The keystone makes the assumption *explicit and minimal*: |
| 52 | + |
| 53 | +[quote] |
| 54 | +____ |
| 55 | +For each modelled `FilesystemOp`, an external observer (the next |
| 56 | +process to look at the filesystem after an interruption) sees *either* |
| 57 | +the pre-operation state *or* the post-operation state — never a third |
| 58 | +"partial" state. |
| 59 | +____ |
| 60 | + |
| 61 | +This is **not** the claim "no real filesystem ever exposes torn |
| 62 | +state". It is the claim "*for the operations valence-shell models at |
| 63 | +the proof layer*, the underlying filesystem's atomicity guarantee is |
| 64 | +strong enough that the two-point model is faithful". The justification |
| 65 | +varies by backing filesystem: |
| 66 | + |
| 67 | +.How the assumption is discharged by real filesystems |
| 68 | +[cols="1,3",options="header"] |
| 69 | +|=== |
| 70 | +| Backing filesystem | How atomicity-within-op is achieved |
| 71 | +| **ext4 `data=journal`** mode |
| 72 | +| Both metadata *and* data are journalled. A `mkdir(2)` is replayed |
| 73 | +or rolled back as a single journal transaction — the post-crash |
| 74 | +observer sees one of the two endpoints. Faithful to the model. |
| 75 | + |
| 76 | +| **ZFS** (and Btrfs in equivalent COW mode) |
| 77 | +| Copy-on-write at the block layer. An interrupted `mkdir` leaves the |
| 78 | +old uberblock pointing at the pre-state tree; the new tree is only |
| 79 | +linked in atomically at commit. Faithful to the model. |
| 80 | + |
| 81 | +| **ext4 default** (`data=ordered`) |
| 82 | +| *Metadata-only* journal. A `mkdir(2)` is still atomic at the |
| 83 | +directory-entry level — the entry either appears or doesn't. The |
| 84 | +keystone is faithful for `mkdir` and `rmdir`; it is *not* faithful for |
| 85 | +operations that mutate file *contents* (those need a separate slice). |
| 86 | + |
| 87 | +| **APFS / NTFS / arbitrary network FS** |
| 88 | +| Vendor-dependent. Where vendor documentation does not guarantee |
| 89 | +single-op atomicity for the operation in question, the model is not |
| 90 | +faithful and the higher-level theorem is correspondingly weakened. |
| 91 | +|=== |
| 92 | + |
| 93 | +The keystone is therefore *conditional*: every theorem that derives |
| 94 | +from it inherits the precondition "the backing filesystem provides |
| 95 | +single-op atomicity for this op". Naming the assumption explicitly |
| 96 | +lets downstream proofs cite it instead of silently assuming it. |
| 97 | + |
| 98 | +== Keystone theorem |
| 99 | + |
| 100 | +Stated informally: |
| 101 | + |
| 102 | +[quote] |
| 103 | +____ |
| 104 | +*`crash_atomic_within_op_mkdir`* — for any pre-state filesystem `fs`, |
| 105 | +any path `p`, and any crash point `cp` observable to an external |
| 106 | +process, the filesystem the external process sees is either equal to |
| 107 | +`fs` (pre-state) or equal to `mkdir p fs` (post-state). No third state |
| 108 | +is observable. |
| 109 | +____ |
| 110 | + |
| 111 | +The Lean 4 mechanisation lives in |
| 112 | +`proofs/lean4/CrashConsistency.lean`. The proof is trivial by case |
| 113 | +analysis on the `CrashPoint` inductive — the value is in the modelling, |
| 114 | +not in the proof labour. The keystone introduces *zero new axioms* |
| 115 | +(verified via `#print axioms`). |
| 116 | + |
| 117 | +A corollary, `crash_state_no_third_value`, states the same fact |
| 118 | +contrapositively: if a filesystem `g` is distinct from both the pre- |
| 119 | +and post-states, then no crash point of an `Op.mkdir p` produces `g`. |
| 120 | + |
| 121 | +The same shape extends to the other operations valence-shell models |
| 122 | +(`rmdir`, file ops, RMOs, …) by extending the `Op` inductive. The |
| 123 | +keystone deliberately covers only `mkdir` so that the modelling story |
| 124 | +can be reviewed independently of the operational coverage story. |
| 125 | + |
| 126 | +== Frontiers (deferred) |
| 127 | + |
| 128 | +The four remaining frontiers are the parts of crash-consistency *not* |
| 129 | +covered by the keystone. Each warrants its own Tier-S issue. |
| 130 | + |
| 131 | +=== Frontier A — Concurrency / linearizability under multi-process |
| 132 | + |
| 133 | +The keystone says nothing about *two* concurrent vsh processes, or vsh |
| 134 | +plus an external editor, or vsh inside a container with sibling |
| 135 | +processes on the same mount. The Lean 4 model is purely sequential. |
| 136 | + |
| 137 | +**Proposed approach.** Use a Herlihy-Wing-style linearizability |
| 138 | +specification. Each `FilesystemOp` is given a linearization point |
| 139 | +(usually the journal-commit moment on ext4, the COW commit on ZFS). |
| 140 | +The proof obligation is: for every interleaving of two processes' |
| 141 | +operations, there exists a sequential schedule that produces the same |
| 142 | +observable history. In Lean 4 this is most natural over an explicit |
| 143 | +operation log + per-inode locks; the first tractable slice is |
| 144 | +"non-overlapping-path operations linearize trivially under per-path |
| 145 | +locks", which is the same first slice already identified in |
| 146 | +`PROOF-OPEN-FRONTIER.adoc#F-4`. |
| 147 | + |
| 148 | +The frontier interacts with the keystone: a concurrent crash is a |
| 149 | +crash *plus* a non-trivial schedule; the linearizability story has to |
| 150 | +be layered *on top of* the atomicity-within-op story, not in place of |
| 151 | +it. |
| 152 | + |
| 153 | +=== Frontier B — POSIX 2024 edges (sub-second mtime, file-clone, copy_file_range) |
| 154 | + |
| 155 | +POSIX 2024 (IEEE Std 1003.1-2024) introduces three classes of operation |
| 156 | +that the current valence-shell model does not cover, each with its own |
| 157 | +crash-consistency wrinkle: |
| 158 | + |
| 159 | +1. **Sub-second mtime** (`struct timespec` in `stat`). A crash between |
| 160 | + the mtime write and the data write can leave the mtime *ahead* of |
| 161 | + the data. The keystone model has no notion of time, so the |
| 162 | + atomicity-within-op assumption needs to be stated against a |
| 163 | + `(Filesystem, Clock)` pair. |
| 164 | + |
| 165 | +2. **`copy_file_range(2)`** as a single syscall. Linux implements it |
| 166 | + as either a reflink (atomic) or a fallback loop (non-atomic). The |
| 167 | + keystone is faithful in the reflink branch and faithful only at the |
| 168 | + *chunk* boundary in the fallback branch — the modelling needs to |
| 169 | + expose that distinction. |
| 170 | + |
| 171 | +3. **`ioctl(FICLONE)` / `FICLONERANGE`**. Reflink semantics — a crash |
| 172 | + leaves either the pre-state or the post-state of the *destination*, |
| 173 | + while the source is unaffected. This is a clean fit for the |
| 174 | + keystone shape but needs to be modelled as a *two-filesystem* |
| 175 | + transition (source + destination), not a single one. |
| 176 | + |
| 177 | +**Proposed approach.** Extend the `Op` inductive with `clone`, |
| 178 | +`copy_range`, and `touch_mtime` constructors, and extend `Filesystem` |
| 179 | +to a record `{ tree : Path → Option FSNode, clock : Time }`. Re-prove |
| 180 | +the keystone for each new constructor; the case-analysis structure |
| 181 | +carries over. See `docs/POSIX-2024-NOTES.md` for the practical-side |
| 182 | +notes on these calls. |
| 183 | + |
| 184 | +=== Frontier C — Idris 2 via typed-WASM crash boundary |
| 185 | + |
| 186 | +valence-shell's Idris 2 proof tree is intended to be extractable to |
| 187 | +typed-WASM (see `docs/SEAM_ANALYSIS_AND_IDRIS2_PLAN.md` and the |
| 188 | +typed-wasm sibling repo). The crash story at the WASM boundary is |
| 189 | +*different* from the crash story at the syscall boundary: |
| 190 | + |
| 191 | +- The WASM host can terminate the module mid-instruction; the host |
| 192 | + observes the linear memory at the termination point. |
| 193 | +- The WASM module has no syscall-level atomicity guarantee; whatever |
| 194 | + atomicity exists has to come from the host's import-function |
| 195 | + contracts. |
| 196 | + |
| 197 | +**Proposed approach.** Define a `WasmCrashPoint` inductive parallel to |
| 198 | +`CrashPoint` but indexed by WASM instruction boundaries. Prove that |
| 199 | +for each `FilesystemOp` *as compiled to typed-WASM*, the only |
| 200 | +host-observable crash points coincide with the host's import-function |
| 201 | +boundaries — and that the import-function contracts then provide the |
| 202 | +atomicity story. This is a two-level proof: the Lean / Idris layer |
| 203 | +proves the source-level keystone (this document), and the |
| 204 | +typed-WASM-layer proof shows the compilation preserves it. Gates on |
| 205 | +typed-wasm proposal 0003 (cross-module L13). |
| 206 | + |
| 207 | +=== Frontier D — Hardware-level (power loss, drive cache, write barriers) |
| 208 | + |
| 209 | +Below the filesystem there is the *block device*. A `mkdir(2)` that |
| 210 | +the kernel reports as committed may still be sitting in the drive's |
| 211 | +volatile write cache when power fails. The keystone assumption |
| 212 | +*presupposes* that write barriers (`fsync`, `fdatasync`, `FUA`) have |
| 213 | +done their job; if they haven't, the post-state observable to the next |
| 214 | +boot is neither pre- nor post- of the keystone — it is a hardware-level |
| 215 | +torn state. |
| 216 | + |
| 217 | +**Proposed approach.** This frontier is *not* a proof obligation in |
| 218 | +the same sense as A–C — it is a *modelling boundary*. The |
| 219 | +recommendation is: |
| 220 | + |
| 221 | +1. Document the assumption explicitly: "every modelled `FilesystemOp` |
| 222 | + is followed by a barrier sufficient to make its effects durable |
| 223 | + before any subsequent op is started". |
| 224 | +2. Annotate the operations in `proofs/lean4/FilesystemModel.lean` with |
| 225 | + the barrier each one assumes (most are `fsync`-on-the-parent-dir for |
| 226 | + directory ops; file ops need `fsync`-on-the-file *and* on the |
| 227 | + parent). |
| 228 | +3. Cross-check against the Rust implementation: `impl/rust/` should |
| 229 | + issue those barriers, and an audit ticket should track any gap. |
| 230 | + |
| 231 | +The modelling boundary is intentional: a proof system cannot promise |
| 232 | +what the firmware will do. Stating the boundary explicitly is the |
| 233 | +honest move. |
| 234 | + |
| 235 | +== L4 (dyadic-resource) cross-link |
| 236 | + |
| 237 | +Crash-consistency is *fundamentally* an L4 (dyadic-resource) shape: a |
| 238 | +power-loss event is the canonical interruption of a linear resource. |
| 239 | +The pre-state is the resource before consumption; the post-state is |
| 240 | +the resource after consumption; the keystone's claim that no third |
| 241 | +state is observable is exactly the linear-resource discipline applied |
| 242 | +to an externally-observable filesystem. |
| 243 | + |
| 244 | +Intended future cross-link: `echo-types::EchoOFSUnivF5` or whichever |
| 245 | +dyadic-layer construct ends up canonical there. The `Op` inductive in |
| 246 | +the keystone can be re-stated as a dyadic resource (consumed at |
| 247 | +linearization, produced at commit) without changing the proof |
| 248 | +structure. This is recorded in `PROOF-NEEDS.md` as |
| 249 | +RECORD-AS-NOT-RELEVANT for this slice and as future-link for the L4 |
| 250 | +slice. |
| 251 | + |
| 252 | +== Bibliography (informal) |
| 253 | + |
| 254 | +- Chen, H., Ziegler, D., Chajed, T., Chlipala, A., Kaashoek, M.F., |
| 255 | + Zeldovich, N. (2015). *Using Crash Hoare Logic for Certifying the |
| 256 | + FSCQ File System.* SOSP '15. — the FSCQ paper; the textbook |
| 257 | + example of why pure-model atomicity-within-op assumptions need to be |
| 258 | + named. |
| 259 | +- Pillai, T.S., Chidambaram, V., Alagappan, R., Al-Kiswany, S., |
| 260 | + Arpaci-Dusseau, A.C., Arpaci-Dusseau, R.H. (2014). *All File Systems |
| 261 | + Are Not Created Equal: On the Complexity of Crafting |
| 262 | + Crash-Consistent Applications.* OSDI '14. — ALICE-style |
| 263 | + crash-state enumeration, motivates Frontier A. |
| 264 | +- Sigurbjarnarson, H., Bornholt, J., Torlak, E., Wang, X. (2016). |
| 265 | + *Push-Button Verification of File Systems via Crash Refinement.* |
| 266 | + OSDI '16. — Yggdrasil; pure-model crash refinement, closest in |
| 267 | + spirit to the keystone shape used here. |
| 268 | +- O'Connor, L., Chen, Z., Susarla, S., Klein, G., Keller, G., Murray, |
| 269 | + T., Heiser, G. (2016). *Refinement through Restraint: Bringing Down |
| 270 | + the Cost of Verification.* ICFP '16. — COGENT; linear-types story |
| 271 | + that motivates the L4 cross-link in §6. |
| 272 | + |
| 273 | +== Status |
| 274 | + |
| 275 | +- **Keystone (this document + Lean 4 proof)**: shipped, zero axiom |
| 276 | + dependencies, `lake build` green on Lean 4 v4.12.0. |
| 277 | +- **Frontiers A–D**: deferred to separate Tier-S issues, filed at |
| 278 | + closure of `valence-shell#45`. |
| 279 | +- **Cross-link to echo-types L4**: future-link recorded in |
| 280 | + `PROOF-NEEDS.md`; no implementation work here. |
0 commit comments