FA-31396 / Keyboard interactions / Open access
Keyboard default edit cancellation pipeline: Read-only input still runs an editing default action · case 01
The event trace violates the readonly gate rule and produces incorrect keyboard state or command output.
ROOT CAUSE
Read-only input still runs an editing default action.
VERIFIED REPAIR
Use the contract transition `if readonly or not (len(key)==1 or key in ('Backspace','Delete')):` at the readonly gate fault site; preserve the other state transitions.
Unsuccessful approach: The partial repair changes this transition to if readonly and not (len(key)==1 or key in ('Backspace','Delete')):, which still violates the model contract on the explicit regression traces.
Case contract
Case [text,lo,hi,key,prevent_down,prevent_before,cancelable,readonly]. Selection is a valid half-open code-point interval. Printable single characters replace selection, Backspace deletes previous point and Delete deletes next point for collapsed selections. Other keys do not edit. A cancelable prevented keydown stops editing; readonly suppresses beforeinput. A changed candidate fires beforeinput, cancellation suppresses mutation; successful edit fires input. Always log keydown and keyup. Return text, selection endpoints and event log. Inputs are finite ordered event traces; return the stated deterministic state. Batch entries are independent. N varies the number of independent input transactions.
Why this case matters
Controlled keyboard event processing model for debugging application event logic.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(cases):
def run(c):
text,lo,hi,key,prevent_down,prevent_before,cancelable,readonly=c
log=['keydown']; original=[text,lo,hi]
if prevent_down and cancelable:
return original+[log+['keyup']]
if not (len(key)==1 or key in ('Backspace','Delete')):
return original+[log+['keyup']]
insert=key if len(key)==1 else ''
if key=='Backspace' and lo==hi: lo=max(0,lo-1)
elif key=='Delete' and lo==hi: hi=min(len(text),hi+1)
changed=text[:lo]+insert+text[hi:]
if changed==text:
return original+[log+['keyup']]
log.append('beforeinput')
if prevent_before:
return original+[log+['keyup']]
text=changed
hi=lo+len(insert); lo=hi
log.append('input')
return [text,lo,hi,log+['keyup']]
return [run(c) for c in cases]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-default-edit scenario 0', solve([['abc', 1, 1, 'X', False, False, True, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 1', solve([['abc', 1, 2, 'XY', False, False, True, False]] * N), [['abc', 1, 2, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 2', solve([['abcd', 1, 3, 'Z', False, False, True, False]] * N), [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 3', solve([['abcd', 1, 3, 'Backspace', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 4', solve([['abcd', 1, 3, 'Delete', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 5', solve([['abcd', 2, 2, 'Backspace', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 6', solve([['abcd', 1, 1, 'Delete', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 7', solve([['abc', 0, 0, 'Backspace', False, False, True, False]] * N), [['abc', 0, 0, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 8', solve([['abc', 0, 3, 'Delete', False, False, True, False]] * N), [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 9', solve([['abc', 1, 1, 'X', True, False, False, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 10', solve([['abc', 1, 1, 'X', True, False, True, False]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 11', solve([['abc', 1, 1, 'X', False, True, False, False]] * N), [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] * N)
check('key-default-edit scenario 12', solve([['abc', 1, 1, 'X', False, False, True, True]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
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 |
|---|---|---|---|
| key-default-edit scenario 0 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 1 | [['abc', 1, 2, ['keydown', 'keyup']]] | [['abc', 1, 2, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 2 | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 3 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 4 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 5 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 6 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 7 | [['abc', 0, 0, ['keydown', 'keyup']]] | [['abc', 0, 0, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 8 | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 9 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 10 | [['abc', 1, 1, ['keydown', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 11 | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | Passed |
| key-default-edit scenario 12 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Failed |
SHA-256 / 422e05cd227709ef5650187393a92aad546f53cd4d0f100238c08c96aaf5c1e8
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(cases):
def run(c):
text,lo,hi,key,prevent_down,prevent_before,cancelable,readonly=c
log=['keydown']; original=[text,lo,hi]
if prevent_down and cancelable:
return original+[log+['keyup']]
if readonly and not (len(key)==1 or key in ('Backspace','Delete')):
return original+[log+['keyup']]
insert=key if len(key)==1 else ''
if key=='Backspace' and lo==hi: lo=max(0,lo-1)
elif key=='Delete' and lo==hi: hi=min(len(text),hi+1)
changed=text[:lo]+insert+text[hi:]
if changed==text:
return original+[log+['keyup']]
log.append('beforeinput')
if prevent_before:
return original+[log+['keyup']]
text=changed
hi=lo+len(insert); lo=hi
log.append('input')
return [text,lo,hi,log+['keyup']]
return [run(c) for c in cases]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-default-edit scenario 0', solve([['abc', 1, 1, 'X', False, False, True, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 1', solve([['abc', 1, 2, 'XY', False, False, True, False]] * N), [['abc', 1, 2, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 2', solve([['abcd', 1, 3, 'Z', False, False, True, False]] * N), [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 3', solve([['abcd', 1, 3, 'Backspace', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 4', solve([['abcd', 1, 3, 'Delete', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 5', solve([['abcd', 2, 2, 'Backspace', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 6', solve([['abcd', 1, 1, 'Delete', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 7', solve([['abc', 0, 0, 'Backspace', False, False, True, False]] * N), [['abc', 0, 0, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 8', solve([['abc', 0, 3, 'Delete', False, False, True, False]] * N), [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 9', solve([['abc', 1, 1, 'X', True, False, False, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 10', solve([['abc', 1, 1, 'X', True, False, True, False]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 11', solve([['abc', 1, 1, 'X', False, True, False, False]] * N), [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] * N)
check('key-default-edit scenario 12', solve([['abc', 1, 1, 'X', False, False, True, True]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
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 |
|---|---|---|---|
| key-default-edit scenario 0 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 1 | [['ac', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['abc', 1, 2, ['keydown', 'keyup']]] | Failed |
| key-default-edit scenario 2 | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 3 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 4 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 5 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 6 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 7 | [['abc', 0, 0, ['keydown', 'keyup']]] | [['abc', 0, 0, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 8 | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 9 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 10 | [['abc', 1, 1, ['keydown', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 11 | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | Passed |
| key-default-edit scenario 12 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Failed |
SHA-256 / 14b850646f52006e72ae3f70b7015baaa5b488140badf1c5a098a698dfb04fbb
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(cases):
def run(c):
text,lo,hi,key,prevent_down,prevent_before,cancelable,readonly=c
log=['keydown']; original=[text,lo,hi]
if prevent_down and cancelable:
return original+[log+['keyup']]
if readonly or not (len(key)==1 or key in ('Backspace','Delete')):
return original+[log+['keyup']]
insert=key if len(key)==1 else ''
if key=='Backspace' and lo==hi: lo=max(0,lo-1)
elif key=='Delete' and lo==hi: hi=min(len(text),hi+1)
changed=text[:lo]+insert+text[hi:]
if changed==text:
return original+[log+['keyup']]
log.append('beforeinput')
if prevent_before:
return original+[log+['keyup']]
text=changed
hi=lo+len(insert); lo=hi
log.append('input')
return [text,lo,hi,log+['keyup']]
return [run(c) for c in cases]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('key-default-edit scenario 0', solve([['abc', 1, 1, 'X', False, False, True, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 1', solve([['abc', 1, 2, 'XY', False, False, True, False]] * N), [['abc', 1, 2, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 2', solve([['abcd', 1, 3, 'Z', False, False, True, False]] * N), [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 3', solve([['abcd', 1, 3, 'Backspace', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 4', solve([['abcd', 1, 3, 'Delete', False, False, True, False]] * N), [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 5', solve([['abcd', 2, 2, 'Backspace', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 6', solve([['abcd', 1, 1, 'Delete', False, False, True, False]] * N), [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 7', solve([['abc', 0, 0, 'Backspace', False, False, True, False]] * N), [['abc', 0, 0, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 8', solve([['abc', 0, 3, 'Delete', False, False, True, False]] * N), [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 9', solve([['abc', 1, 1, 'X', True, False, False, False]] * N), [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] * N)
check('key-default-edit scenario 10', solve([['abc', 1, 1, 'X', True, False, True, False]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
check('key-default-edit scenario 11', solve([['abc', 1, 1, 'X', False, True, False, False]] * N), [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] * N)
check('key-default-edit scenario 12', solve([['abc', 1, 1, 'X', False, False, True, True]] * N), [['abc', 1, 1, ['keydown', 'keyup']]] * N)
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 |
|---|---|---|---|
| key-default-edit scenario 0 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 1 | [['abc', 1, 2, ['keydown', 'keyup']]] | [['abc', 1, 2, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 2 | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aZd', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 3 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 4 | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['ad', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 5 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 6 | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['acd', 1, 1, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 7 | [['abc', 0, 0, ['keydown', 'keyup']]] | [['abc', 0, 0, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 8 | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['', 0, 0, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 9 | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | [['aXbc', 2, 2, ['keydown', 'beforeinput', 'input', 'keyup']]] | Passed |
| key-default-edit scenario 10 | [['abc', 1, 1, ['keydown', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Passed |
| key-default-edit scenario 11 | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | [['abc', 1, 1, ['keydown', 'beforeinput', 'keyup']]] | Passed |
| key-default-edit scenario 12 | [['abc', 1, 1, ['keydown', 'keyup']]] | [['abc', 1, 1, ['keydown', 'keyup']]] | Passed |
SHA-256 / c0c60075f03e76ea4cae283d680c3a204401ac65b5b85df71f07606e58329c53
Verification & scope
Offline stipulated event model, not a browser implementation or web standard conformance claim. 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:42:01.618088+00:00.
Case digest / b544739b5e76a4ca0b7c872e2501fc9ec21a3011b1af3a489624411811462fa0