FA-37391 / Table interactions / Open access
Table column drag transaction: drag block order · case 01
The table drag block order result violates the stipulated table column drag transaction contract.
ROOT CAUSE
The drag block order decision uses '[c for c in moving if c in order]' instead of '[c for c in order if c in moving]'.
VERIFIED REPAIR
Apply the complete drag block order rule: [c for c in order if c in moving].
Unsuccessful approach: The attempted repair '[c for c in reversed(order) if c in moving]' still loses a required part of the table contract.
Case contract
Move selected columns as a schema-ordered block relative to a visible nonmoving target, preserve hidden columns, reject any locked or pane-incompatible member, and compute insertion after removing the whole source block.
Why this case matters
An offline table UI model isolates table column drag transaction, including adversarial row/column relationships.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(order, moving, target, side, hidden, pinned, locked):
block=[c for c in moving if c in order]
if not block or target in block: return order
if any(c in locked for c in block): return order
if any(pinned.get(c,'center')!=pinned.get(target,'center') for c in block): return order
visible=[c for c in order if c not in hidden]
if target not in visible: return order
remaining=[c for c in order if c not in block]
index=(remaining.index(target) if target in remaining else 0)+(side=='after')
return remaining[:index]+block+remaining[index:]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
if N == 1:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(1)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e1'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 2:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(2)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e2'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 3:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(3)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e3'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 4:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(4)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e4'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 5:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(5)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e5'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
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 |
|---|---|---|---|
| discontiguous selected columns | ['b', 'd', 'c', 'a', 'e1'] | ['b', 'd', 'a', 'c', 'e1'] | Failed |
| before target | ['a', 'd', 'b', 'c'] | ['a', 'd', 'b', 'c'] | Passed |
| self drop on second | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed locked block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed pane block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| hidden target | ['a', 'b', 'c', 'd'] | ['a', 'b', 'c', 'd'] | Passed |
| no drag | ['a'] | ['a'] | Passed |
SHA-256 / 75255387d1f39c254fdd11871e9e86f210c8e00ef59af1cb6fb1be72e4efe97c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(order, moving, target, side, hidden, pinned, locked):
block=[c for c in reversed(order) if c in moving]
if not block or target in block: return order
if any(c in locked for c in block): return order
if any(pinned.get(c,'center')!=pinned.get(target,'center') for c in block): return order
visible=[c for c in order if c not in hidden]
if target not in visible: return order
remaining=[c for c in order if c not in block]
index=(remaining.index(target) if target in remaining else 0)+(side=='after')
return remaining[:index]+block+remaining[index:]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
if N == 1:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(1)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e1'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 2:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(2)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e2'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 3:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(3)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e3'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 4:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(4)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e4'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 5:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(5)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e5'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
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 |
|---|---|---|---|
| discontiguous selected columns | ['b', 'd', 'c', 'a', 'e1'] | ['b', 'd', 'a', 'c', 'e1'] | Failed |
| before target | ['a', 'd', 'b', 'c'] | ['a', 'd', 'b', 'c'] | Passed |
| self drop on second | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed locked block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed pane block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| hidden target | ['a', 'b', 'c', 'd'] | ['a', 'b', 'c', 'd'] | Passed |
| no drag | ['a'] | ['a'] | Passed |
SHA-256 / d98b5e07fce5c2a46d9f1d6cc431d3a2503a27ba5f15fc23790ee774be684f1e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(order, moving, target, side, hidden, pinned, locked):
block=[c for c in order if c in moving]
if not block or target in block: return order
if any(c in locked for c in block): return order
if any(pinned.get(c,'center')!=pinned.get(target,'center') for c in block): return order
visible=[c for c in order if c not in hidden]
if target not in visible: return order
remaining=[c for c in order if c not in block]
index=(remaining.index(target) if target in remaining else 0)+(side=='after')
return remaining[:index]+block+remaining[index:]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
if N == 1:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(1)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e1'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 2:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(2)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e2'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 3:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(3)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e3'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 4:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(4)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e4'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
if N == 5:
check('discontiguous selected columns', solve(['a','b','c','d','e'+str(5)], ['c','a'], 'd', 'after', [], {}, []), ['b', 'd', 'a', 'c', 'e5'])
check('before target', solve(['a','b','c','d'], ['d'], 'b', 'before', [], {}, []), ['a', 'd', 'b', 'c'])
check('self drop on second', solve(['a','b','c'], ['a','c'], 'c', 'before', [], {}, []), ['a', 'b', 'c'])
check('mixed locked block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {}, ['b']), ['a', 'b', 'c'])
check('mixed pane block', solve(['a','b','c'], ['a','b'], 'c', 'after', [], {'b':'left'}, []), ['a', 'b', 'c'])
check('hidden target', solve(['a','b','c','d'], ['a'], 'c', 'after', ['b','c'], {}, []), ['a', 'b', 'c', 'd'])
check('no drag', solve(['a'], [], 'a', 'before', [], {}, []), ['a'])
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 |
|---|---|---|---|
| discontiguous selected columns | ['b', 'd', 'a', 'c', 'e1'] | ['b', 'd', 'a', 'c', 'e1'] | Passed |
| before target | ['a', 'd', 'b', 'c'] | ['a', 'd', 'b', 'c'] | Passed |
| self drop on second | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed locked block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| mixed pane block | ['a', 'b', 'c'] | ['a', 'b', 'c'] | Passed |
| hidden target | ['a', 'b', 'c', 'd'] | ['a', 'b', 'c', 'd'] | Passed |
| no drag | ['a'] | ['a'] | Passed |
SHA-256 / 8feb35b6bcbb885e7412939cfdd1d03bfe7b250284a4812aa5435c5446d36133
Verification & scope
Column IDs are unique and the drop target belongs to the source schema; pane labels are left, right, or center. Deterministic offline table UI model; no browser, DOM, framework, or standards conformance is claimed. 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:43:00.758267+00:00.
Case digest / 3d62fb8d6fccf8389598dcdcb4576fc5408c1297e9e106b73e1485b3699adb15