FA-53426 / Accessibility interaction semantics / Open access
Negative range starts are measured from zero · case 01
Negative range starts are measured from zero.
ROOT CAUSE
The negative origin start decision in the text range bridge adapter violates its declared contract.
VERIFIED REPAIR
Apply the exact contract at the negative origin start decision: if start < 0: start += size
Unsuccessful approach: The local repair substitutes if start < -size: start = 0 but still mishandles the regression fixtures.
Case contract
Input [text,start,end,unit] where indices are measured in utf16 or codepoint units. Negative indices count backward from end, clamp to valid extent, and sort reversed endpoints. A boundary inside a surrogate pair expands outward (start down, end up). CRLF is exposed as one LF after slicing. NUL becomes replacement character. Return [exposed_text,normalized_start,normalized_end,source_units]. UTF16 offsets refer to original source, not normalized text.
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):
text,start,end,unit = data
widths = [2 if unit == 'utf16' and ord(c) > 65535 else 1 for c in text]
edges = [0]
for width in widths: edges.append(edges[-1]+width)
size = edges[-1]
if start < 0: start = 0
if end < 0: end += size
start = min(size,max(0,start))
end = min(size,max(0,end))
if start > end: start,end = end,start
left = max(i for i,x in enumerate(edges) if x <= start)
right = min(i for i,x in enumerate(edges) if x >= end)
snippet = text[left:right].replace('\r\n','\n').replace('\x00','\ufffd')
return [snippet,edges[left],edges[right],size]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('surrogate interior expands', solve(['A'*N+'😀Z',N+1,N+2,'utf16'],), ['😀',N,N+2,N+3])
check('negative start', solve(['A'*N+'BC',-2,N+2,'codepoint'],), ['BC',N,N+2,N+2])
check('negative end', solve(['A'*N+'BC',N,-1,'codepoint'],), ['B',N,N+1,N+2])
check('clamp both', solve(['AB',-20,40,'utf16'],), ['AB',0,2,2])
check('reverse', solve(['ABCDE',4,1,'codepoint'],), ['BCD',1,4,5])
check('CRLF normalization', solve(['A\r\nB',0,4,'utf16'],), ['A\nB',0,4,4])
check('NUL replacement', solve(['A\x00B',0,3,'utf16'],), ['A�B',0,3,3])
check('codepoint emoji', solve(['😀B',1,2,'codepoint'],), ['B',1,2,2])
check('empty', solve(['',0,0,'utf16'],), ['',0,0,0])
check('surrogate end expands', solve(['A😀Z',0,2,'utf16'],), ['A😀',0,3,4])
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 |
|---|---|---|---|
| surrogate interior expands | ['😀', 1, 3, 4] | ['😀', 1, 3, 4] | Passed |
| negative start | ['ABC', 0, 3, 3] | ['BC', 1, 3, 3] | Failed |
| negative end | ['B', 1, 2, 3] | ['B', 1, 2, 3] | Passed |
| clamp both | ['AB', 0, 2, 2] | ['AB', 0, 2, 2] | Passed |
| reverse | ['BCD', 1, 4, 5] | ['BCD', 1, 4, 5] | Passed |
| CRLF normalization | ['A\nB', 0, 4, 4] | ['A\nB', 0, 4, 4] | Passed |
| NUL replacement | ['A�B', 0, 3, 3] | ['A�B', 0, 3, 3] | Passed |
| codepoint emoji | ['B', 1, 2, 2] | ['B', 1, 2, 2] | Passed |
| empty | ['', 0, 0, 0] | ['', 0, 0, 0] | Passed |
| surrogate end expands | ['A😀', 0, 3, 4] | ['A😀', 0, 3, 4] | Passed |
SHA-256 / 2c24eb29778cdda6c6b63c0c3d7d9a5992fc5d990d6293356a232111e6bcb6bd
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(data):
text,start,end,unit = data
widths = [2 if unit == 'utf16' and ord(c) > 65535 else 1 for c in text]
edges = [0]
for width in widths: edges.append(edges[-1]+width)
size = edges[-1]
if start < -size: start = 0
if end < 0: end += size
start = min(size,max(0,start))
end = min(size,max(0,end))
if start > end: start,end = end,start
left = max(i for i,x in enumerate(edges) if x <= start)
right = min(i for i,x in enumerate(edges) if x >= end)
snippet = text[left:right].replace('\r\n','\n').replace('\x00','\ufffd')
return [snippet,edges[left],edges[right],size]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('surrogate interior expands', solve(['A'*N+'😀Z',N+1,N+2,'utf16'],), ['😀',N,N+2,N+3])
check('negative start', solve(['A'*N+'BC',-2,N+2,'codepoint'],), ['BC',N,N+2,N+2])
check('negative end', solve(['A'*N+'BC',N,-1,'codepoint'],), ['B',N,N+1,N+2])
check('clamp both', solve(['AB',-20,40,'utf16'],), ['AB',0,2,2])
check('reverse', solve(['ABCDE',4,1,'codepoint'],), ['BCD',1,4,5])
check('CRLF normalization', solve(['A\r\nB',0,4,'utf16'],), ['A\nB',0,4,4])
check('NUL replacement', solve(['A\x00B',0,3,'utf16'],), ['A�B',0,3,3])
check('codepoint emoji', solve(['😀B',1,2,'codepoint'],), ['B',1,2,2])
check('empty', solve(['',0,0,'utf16'],), ['',0,0,0])
check('surrogate end expands', solve(['A😀Z',0,2,'utf16'],), ['A😀',0,3,4])
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 |
|---|---|---|---|
| surrogate interior expands | ['😀', 1, 3, 4] | ['😀', 1, 3, 4] | Passed |
| negative start | ['ABC', 0, 3, 3] | ['BC', 1, 3, 3] | Failed |
| negative end | ['B', 1, 2, 3] | ['B', 1, 2, 3] | Passed |
| clamp both | ['AB', 0, 2, 2] | ['AB', 0, 2, 2] | Passed |
| reverse | ['BCD', 1, 4, 5] | ['BCD', 1, 4, 5] | Passed |
| CRLF normalization | ['A\nB', 0, 4, 4] | ['A\nB', 0, 4, 4] | Passed |
| NUL replacement | ['A�B', 0, 3, 3] | ['A�B', 0, 3, 3] | Passed |
| codepoint emoji | ['B', 1, 2, 2] | ['B', 1, 2, 2] | Passed |
| empty | ['', 0, 0, 0] | ['', 0, 0, 0] | Passed |
| surrogate end expands | ['A😀', 0, 3, 4] | ['A😀', 0, 3, 4] | Passed |
SHA-256 / b4d2a71b554ab49f1e39535bef9d38dcaa862623d0314b114cfd6c6405ae6dfb
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(data):
text,start,end,unit = data
widths = [2 if unit == 'utf16' and ord(c) > 65535 else 1 for c in text]
edges = [0]
for width in widths: edges.append(edges[-1]+width)
size = edges[-1]
if start < 0: start += size
if end < 0: end += size
start = min(size,max(0,start))
end = min(size,max(0,end))
if start > end: start,end = end,start
left = max(i for i,x in enumerate(edges) if x <= start)
right = min(i for i,x in enumerate(edges) if x >= end)
snippet = text[left:right].replace('\r\n','\n').replace('\x00','\ufffd')
return [snippet,edges[left],edges[right],size]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('surrogate interior expands', solve(['A'*N+'😀Z',N+1,N+2,'utf16'],), ['😀',N,N+2,N+3])
check('negative start', solve(['A'*N+'BC',-2,N+2,'codepoint'],), ['BC',N,N+2,N+2])
check('negative end', solve(['A'*N+'BC',N,-1,'codepoint'],), ['B',N,N+1,N+2])
check('clamp both', solve(['AB',-20,40,'utf16'],), ['AB',0,2,2])
check('reverse', solve(['ABCDE',4,1,'codepoint'],), ['BCD',1,4,5])
check('CRLF normalization', solve(['A\r\nB',0,4,'utf16'],), ['A\nB',0,4,4])
check('NUL replacement', solve(['A\x00B',0,3,'utf16'],), ['A�B',0,3,3])
check('codepoint emoji', solve(['😀B',1,2,'codepoint'],), ['B',1,2,2])
check('empty', solve(['',0,0,'utf16'],), ['',0,0,0])
check('surrogate end expands', solve(['A😀Z',0,2,'utf16'],), ['A😀',0,3,4])
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 |
|---|---|---|---|
| surrogate interior expands | ['😀', 1, 3, 4] | ['😀', 1, 3, 4] | Passed |
| negative start | ['BC', 1, 3, 3] | ['BC', 1, 3, 3] | Passed |
| negative end | ['B', 1, 2, 3] | ['B', 1, 2, 3] | Passed |
| clamp both | ['AB', 0, 2, 2] | ['AB', 0, 2, 2] | Passed |
| reverse | ['BCD', 1, 4, 5] | ['BCD', 1, 4, 5] | Passed |
| CRLF normalization | ['A\nB', 0, 4, 4] | ['A\nB', 0, 4, 4] | Passed |
| NUL replacement | ['A�B', 0, 3, 3] | ['A�B', 0, 3, 3] | Passed |
| codepoint emoji | ['B', 1, 2, 2] | ['B', 1, 2, 2] | Passed |
| empty | ['', 0, 0, 0] | ['', 0, 0, 0] | Passed |
| surrogate end expands | ['A😀', 0, 3, 4] | ['A😀', 0, 3, 4] | Passed |
SHA-256 / 7eaa050111be90c437ec742e294457f17a16a44e622078e38a0dd32f96a504e1
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:38.783749+00:00.
Case digest / 60de8c6cff1e7d7beb7e0db7d5c335c8eeea6ba185e4a0074ba97c3969675aac