FA-11586 / Filesystem semantics / Open access
A dangling symbolic link disappears from metadata results · case 01
A dangling symbolic link disappears from metadata results.
ROOT CAUSE
Entry inspection follows a symbolic link before reporting the entry type.
VERIFIED REPAIR
Report the named entry itself for lstat-style inspection.
Unsuccessful approach: Falling back to link type only for missing targets still misreports links to existing files.
Case contract
Deterministic in-memory filesystem model. entries maps flat names to [kind,target], where kind is file,dir,or link and target is used only for links. Return named entry kind or missing without following any link.
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(entries, name):
entry=entries.get(name)
if entry is None: return 'missing'
if entry[0]=='link': return entries.get(entry[1],['missing'])[0]
return entry[0]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
name='link'+str(N)
check('dangling link exists',solve({name:['link','absent']},name),'link')
check('link to file remains link',solve({name:['link','f'],'f':['file',None]},name),'link')
check('link to directory remains link',solve({name:['link','d'],'d':['dir',None]},name),'link')
check('ordinary file',solve({'f':['file',None]},'f'),'file')
check('missing entry',solve({},name),'missing')
check('self link does not recurse',solve({name:['link',name]},name),'link')
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 |
|---|---|---|---|
| dangling link exists | missing | link | Failed |
| link to file remains link | file | link | Failed |
| link to directory remains link | dir | link | Failed |
| ordinary file | file | file | Passed |
| missing entry | missing | missing | Passed |
| self link does not recurse | link | link | Passed |
SHA-256 / a1ef757f183702776dbaf0cabff756246b7167a47ad5f09912f5e02a5120fe61
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(entries, name):
entry=entries.get(name)
if entry is None: return 'missing'
if entry[0]=='link': return entries.get(entry[1],['link'])[0]
return entry[0]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
name='link'+str(N)
check('dangling link exists',solve({name:['link','absent']},name),'link')
check('link to file remains link',solve({name:['link','f'],'f':['file',None]},name),'link')
check('link to directory remains link',solve({name:['link','d'],'d':['dir',None]},name),'link')
check('ordinary file',solve({'f':['file',None]},'f'),'file')
check('missing entry',solve({},name),'missing')
check('self link does not recurse',solve({name:['link',name]},name),'link')
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 |
|---|---|---|---|
| dangling link exists | link | link | Passed |
| link to file remains link | file | link | Failed |
| link to directory remains link | dir | link | Failed |
| ordinary file | file | file | Passed |
| missing entry | missing | missing | Passed |
| self link does not recurse | link | link | Passed |
SHA-256 / db377e0d2869f9e0377514b87cbc80fe62c2c6c7e56ea5b33d3e290871624341
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(entries, name):
return entries.get(name,['missing'])[0]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
name='link'+str(N)
check('dangling link exists',solve({name:['link','absent']},name),'link')
check('link to file remains link',solve({name:['link','f'],'f':['file',None]},name),'link')
check('link to directory remains link',solve({name:['link','d'],'d':['dir',None]},name),'link')
check('ordinary file',solve({'f':['file',None]},'f'),'file')
check('missing entry',solve({},name),'missing')
check('self link does not recurse',solve({name:['link',name]},name),'link')
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 |
|---|---|---|---|
| dangling link exists | link | link | Passed |
| link to file remains link | link | link | Passed |
| link to directory remains link | link | link | Passed |
| ordinary file | file | file | Passed |
| missing entry | missing | missing | Passed |
| self link does not recurse | link | link | Passed |
SHA-256 / 7d16a7f59866273478f2517d07da0bc1a85acb1eaa62b11fc99e8daa2a25fddd
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:49.248546+00:00.
Case digest / 29e4956cb249e6e5806bbbde1e28c6b2ca9187c30c4f15f1ec56b2ea8ba7834a