FA-20906 / Assistive announcements / Open access
Canceling another job also stops current speech · case 01
The announcement trace violates the stated interrupt-resumption contract.
ROOT CAUSE
Fault site: if current is not None and current[0]==e[1]: current=None is implemented as if current is not None: current=None
VERIFIED REPAIR
Restore the stated transition: if current is not None and current[0]==e[1]: current=None
Unsuccessful approach: The attempted repair substitutes if current is not None and current[0]==e[1]: 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: 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 | [[['c', 'c']], None, [], []] | [[['b', 'b'], ['c', 'c']], None, [], []] | Failed |
| trace-4 | [[], ['c', 0], [], []] | [[], ['a', 0], [], [['c', 0]]] | Failed |
| trace-5 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-6 | [[], ['b', 0], [], []] | [[], ['c', 0], [['b', 0]], []] | Failed |
| trace-7 | [[], None, [], []] | [[], None, [], []] | Passed |
| trace-8 | [[], None, [], []] | [[], None, [], []] | Passed |
SHA-256 / cad07887a8a1f757920b192a249433cab6df7961352986f501a97c87fdc98ffb
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[2]=0
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 | [[], ['a', 0], [], []] | [[], None, [], []] | Failed |
SHA-256 / 332b425708d440f5c1e5c5423b074be19b9d94fa5b1e295939dfb636ca183765
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.482138+00:00.
Case digest / 90cbd825f74943918328f9058f30faaab8875011658bd64bd07479aeb3b64b8b