FA-53461 / Accessibility interaction semantics / Open access
Platform text ranges expose carriage-return duplicates · case 01
Platform text ranges expose carriage-return duplicates.
ROOT CAUSE
The line normalization decision in the text range bridge adapter violates its declared contract.
VERIFIED REPAIR
Apply the exact contract at the line normalization decision: .replace('\r\n','\n')
Unsuccessful approach: The local repair substitutes .replace('\r\n','\r') 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 += 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('\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\r\nB', 0, 4, 4] | ['A\nB', 0, 4, 4] | Failed |
| 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 / 5a66466e4b9b33ad18497396430670b4d32a95faa5eb06ee6f4920ec67a56ca4
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 < 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','\r').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\rB', 0, 4, 4] | ['A\nB', 0, 4, 4] | Failed |
| 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 / 1d76ef0ae32b1539f4551bc792ef58d9584ac8a7bfa9aca92a06dc889fe0c850
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:39.247404+00:00.
Case digest / c66c50b689a77b5a46784d0ed9a69b13e3efbbbdd0f2c1fd732fde09faba928d