FAILURE MAP
← Case archive

FA-11431 / Accessibility interaction semantics / Open access

A nested header is exposed as a page-wide banner landmark · case 01

Section-local headers and footers appear in the page landmark inventory, or unnamed sections create noisy regions.

Verified by executionVariant 1 · 10 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

HTML tag names are mapped to landmark roles without the required naming and ancestor context.

VERIFIED REPAIR

Require names for form/section landmarks and inspect the complete ancestor chain for header/footer sectioning context.

Unsuccessful approach: Checking only the nearest ancestor still mistakes a header wrapped inside an article for a page-wide banner.

Case contract

This deliberately bounded native landmark adapter accepts tag, accessible name, and nearest-first ancestor tag names. main/nav/aside map to main/navigation/complementary. Named form/section map to form/region; empty or ASCII-whitespace-only names yield None. header/footer map to banner/contentinfo only outside every article, aside, main, nav or section ancestor. Other tags yield None. This adapter does not implement author role overrides or full HTML semantics.

Why this case matters

A controlled offline accessibility-data model. It isolates the stated contract; it does not simulate browser accessibility APIs or claim full ARIA conformance.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(tag, name, ancestors):
    return {'main':'main','nav':'navigation','aside':'complementary','form':'form','section':'region','header':'banner','footer':'contentinfo'}.get(tag)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('deep local header', solve('header', str(N), ['div']*N+['article','body']), None)
check('deep local footer', solve('footer', str(N), ['div']*N+['main','body']), None)
check('page header', solve('header', str(N), ['div']*N+['body']), 'banner')
check('page footer', solve('footer', '', ['body']), 'contentinfo')
check('unnamed section', solve('section', ' '*N, ['body']), None)
check('named section', solve('section', 'Results '+str(N), ['body']), 'region')
check('unnamed form', solve('form', '', []), None)
check('named form', solve('form', str(N), []), 'form')
check('unconditional navigation', solve('nav', '', ['article']), 'navigation')
check('nonlandmark', solve('div', str(N), []), None)
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 fixtureActualExpectedOutcome
deep local headerbannerNoneFailed
deep local footercontentinfoNoneFailed
page headerbannerbannerPassed
page footercontentinfocontentinfoPassed
unnamed sectionregionNoneFailed
named sectionregionregionPassed
unnamed formformNoneFailed
named formformformPassed
unconditional navigationnavigationnavigationPassed
nonlandmarkNoneNonePassed

SHA-256 / 6912ca38d7428809618ee2705037b35dcbd781dce49cf426f2979731770cbddf

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(tag, name, ancestors):
    if tag in ('form','section') and not name.strip(''.join(map(chr, [32,9,13,10,12,11]))): return None
    if tag in ('header','footer') and ancestors and ancestors[0] in {'article','aside','main','nav','section'}: return None
    return {'main':'main','nav':'navigation','aside':'complementary','form':'form','section':'region','header':'banner','footer':'contentinfo'}.get(tag)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('deep local header', solve('header', str(N), ['div']*N+['article','body']), None)
check('deep local footer', solve('footer', str(N), ['div']*N+['main','body']), None)
check('page header', solve('header', str(N), ['div']*N+['body']), 'banner')
check('page footer', solve('footer', '', ['body']), 'contentinfo')
check('unnamed section', solve('section', ' '*N, ['body']), None)
check('named section', solve('section', 'Results '+str(N), ['body']), 'region')
check('unnamed form', solve('form', '', []), None)
check('named form', solve('form', str(N), []), 'form')
check('unconditional navigation', solve('nav', '', ['article']), 'navigation')
check('nonlandmark', solve('div', str(N), []), None)
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 fixtureActualExpectedOutcome
deep local headerbannerNoneFailed
deep local footercontentinfoNoneFailed
page headerbannerbannerPassed
page footercontentinfocontentinfoPassed
unnamed sectionNoneNonePassed
named sectionregionregionPassed
unnamed formNoneNonePassed
named formformformPassed
unconditional navigationnavigationnavigationPassed
nonlandmarkNoneNonePassed

SHA-256 / 19bbb31ca97f3b4b1b108b716aad15deecd1eaae0e467e8f094e73fe6da4a6ce

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(tag, name, ancestors):
    if tag in ('form','section') and not name.strip(''.join(map(chr, [32,9,13,10,12,11]))): return None
    if tag in ('header','footer') and any(parent in {'article','aside','main','nav','section'} for parent in ancestors): return None
    return {'main':'main','nav':'navigation','aside':'complementary','form':'form','section':'region','header':'banner','footer':'contentinfo'}.get(tag)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('deep local header', solve('header', str(N), ['div']*N+['article','body']), None)
check('deep local footer', solve('footer', str(N), ['div']*N+['main','body']), None)
check('page header', solve('header', str(N), ['div']*N+['body']), 'banner')
check('page footer', solve('footer', '', ['body']), 'contentinfo')
check('unnamed section', solve('section', ' '*N, ['body']), None)
check('named section', solve('section', 'Results '+str(N), ['body']), 'region')
check('unnamed form', solve('form', '', []), None)
check('named form', solve('form', str(N), []), 'form')
check('unconditional navigation', solve('nav', '', ['article']), 'navigation')
check('nonlandmark', solve('div', str(N), []), None)
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 fixtureActualExpectedOutcome
deep local headerNoneNonePassed
deep local footerNoneNonePassed
page headerbannerbannerPassed
page footercontentinfocontentinfoPassed
unnamed sectionNoneNonePassed
named sectionregionregionPassed
unnamed formNoneNonePassed
named formformformPassed
unconditional navigationnavigationnavigationPassed
nonlandmarkNoneNonePassed

SHA-256 / 13c3a15a938b9c49a09ccebbfc51d247b71a90654b898a60c3ecaf6a0c02b5ff

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:47.907254+00:00.

Case digest / 5cd85c5a288926421c92c64cd857edb843d5cae0da20a2f3422be1da26d9056a