FAILURE MAP
← Case archive

FA-8946 / Table interactions / Open access

Multi-column sort interaction: Single-sort activation retains other sort priorities · case 01

Single-sort activation retains other sort priorities.

Verified by executionVariant 1 · 9 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The single column operation uses `if not multi: return sorting + ([entry] if entry else [])` where the contract requires `if not multi: return [entry] if entry else []`.

VERIFIED REPAIR

Implement the single column operation as `if not multi: return [entry] if entry else []`.

Unsuccessful approach: Returning old state ignores the requested sort entirely.

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, 'desc')
    else: entry = None if removable else (column, 'asc')
    if not multi: return sorting + ([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 fixtureActualExpectedOutcome
new[['a', 'asc']][['a', 'asc']]Passed
reverse[['a', 'asc'], ['a', 'desc']][['a', 'desc']]Failed
locked[['a', 'desc'], ['a', 'asc']][['a', 'asc']]Failed
remove[['a', 'desc']][]Failed
single[['a', 'asc'], ['b', 'asc']][['b', 'asc']]Failed
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 / 47bff2abececb4c424ad6c6e461817d1e36f87435e6e90c8cfce79f3cb6bbc72

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 = (column, 'desc')
    else: entry = None if removable else (column, 'asc')
    if not multi: return sorting
    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 fixtureActualExpectedOutcome
new[][['a', 'asc']]Failed
reverse[['a', 'asc']][['a', 'desc']]Failed
locked[['a', 'desc']][['a', 'asc']]Failed
remove[['a', 'desc']][]Failed
single[['a', 'asc']][['b', 'asc']]Failed
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']]Failed
repeat append[['a', 'asc'], ['b', 'asc']][['a', 'asc'], ['b', 'asc']]Passed

SHA-256 / a813d87a5164588944b8438c6dbcf1957fe30f03f82368c9ec74337da87663ce

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 fixtureActualExpectedOutcome
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.701819+00:00.

Case digest / f9fe37cf8a35f967f8d868b75ab93c951a47b3e2b4a5298bcbaaf48c3969703d