FA-8246 / Direct manipulation / Open access
Pointer-capture ownership: Cancelled pointer capture remains active · case 01
Cancelled pointer capture remains active.
ROOT CAUSE
The cancel operation uses `if event == 'cancel' and pointer == owner: return (owner, position)` where the contract requires `if event == 'cancel' and pointer == owner: return (None, position)`.
VERIFIED REPAIR
Implement the cancel operation as `if event == 'cancel' and pointer == owner: return (None, position)`.
Unsuccessful approach: Resetting position on cancellation discards the last committed drag position.
Case contract
First pointer captures ownership; only its movement and release affect a drag; cancellation and capture loss clear ownership while preserving position.
Why this case matters
A deterministic model of pointer-capture ownership; this isolates one interface invariant without requiring a browser.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (pointer if owner is None else owner, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (owner, position)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 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 |
|---|---|---|---|
| initial down | [1, 4] | [1, 4] | Passed |
| second down | [1, 4] | [1, 4] | Passed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [1, 4] | [None, 4] | Failed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / 015b7dd1849a94a7f366d2d88f872ef101cbc9dd3c3ede30ec32e8e72be344fa
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (pointer if owner is None else owner, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (None, 0)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 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 |
|---|---|---|---|
| initial down | [1, 4] | [1, 4] | Passed |
| second down | [1, 4] | [1, 4] | Passed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [None, 0] | [None, 4] | Failed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / f2b24bcdbf0320fdcab27d09c49193861f00b908abe78a47140dd6e70033f5c8
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(owner, position, event, pointer, delta):
if event == 'down': return (pointer if owner is None else owner, position)
if event == 'move' and pointer == owner: return (owner, position + delta)
if event == 'up' and pointer == owner: return (None, position)
if event == 'cancel' and pointer == owner: return (None, position)
if event == 'lost': return (None, position)
return (owner, position)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 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 |
|---|---|---|---|
| initial down | [1, 4] | [1, 4] | Passed |
| second down | [1, 4] | [1, 4] | Passed |
| other move | [1, 4] | [1, 4] | Passed |
| owner move | [1, 7] | [1, 7] | Passed |
| other up | [1, 4] | [1, 4] | Passed |
| owner up | [None, 4] | [None, 4] | Passed |
| cancel | [None, 4] | [None, 4] | Passed |
| lost | [None, 4] | [None, 4] | Passed |
| parameterized pointer ownership | [1, 2] | [1, 2] | Passed |
| repeat lost | [None, 4] | [None, 4] | Passed |
SHA-256 / b57d2a364c43b5fad81f31ca1ee3ae6da82ece5397810eca5948879bc52cd522
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:19.654583+00:00.
Case digest / 4dd17726527ba25dba2801fe601aba9278b64ba739e4733b310bf0475c8721fe