FA-20931 / Assistive announcements / Open access
Nested interruption resumes the oldest suspended utterance · case 01
The announcement trace violates the stated interrupt-resumption contract.
ROOT CAUSE
Fault site: if suspended: current=suspended.pop() is implemented as if suspended: current=suspended.pop(0)
VERIFIED REPAIR
Restore the stated transition: if suspended: current=suspended.pop()
Unsuccessful approach: The attempted repair substitutes if suspended: current=suspended.pop(); current[2]=0 and still violates a regression oracle.
Case contract
Speech jobs are [id,words,cursor]. Start queues a new job when one is active. Urgent pushes the current job on a LIFO suspended stack and begins fresh. Step speaks one word and finishes only after its last word; suspended jobs resume before queued normal work. Cancel ID removes it from every location and promotes next available job. Clear discards all jobs without speech. Report emitted [id,word] and current/suspended/waiting IDs with cursors.
Why this case matters
Deterministic controlled model of assistive announcement delivery.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
current=None; suspended=[]; waiting=[]; spoken=[]
for e in events:
if e[0] in ('start','urgent'):
job=[e[1],list(e[2]),0]
if e[0]=='urgent':
if current is not None: suspended.append(current)
current=job
elif current is None: current=job
else: waiting.append(job)
elif e[0]=='cancel':
if current is not None and current[0]==e[1]: current=None
suspended=[x for x in suspended if x[0]!=e[1]]
waiting=[x for x in waiting if x[0]!=e[1]]
elif e[0]=='clear': current=None; suspended=[]; waiting=[]
elif e[0]=='step' and current is not None:
if current[2]<len(current[1]):
spoken.append([current[0],current[1][current[2]]])
current[2]+=1
if current[2]>=len(current[1]): current=None
if current is None:
if suspended: current=suspended.pop(0)
elif waiting: current=waiting.pop(0)
return [spoken,None if current is None else [current[0],current[2]],[[x[0],x[2]] for x in suspended],[[x[0],x[2]] for x in waiting]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = ["[['start','a',['a']*(N+2)],['step'],['urgent','b',['b']],['urgent','c',['c']],['step'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['b']],['start','c',['c']]]+[['step']]*(N+2)", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['cancel','a'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['beta']],['start','c',['gamma']],['cancel','b']]", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['clear']]", "[['start','a',['a']*N],['urgent','b',['b']],['urgent','c',['c']],['cancel','a']]", "[['start','a',[]],['step']]", "[['start','a',['a']*N],['cancel','a']]"]
expected = {1: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 2: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 3: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 4: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 5: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]]}[N]
for i, expression in enumerate(fixtures):
check("trace-"+str(i+1), solve(eval(expression)), expected[i])
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 |
|---|---|---|---|
| trace-1 | [[['a', 'a'], ['c', 'c'], ['a', 'a'], ['a', 'a']], ['b', 0], [], []] | [[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []] | Failed |
| trace-2 | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-3 | [[['b', 'b'], ['c', 'c']], None, [], []] | [[['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-4 | [[], ['a', 0], [], [['c', 0]]] | [[], ['a', 0], [], [['c', 0]]] | Passed |
| trace-5 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-6 | [[], ['c', 0], [['b', 0]], []] | [[], ['c', 0], [['b', 0]], []] | Passed |
| trace-7 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-8 | [[], None, [], []] | [[], None, [], []] | Passed |
SHA-256 / d1ed5b06f94b2d0e03dadc9dc0dd96855180336bf6a64502ca6d48dc119e3bd4
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
current=None; suspended=[]; waiting=[]; spoken=[]
for e in events:
if e[0] in ('start','urgent'):
job=[e[1],list(e[2]),0]
if e[0]=='urgent':
if current is not None: suspended.append(current)
current=job
elif current is None: current=job
else: waiting.append(job)
elif e[0]=='cancel':
if current is not None and current[0]==e[1]: current=None
suspended=[x for x in suspended if x[0]!=e[1]]
waiting=[x for x in waiting if x[0]!=e[1]]
elif e[0]=='clear': current=None; suspended=[]; waiting=[]
elif e[0]=='step' and current is not None:
if current[2]<len(current[1]):
spoken.append([current[0],current[1][current[2]]])
current[2]+=1
if current[2]>=len(current[1]): current=None
if current is None:
if suspended: current=suspended.pop(); current[2]=0
elif waiting: current=waiting.pop(0)
return [spoken,None if current is None else [current[0],current[2]],[[x[0],x[2]] for x in suspended],[[x[0],x[2]] for x in waiting]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = ["[['start','a',['a']*(N+2)],['step'],['urgent','b',['b']],['urgent','c',['c']],['step'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['b']],['start','c',['c']]]+[['step']]*(N+2)", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['cancel','a'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['beta']],['start','c',['gamma']],['cancel','b']]", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['clear']]", "[['start','a',['a']*N],['urgent','b',['b']],['urgent','c',['c']],['cancel','a']]", "[['start','a',[]],['step']]", "[['start','a',['a']*N],['cancel','a']]"]
expected = {1: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 2: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 3: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 4: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 5: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]]}[N]
for i, expression in enumerate(fixtures):
check("trace-"+str(i+1), solve(eval(expression)), expected[i])
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 |
|---|---|---|---|
| trace-1 | [[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 1], [], []] | [[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []] | Failed |
| trace-2 | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-3 | [[['b', 'b'], ['c', 'c']], None, [], []] | [[['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-4 | [[], ['a', 0], [], [['c', 0]]] | [[], ['a', 0], [], [['c', 0]]] | Passed |
| trace-5 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-6 | [[], ['c', 0], [['b', 0]], []] | [[], ['c', 0], [['b', 0]], []] | Passed |
| trace-7 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-8 | [[], None, [], []] | [[], None, [], []] | Passed |
SHA-256 / 1c58c43ca2a2e921a3eeb29ff4d5dc250d1b172bbc8a7ecfb915106f47073f08
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
current=None; suspended=[]; waiting=[]; spoken=[]
for e in events:
if e[0] in ('start','urgent'):
job=[e[1],list(e[2]),0]
if e[0]=='urgent':
if current is not None: suspended.append(current)
current=job
elif current is None: current=job
else: waiting.append(job)
elif e[0]=='cancel':
if current is not None and current[0]==e[1]: current=None
suspended=[x for x in suspended if x[0]!=e[1]]
waiting=[x for x in waiting if x[0]!=e[1]]
elif e[0]=='clear': current=None; suspended=[]; waiting=[]
elif e[0]=='step' and current is not None:
if current[2]<len(current[1]):
spoken.append([current[0],current[1][current[2]]])
current[2]+=1
if current[2]>=len(current[1]): current=None
if current is None:
if suspended: current=suspended.pop()
elif waiting: current=waiting.pop(0)
return [spoken,None if current is None else [current[0],current[2]],[[x[0],x[2]] for x in suspended],[[x[0],x[2]] for x in waiting]]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = ["[['start','a',['a']*(N+2)],['step'],['urgent','b',['b']],['urgent','c',['c']],['step'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['b']],['start','c',['c']]]+[['step']]*(N+2)", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['cancel','a'],['step'],['step']]", "[['start','a',['a']*N],['start','b',['beta']],['start','c',['gamma']],['cancel','b']]", "[['start','a',['a']*N],['urgent','b',['b']],['start','c',['c']],['clear']]", "[['start','a',['a']*N],['urgent','b',['b']],['urgent','c',['c']],['cancel','a']]", "[['start','a',[]],['step']]", "[['start','a',['a']*N],['cancel','a']]"]
expected = {1: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 2: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 3: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 4: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]], 5: [[[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []], [[['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []], [[['b', 'b'], ['c', 'c']], None, [], []], [[], ['a', 0], [], [['c', 0]]], [[], None, [], []], [[], ['c', 0], [['b', 0]], []], [[], None, [], []], [[], None, [], []]]}[N]
for i, expression in enumerate(fixtures):
check("trace-"+str(i+1), solve(eval(expression)), expected[i])
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 |
|---|---|---|---|
| trace-1 | [[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []] | [[['a', 'a'], ['c', 'c'], ['b', 'b'], ['a', 'a']], ['a', 2], [], []] | Passed |
| trace-2 | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | [[['a', 'a'], ['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-3 | [[['b', 'b'], ['c', 'c']], None, [], []] | [[['b', 'b'], ['c', 'c']], None, [], []] | Passed |
| trace-4 | [[], ['a', 0], [], [['c', 0]]] | [[], ['a', 0], [], [['c', 0]]] | Passed |
| trace-5 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-6 | [[], ['c', 0], [['b', 0]], []] | [[], ['c', 0], [['b', 0]], []] | Passed |
| trace-7 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-8 | [[], None, [], []] | [[], None, [], []] | Passed |
SHA-256 / 5e832a3e83061f1ee0093431e9d549659342050e125ab0deee199556dd419aae
Verification & scope
Stipulated bounded policy, not a browser, speech engine, platform API, or standards 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:40:23.782842+00:00.
Case digest / 5770e308e2dc73aa5abb9ca41135d2344510f3bc9e019ef3b08b1a0b8ebcaa68