FA-53671 / Accessibility interaction semantics / Open access
Images without an alt fail to expose a title fallback · case 01
Images without an alt fail to expose a title fallback.
ROOT CAUSE
The title fallback decision in the image alternative adapter violates its declared contract.
VERIFIED REPAIR
Apply the exact contract at the title fallback decision: if title: return [title.strip(' '),'title',status]
Unsuccessful approach: The local repair substitutes if title and loaded: return [title.strip(' '),'title',status] but still mishandles the regression fixtures.
Case contract
Input [decorative,interactive,alt,title,caption,ocr,ocr_consent,filename,loaded,broken]. None alt means absent; empty alt is intentional. Interactive decorative images retain semantics. Source priority is explicit alt (even empty), nonempty caption, title, consented OCR when loaded and not broken; filename is never spoken. Decorative noninteractive images return None. Return [text,source,status] where status is broken, loading, ready with broken highest priority. Normalization strips only surrounding ASCII spaces; OCR is marked inferred.
Why this case matters
Offline accessibility bridge model with explicit policy; useful for testing semantic API adapters independently of browser implementations.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(data):
decorative,interactive,alt,title,caption,ocr,consent,filename,loaded,broken = data
if decorative and not interactive: return None
status = 'broken' if broken else ('ready' if loaded else 'loading')
if alt is not None: return [alt.strip(' '),'alt',status]
if caption: return [caption.strip(' '),'caption',status]
if False: return [title.strip(' '),'title',status]
if consent and loaded and not broken and ocr: return [ocr.strip(' '),'inferred',status]
return ['',None,status]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('decorative inert', solve([True,False,None,'title','caption','ocr',True,'file',True,False],), None)
check('decorative interactive', solve([True,True,' Label '+str(N)+' ','title','caption','ocr',True,'file',True,False],), ['Label '+str(N),'alt','ready'])
check('intentional empty', solve([False,False,'','title','caption','ocr',True,'file',True,False],), ['','alt','ready'])
check('caption precedence', solve([False,False,None,'title',' caption ','ocr',True,'file',True,False],), ['caption','caption','ready'])
check('title only', solve([False,False,None,' title ','','ocr',True,'file',False,False],), ['title','title','loading'])
check('OCR inferred', solve([False,False,None,'','',' OCR '+str(N)+' ',True,'file',True,False],), ['OCR '+str(N),'inferred','ready'])
check('unconsented OCR', solve([False,False,None,'','','ocr',False,'file',True,False],), ['',None,'ready'])
check('loading OCR', solve([False,False,None,'','','ocr',True,'file',False,False],), ['',None,'loading'])
check('broken OCR', solve([False,False,None,'','','ocr',True,'file',True,True],), ['',None,'broken'])
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 |
|---|---|---|---|
| decorative inert | None | None | Passed |
| decorative interactive | ['Label 1', 'alt', 'ready'] | ['Label 1', 'alt', 'ready'] | Passed |
| intentional empty | ['', 'alt', 'ready'] | ['', 'alt', 'ready'] | Passed |
| caption precedence | ['caption', 'caption', 'ready'] | ['caption', 'caption', 'ready'] | Passed |
| title only | ['', None, 'loading'] | ['title', 'title', 'loading'] | Failed |
| OCR inferred | ['OCR 1', 'inferred', 'ready'] | ['OCR 1', 'inferred', 'ready'] | Passed |
| unconsented OCR | ['', None, 'ready'] | ['', None, 'ready'] | Passed |
| loading OCR | ['', None, 'loading'] | ['', None, 'loading'] | Passed |
| broken OCR | ['', None, 'broken'] | ['', None, 'broken'] | Passed |
SHA-256 / 64a6837937615592311bece5732ddcd1bd62cc515e3791eb476cfdc39afdf567
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(data):
decorative,interactive,alt,title,caption,ocr,consent,filename,loaded,broken = data
if decorative and not interactive: return None
status = 'broken' if broken else ('ready' if loaded else 'loading')
if alt is not None: return [alt.strip(' '),'alt',status]
if caption: return [caption.strip(' '),'caption',status]
if title and loaded: return [title.strip(' '),'title',status]
if consent and loaded and not broken and ocr: return [ocr.strip(' '),'inferred',status]
return ['',None,status]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('decorative inert', solve([True,False,None,'title','caption','ocr',True,'file',True,False],), None)
check('decorative interactive', solve([True,True,' Label '+str(N)+' ','title','caption','ocr',True,'file',True,False],), ['Label '+str(N),'alt','ready'])
check('intentional empty', solve([False,False,'','title','caption','ocr',True,'file',True,False],), ['','alt','ready'])
check('caption precedence', solve([False,False,None,'title',' caption ','ocr',True,'file',True,False],), ['caption','caption','ready'])
check('title only', solve([False,False,None,' title ','','ocr',True,'file',False,False],), ['title','title','loading'])
check('OCR inferred', solve([False,False,None,'','',' OCR '+str(N)+' ',True,'file',True,False],), ['OCR '+str(N),'inferred','ready'])
check('unconsented OCR', solve([False,False,None,'','','ocr',False,'file',True,False],), ['',None,'ready'])
check('loading OCR', solve([False,False,None,'','','ocr',True,'file',False,False],), ['',None,'loading'])
check('broken OCR', solve([False,False,None,'','','ocr',True,'file',True,True],), ['',None,'broken'])
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 |
|---|---|---|---|
| decorative inert | None | None | Passed |
| decorative interactive | ['Label 1', 'alt', 'ready'] | ['Label 1', 'alt', 'ready'] | Passed |
| intentional empty | ['', 'alt', 'ready'] | ['', 'alt', 'ready'] | Passed |
| caption precedence | ['caption', 'caption', 'ready'] | ['caption', 'caption', 'ready'] | Passed |
| title only | ['', None, 'loading'] | ['title', 'title', 'loading'] | Failed |
| OCR inferred | ['OCR 1', 'inferred', 'ready'] | ['OCR 1', 'inferred', 'ready'] | Passed |
| unconsented OCR | ['', None, 'ready'] | ['', None, 'ready'] | Passed |
| loading OCR | ['', None, 'loading'] | ['', None, 'loading'] | Passed |
| broken OCR | ['', None, 'broken'] | ['', None, 'broken'] | Passed |
SHA-256 / cbf51b522d47040765200cae7fc86199ffc08dea0ede17ef3e841b6ced184f02
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(data):
decorative,interactive,alt,title,caption,ocr,consent,filename,loaded,broken = data
if decorative and not interactive: return None
status = 'broken' if broken else ('ready' if loaded else 'loading')
if alt is not None: return [alt.strip(' '),'alt',status]
if caption: return [caption.strip(' '),'caption',status]
if title: return [title.strip(' '),'title',status]
if consent and loaded and not broken and ocr: return [ocr.strip(' '),'inferred',status]
return ['',None,status]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('decorative inert', solve([True,False,None,'title','caption','ocr',True,'file',True,False],), None)
check('decorative interactive', solve([True,True,' Label '+str(N)+' ','title','caption','ocr',True,'file',True,False],), ['Label '+str(N),'alt','ready'])
check('intentional empty', solve([False,False,'','title','caption','ocr',True,'file',True,False],), ['','alt','ready'])
check('caption precedence', solve([False,False,None,'title',' caption ','ocr',True,'file',True,False],), ['caption','caption','ready'])
check('title only', solve([False,False,None,' title ','','ocr',True,'file',False,False],), ['title','title','loading'])
check('OCR inferred', solve([False,False,None,'','',' OCR '+str(N)+' ',True,'file',True,False],), ['OCR '+str(N),'inferred','ready'])
check('unconsented OCR', solve([False,False,None,'','','ocr',False,'file',True,False],), ['',None,'ready'])
check('loading OCR', solve([False,False,None,'','','ocr',True,'file',False,False],), ['',None,'loading'])
check('broken OCR', solve([False,False,None,'','','ocr',True,'file',True,True],), ['',None,'broken'])
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 |
|---|---|---|---|
| decorative inert | None | None | Passed |
| decorative interactive | ['Label 1', 'alt', 'ready'] | ['Label 1', 'alt', 'ready'] | Passed |
| intentional empty | ['', 'alt', 'ready'] | ['', 'alt', 'ready'] | Passed |
| caption precedence | ['caption', 'caption', 'ready'] | ['caption', 'caption', 'ready'] | Passed |
| title only | ['title', 'title', 'loading'] | ['title', 'title', 'loading'] | Passed |
| OCR inferred | ['OCR 1', 'inferred', 'ready'] | ['OCR 1', 'inferred', 'ready'] | Passed |
| unconsented OCR | ['', None, 'ready'] | ['', None, 'ready'] | Passed |
| loading OCR | ['', None, 'loading'] | ['', None, 'loading'] | Passed |
| broken OCR | ['', None, 'broken'] | ['', None, 'broken'] | Passed |
SHA-256 / 4a3a0b6358cafdc59ac1ed787bc5ac36978c5f0b74fe66a599ef6f48a95f4639
Verification & scope
Stipulated finite adapter contract, not a claim of platform accessibility or ARIA conformance. 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:45:41.242301+00:00.
Case digest / 1eeb64b777eb0c00e15d1221a28b605aa774d85a26d9f38e2bfe9431f2faa837