FA-8936 / Table interactions / Open access
Multi-column sort interaction: Reactivating ascending sort does not reverse direction · case 01
Reactivating ascending sort does not reverse direction.
ROOT CAUSE
The asc cycle operation uses `elif sorting[index][1] == 'asc': entry = (column, 'asc')` where the contract requires `elif sorting[index][1] == 'asc': entry = (column, 'desc')`.
VERIFIED REPAIR
Implement the asc cycle operation as `elif sorting[index][1] == 'asc': entry = (column, 'desc')`.
Unsuccessful approach: Removing it skips the descending state.
Case contract
New sort is ascending; activation cycles ascending to descending to removed when allowed; single mode replaces all sorts; multi mode preserves priorities when changing direction.
Why this case matters
A deterministic model of multi-column sort interaction; 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(sorting, column, multi, removable):
sorting = list(sorting)
index = next((i for i, (c, d) in enumerate(sorting) if c == column), None)
if index is None: entry = (column, 'asc')
elif sorting[index][1] == 'asc': entry = (column, 'asc')
else: entry = None if removable else (column, 'asc')
if not multi: return [entry] if entry else []
if index is None: return sorting + [entry]
if entry is None: return sorting[:index] + sorting[index+1:]
sorting[index] = entry
return sorting
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('new', solve([], 'a', False, True), [('a', 'asc')])
check('reverse', solve([('a','asc')], 'a', False, True), [('a', 'desc')])
check('locked', solve([('a','desc')], 'a', False, False), [('a', 'asc')])
check('remove', solve([('a','desc')], 'a', False, True), [])
check('single', solve([('a','asc')], 'b', False, True), [('b', 'asc')])
check('priority', solve([('a','asc'),('b','asc')], 'a', True, True), [('a', 'desc'), ('b', 'asc')])
check('append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
check('parameterized sort identity', solve([],str(N),False,True), [(str(N),'asc')])
for repetition in range(N):
check('repeat append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
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 |
|---|---|---|---|
| new | [['a', 'asc']] | [['a', 'asc']] | Passed |
| reverse | [['a', 'asc']] | [['a', 'desc']] | Failed |
| locked | [['a', 'asc']] | [['a', 'asc']] | Passed |
| remove | [] | [] | Passed |
| single | [['b', 'asc']] | [['b', 'asc']] | Passed |
| priority | [['a', 'asc'], ['b', 'asc']] | [['a', 'desc'], ['b', 'asc']] | Failed |
| append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
| parameterized sort identity | [['1', 'asc']] | [['1', 'asc']] | Passed |
| repeat append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
SHA-256 / 5007e834e1cfca025edce2983bd12749968116513325cef5fdfd389118ea31da
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(sorting, column, multi, removable):
sorting = list(sorting)
index = next((i for i, (c, d) in enumerate(sorting) if c == column), None)
if index is None: entry = (column, 'asc')
elif sorting[index][1] == 'asc': entry = None
else: entry = None if removable else (column, 'asc')
if not multi: return [entry] if entry else []
if index is None: return sorting + [entry]
if entry is None: return sorting[:index] + sorting[index+1:]
sorting[index] = entry
return sorting
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('new', solve([], 'a', False, True), [('a', 'asc')])
check('reverse', solve([('a','asc')], 'a', False, True), [('a', 'desc')])
check('locked', solve([('a','desc')], 'a', False, False), [('a', 'asc')])
check('remove', solve([('a','desc')], 'a', False, True), [])
check('single', solve([('a','asc')], 'b', False, True), [('b', 'asc')])
check('priority', solve([('a','asc'),('b','asc')], 'a', True, True), [('a', 'desc'), ('b', 'asc')])
check('append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
check('parameterized sort identity', solve([],str(N),False,True), [(str(N),'asc')])
for repetition in range(N):
check('repeat append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
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 |
|---|---|---|---|
| new | [['a', 'asc']] | [['a', 'asc']] | Passed |
| reverse | [] | [['a', 'desc']] | Failed |
| locked | [['a', 'asc']] | [['a', 'asc']] | Passed |
| remove | [] | [] | Passed |
| single | [['b', 'asc']] | [['b', 'asc']] | Passed |
| priority | [['b', 'asc']] | [['a', 'desc'], ['b', 'asc']] | Failed |
| append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
| parameterized sort identity | [['1', 'asc']] | [['1', 'asc']] | Passed |
| repeat append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
SHA-256 / 3f74315e14259e8385baab6158971ac9dc3b945eccf25fcd533e53c5bf87624a
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(sorting, column, multi, removable):
sorting = list(sorting)
index = next((i for i, (c, d) in enumerate(sorting) if c == column), None)
if index is None: entry = (column, 'asc')
elif sorting[index][1] == 'asc': entry = (column, 'desc')
else: entry = None if removable else (column, 'asc')
if not multi: return [entry] if entry else []
if index is None: return sorting + [entry]
if entry is None: return sorting[:index] + sorting[index+1:]
sorting[index] = entry
return sorting
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('new', solve([], 'a', False, True), [('a', 'asc')])
check('reverse', solve([('a','asc')], 'a', False, True), [('a', 'desc')])
check('locked', solve([('a','desc')], 'a', False, False), [('a', 'asc')])
check('remove', solve([('a','desc')], 'a', False, True), [])
check('single', solve([('a','asc')], 'b', False, True), [('b', 'asc')])
check('priority', solve([('a','asc'),('b','asc')], 'a', True, True), [('a', 'desc'), ('b', 'asc')])
check('append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
check('parameterized sort identity', solve([],str(N),False,True), [(str(N),'asc')])
for repetition in range(N):
check('repeat append', solve([('a','asc')], 'b', True, True), [('a', 'asc'), ('b', 'asc')])
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 |
|---|---|---|---|
| new | [['a', 'asc']] | [['a', 'asc']] | Passed |
| reverse | [['a', 'desc']] | [['a', 'desc']] | Passed |
| locked | [['a', 'asc']] | [['a', 'asc']] | Passed |
| remove | [] | [] | Passed |
| single | [['b', 'asc']] | [['b', 'asc']] | Passed |
| priority | [['a', 'desc'], ['b', 'asc']] | [['a', 'desc'], ['b', 'asc']] | Passed |
| append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
| parameterized sort identity | [['1', 'asc']] | [['1', 'asc']] | Passed |
| repeat append | [['a', 'asc'], ['b', 'asc']] | [['a', 'asc'], ['b', 'asc']] | Passed |
SHA-256 / a08be1d7850601d077383173dfb4eb1fa4f9128cc14b5bd4a88ee340523c72f7
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:25.623995+00:00.
Case digest / e7dba3fc67e1d2c8f40ba5ebc7a4e5b6894faa01a849077bf5b680c657f8451e