FA-30801 / HTTP retries / Open access
Retry hook failure containment: Hook failure fatal violates retry transition semantics · case 01
Hook failure fatal violates retry transition semantics
ROOT CAUSE
Hook failure fatal violates retry transition semantics
VERIFIED REPAIR
Restore the specified transition if not e[2]: state='failed'; out.append('hook-error'); continue.
Unsuccessful approach: The attempted repair changes the faulty site to if not e[2]: state='waiting'; out.append('hook-error'); continue but still violates a regression oracle.
Case contract
begin(cap) starts pending request. fail(eligible,before-hook-ok) calls retry hook only for eligible failures with remaining cap; a failing hook terminally records hook-error without spending retry budget. Otherwise admit consumes one cap and enters waiting. wake starts it. success(observer-ok) stores success before observer invocation; observer failure is diagnostic only and never creates a retry. cancel is terminal. Return lifecycle decisions and remaining cap.
Why this case matters
Offline deterministic model of HTTP request retries.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
cap=0; state='idle'; out=[]
for e in events:
if e[0]=='begin': cap=e[1]; state='running'
elif e[0]=='fail':
if state!='running': out.append('ignored'); continue
if not e[1]: state='failed'; out.append('fatal'); continue
if cap==0: state='failed'; out.append('exhausted'); continue
if not e[2]: state='running'; out.append('hook-error'); continue
cap-=1; state='waiting'; out.append('scheduled')
elif e[0]=='wake':
if state=='waiting': state='running'; out.append('started')
elif e[0]=='success':
if state!='running': out.append('ignored'); continue
state='done'; out.append('success')
if not e[1]: out.append('observer-error')
elif e[0]=='cancel': state='cancelled'
return [out,cap,state]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
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 |
|---|---|---|---|
| 0 | [[], 0, 'idle'] | [[], 0, 'idle'] | Passed |
| 1 | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | Passed |
| 2 | [['hook-error'], 1, 'running'] | [['hook-error'], 1, 'failed'] | Failed |
| 3 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
| 4 | [['exhausted'], 0, 'failed'] | [['exhausted'], 0, 'failed'] | Passed |
| 5 | [['success', 'ignored'], 1, 'done'] | [['success', 'ignored'], 1, 'done'] | Passed |
| 6 | [['scheduled', 'ignored'], 0, 'cancelled'] | [['scheduled', 'ignored'], 0, 'cancelled'] | Passed |
| 7 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
SHA-256 / 1ef063268111f309a0ce1ecd5e9071510aece5fe8d12404c17400547438cdfac
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
cap=0; state='idle'; out=[]
for e in events:
if e[0]=='begin': cap=e[1]; state='running'
elif e[0]=='fail':
if state!='running': out.append('ignored'); continue
if not e[1]: state='failed'; out.append('fatal'); continue
if cap==0: state='failed'; out.append('exhausted'); continue
if not e[2]: state='waiting'; out.append('hook-error'); continue
cap-=1; state='waiting'; out.append('scheduled')
elif e[0]=='wake':
if state=='waiting': state='running'; out.append('started')
elif e[0]=='success':
if state!='running': out.append('ignored'); continue
state='done'; out.append('success')
if not e[1]: out.append('observer-error')
elif e[0]=='cancel': state='cancelled'
return [out,cap,state]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
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 |
|---|---|---|---|
| 0 | [[], 0, 'idle'] | [[], 0, 'idle'] | Passed |
| 1 | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | Passed |
| 2 | [['hook-error', 'started'], 1, 'running'] | [['hook-error'], 1, 'failed'] | Failed |
| 3 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
| 4 | [['exhausted'], 0, 'failed'] | [['exhausted'], 0, 'failed'] | Passed |
| 5 | [['success', 'ignored'], 1, 'done'] | [['success', 'ignored'], 1, 'done'] | Passed |
| 6 | [['scheduled', 'ignored'], 0, 'cancelled'] | [['scheduled', 'ignored'], 0, 'cancelled'] | Passed |
| 7 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
SHA-256 / 711e14e56084a73462abdd686ce5e492e2e29356f680d4a78cf36b8cdd5e03f5
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events):
cap=0; state='idle'; out=[]
for e in events:
if e[0]=='begin': cap=e[1]; state='running'
elif e[0]=='fail':
if state!='running': out.append('ignored'); continue
if not e[1]: state='failed'; out.append('fatal'); continue
if cap==0: state='failed'; out.append('exhausted'); continue
if not e[2]: state='failed'; out.append('hook-error'); continue
cap-=1; state='waiting'; out.append('scheduled')
elif e[0]=='wake':
if state=='waiting': state='running'; out.append('started')
elif e[0]=='success':
if state!='running': out.append('ignored'); continue
state='done'; out.append('success')
if not e[1]: out.append('observer-error')
elif e[0]=='cancel': state='cancelled'
return [out,cap,state]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,'idle'])
check('1', solve([('begin',2),('fail',True,True),('fail',True,True),('wake',),('success',False),('fail',True,True)]), [['scheduled','ignored','started','success','observer-error','ignored'],1,'done'])
check('2', solve([('begin',N),('fail',True,False),('wake',)]), [['hook-error'],N,'failed'])
check('3', solve([('begin',N),('fail',False,False)]), [['fatal'],N,'failed'])
check('4', solve([('begin',0),('fail',True,False)]), [['exhausted'],0,'failed'])
check('5', solve([('begin',N),('success',True),('success',False)]), [['success','ignored'],N,'done'])
check('6', solve([('begin',N),('fail',True,True),('cancel',),('wake',),('success',False)]), [['scheduled','ignored'],N-1,'cancelled'])
check('7', solve([('begin',N),('fail',False,True)]), [['fatal'],N,'failed'])
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 |
|---|---|---|---|
| 0 | [[], 0, 'idle'] | [[], 0, 'idle'] | Passed |
| 1 | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | [['scheduled', 'ignored', 'started', 'success', 'observer-error', 'ignored'], 1, 'done'] | Passed |
| 2 | [['hook-error'], 1, 'failed'] | [['hook-error'], 1, 'failed'] | Passed |
| 3 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
| 4 | [['exhausted'], 0, 'failed'] | [['exhausted'], 0, 'failed'] | Passed |
| 5 | [['success', 'ignored'], 1, 'done'] | [['success', 'ignored'], 1, 'done'] | Passed |
| 6 | [['scheduled', 'ignored'], 0, 'cancelled'] | [['scheduled', 'ignored'], 0, 'cancelled'] | Passed |
| 7 | [['fatal'], 1, 'failed'] | [['fatal'], 1, 'failed'] | Passed |
SHA-256 / 06065f51f00d5003576e0940508b0051d6a7ffa05b1aec0009a7e3582d63776a
Verification & scope
Stipulated bounded simulator, not a complete HTTP implementation or a 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:41:56.246901+00:00.
Case digest / 8bd0315315b3fb9edff3b77d460e9b8f16bcb1eab705142400e77d29ac42536d