FA-11411 / Accessibility interaction semantics / Open access
Table headers leak across row groups or override explicit associations · case 01
A cell announces a group header belonging to a different section or inferred headers alongside explicit ones.
ROOT CAUSE
Header inference ignores the cell’s group identity and explicit-association boundary.
VERIFIED REPAIR
When explicit IDs are present resolve only them; otherwise associate by row, column, or exact group scope.
Unsuccessful approach: Matching groups repairs inferred associations but still merges inferred headers into an explicit header list.
Case contract
Controlled table model: each header is [id,scope,index,group], scope is row, col, rowgroup or colgroup. Cell is [row,col,rowgroup,colgroup]. None explicit IDs enables inference: row/col match index, group scopes match corresponding group. An explicit list, even empty, disables inference; resolve its existing IDs in given order, deduplicating. Return IDs; fixture IDs are unique and every header has one scope.
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(headers, cell, explicit):
return [ident for ident,scope,index,group in headers if scope in ('rowgroup','colgroup') or (scope == 'row' and index == cell[0]) or (scope == 'col' and index == cell[1])]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('row group boundary', solve([('old','rowgroup',0,'old'),('current','rowgroup',0,str(N))], [N,0,str(N),'c'], None), ['current'])
check('column group boundary', solve([('old','colgroup',0,'old'),('current','colgroup',0,str(N))], [0,N,'r',str(N)], None), ['current'])
check('explicit overrides inference', solve([('inferred','row',N,'r'),('chosen','col',100,'c')], [N,0,'r','c'], ['chosen']), ['chosen'])
check('empty explicit suppresses inference', solve([('row','row',N,'r')], [N,0,'r','c'], []), [])
check('explicit order and dedup', solve([('a','row',N,'r'),('b','col',N,'c')], [N,N,'r','c'], ['b','gone','a','b']), ['b','a'])
check('row and column inference', solve([('row','row',N,'r'),('col','col',N+1,'c'),('other','row',N+1,'r')], [N,N+1,'r','c'], None), ['row','col'])
check('empty table', solve([], [N,N,'r','c'], 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| row group boundary | ['old', 'current'] | ['current'] | Failed |
| column group boundary | ['old', 'current'] | ['current'] | Failed |
| explicit overrides inference | ['inferred'] | ['chosen'] | Failed |
| empty explicit suppresses inference | ['row'] | [] | Failed |
| explicit order and dedup | ['a', 'b'] | ['b', 'a'] | Failed |
| row and column inference | ['row', 'col'] | ['row', 'col'] | Passed |
| empty table | [] | [] | Passed |
SHA-256 / 29651f45eaee63b3efd780cb496ff6fb8e3fb7bc5029cbcf92c6c0af22cdd763
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(headers, cell, explicit):
inferred = [ident for ident,scope,index,group in headers if (scope == 'row' and index == cell[0]) or (scope == 'col' and index == cell[1]) or (scope == 'rowgroup' and group == cell[2]) or (scope == 'colgroup' and group == cell[3])]
known = {h[0] for h in headers}
return list(dict.fromkeys(([x for x in explicit if x in known] if explicit is not None else []) + inferred))
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('row group boundary', solve([('old','rowgroup',0,'old'),('current','rowgroup',0,str(N))], [N,0,str(N),'c'], None), ['current'])
check('column group boundary', solve([('old','colgroup',0,'old'),('current','colgroup',0,str(N))], [0,N,'r',str(N)], None), ['current'])
check('explicit overrides inference', solve([('inferred','row',N,'r'),('chosen','col',100,'c')], [N,0,'r','c'], ['chosen']), ['chosen'])
check('empty explicit suppresses inference', solve([('row','row',N,'r')], [N,0,'r','c'], []), [])
check('explicit order and dedup', solve([('a','row',N,'r'),('b','col',N,'c')], [N,N,'r','c'], ['b','gone','a','b']), ['b','a'])
check('row and column inference', solve([('row','row',N,'r'),('col','col',N+1,'c'),('other','row',N+1,'r')], [N,N+1,'r','c'], None), ['row','col'])
check('empty table', solve([], [N,N,'r','c'], 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| row group boundary | ['current'] | ['current'] | Passed |
| column group boundary | ['current'] | ['current'] | Passed |
| explicit overrides inference | ['chosen', 'inferred'] | ['chosen'] | Failed |
| empty explicit suppresses inference | ['row'] | [] | Failed |
| explicit order and dedup | ['b', 'a'] | ['b', 'a'] | Passed |
| row and column inference | ['row', 'col'] | ['row', 'col'] | Passed |
| empty table | [] | [] | Passed |
SHA-256 / 26dc338b8d8a43ff9e772c9d6f39220f21e87d3ba57f3874d1928d46799e5c64
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(headers, cell, explicit):
known = {h[0] for h in headers}
if explicit is not None: return list(dict.fromkeys(x for x in explicit if x in known))
return [ident for ident,scope,index,group in headers if (scope == 'row' and index == cell[0]) or (scope == 'col' and index == cell[1]) or (scope == 'rowgroup' and group == cell[2]) or (scope == 'colgroup' and group == cell[3])]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('row group boundary', solve([('old','rowgroup',0,'old'),('current','rowgroup',0,str(N))], [N,0,str(N),'c'], None), ['current'])
check('column group boundary', solve([('old','colgroup',0,'old'),('current','colgroup',0,str(N))], [0,N,'r',str(N)], None), ['current'])
check('explicit overrides inference', solve([('inferred','row',N,'r'),('chosen','col',100,'c')], [N,0,'r','c'], ['chosen']), ['chosen'])
check('empty explicit suppresses inference', solve([('row','row',N,'r')], [N,0,'r','c'], []), [])
check('explicit order and dedup', solve([('a','row',N,'r'),('b','col',N,'c')], [N,N,'r','c'], ['b','gone','a','b']), ['b','a'])
check('row and column inference', solve([('row','row',N,'r'),('col','col',N+1,'c'),('other','row',N+1,'r')], [N,N+1,'r','c'], None), ['row','col'])
check('empty table', solve([], [N,N,'r','c'], 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| row group boundary | ['current'] | ['current'] | Passed |
| column group boundary | ['current'] | ['current'] | Passed |
| explicit overrides inference | ['chosen'] | ['chosen'] | Passed |
| empty explicit suppresses inference | [] | [] | Passed |
| explicit order and dedup | ['b', 'a'] | ['b', 'a'] | Passed |
| row and column inference | ['row', 'col'] | ['row', 'col'] | Passed |
| empty table | [] | [] | Passed |
SHA-256 / f98788bfecb65c5c64931527d3bea9c957155119d787da832fbeb1cc1dda18ce
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.694828+00:00.
Case digest / 37ff8bc28a44a8637545e6efd41965d58e2c47374933bb865791e6b599551dc9