FA-11541 / Filesystem semantics / Open access
Unlinking the last name invalidates an open file · case 01
Unlinking the last name invalidates an open file.
ROOT CAUSE
Object lifetime is equated with directory-name lifetime.
VERIFIED REPAIR
Retain the inode while either a link or an open descriptor refers to it.
Unsuccessful approach: Keeping files only when multiple descriptors exist loses the single remaining descriptor.
Case contract
Deterministic in-memory filesystem model. Given initial positive link count and nonnegative open count, remove one link; return [remaining links, whether inode survives].
Why this case matters
An offline semantic experiment for file API clients; it models only the stated operations, not a complete operating system.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(links, opens):
return [links-1, links-1 > 0]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('one descriptor retains last unlinked inode', solve(1,1), [0,True])
check('several descriptors retain inode', solve(1,N+1), [0,True])
check('last reference removed', solve(1,0), [0,False])
check('another name retains inode', solve(N+1,0), [N,True])
check('names and descriptor retain inode', solve(N+1,1), [N,True])
check('two links no descriptors', solve(2,0), [1,True])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| one descriptor retains last unlinked inode | [0, False] | [0, True] | Failed |
| several descriptors retain inode | [0, False] | [0, True] | Failed |
| last reference removed | [0, False] | [0, False] | Passed |
| another name retains inode | [1, True] | [1, True] | Passed |
| names and descriptor retain inode | [1, True] | [1, True] | Passed |
| two links no descriptors | [1, True] | [1, True] | Passed |
SHA-256 / f87aca1496b8864b6794e273519740bc1fcfcf28928d084311f89da6f125953a
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(links, opens):
return [links-1, links-1 > 0 or opens > 1]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('one descriptor retains last unlinked inode', solve(1,1), [0,True])
check('several descriptors retain inode', solve(1,N+1), [0,True])
check('last reference removed', solve(1,0), [0,False])
check('another name retains inode', solve(N+1,0), [N,True])
check('names and descriptor retain inode', solve(N+1,1), [N,True])
check('two links no descriptors', solve(2,0), [1,True])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| one descriptor retains last unlinked inode | [0, False] | [0, True] | Failed |
| several descriptors retain inode | [0, True] | [0, True] | Passed |
| last reference removed | [0, False] | [0, False] | Passed |
| another name retains inode | [1, True] | [1, True] | Passed |
| names and descriptor retain inode | [1, True] | [1, True] | Passed |
| two links no descriptors | [1, True] | [1, True] | Passed |
SHA-256 / 63c3de083da55b94dc637cecee8b5a7e56b8fe5bd7c4a2650d5e5f0e893dc03e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(links, opens):
return [links-1, links-1 > 0 or opens > 0]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('one descriptor retains last unlinked inode', solve(1,1), [0,True])
check('several descriptors retain inode', solve(1,N+1), [0,True])
check('last reference removed', solve(1,0), [0,False])
check('another name retains inode', solve(N+1,0), [N,True])
check('names and descriptor retain inode', solve(N+1,1), [N,True])
check('two links no descriptors', solve(2,0), [1,True])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| one descriptor retains last unlinked inode | [0, True] | [0, True] | Passed |
| several descriptors retain inode | [0, True] | [0, True] | Passed |
| last reference removed | [0, False] | [0, False] | Passed |
| another name retains inode | [1, True] | [1, True] | Passed |
| names and descriptor retain inode | [1, True] | [1, True] | Passed |
| two links no descriptors | [1, True] | [1, True] | Passed |
SHA-256 / 55dd7af11983bd946c314900bb646f21f249c4b9e976a8e3ad7a75008cb8c286
Verification & scope
This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:38:48.769769+00:00.
Case digest / 49395aab9aedcd6cc2136c4c420f80fe006d82405506d874994b96a107bfe26e