FA-30806 / HTTP retries / Open access
Retry hook failure containment: Hook error not network violates retry transition semantics · case 01
Hook error not network violates retry transition semantics
ROOT CAUSE
Hook error not network violates retry transition semantics
VERIFIED REPAIR
Restore the specified transition out.append('hook-error').
Unsuccessful approach: The attempted repair changes the faulty site to out.append('exhausted') 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='failed'; out.append('fatal'); 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 | [['fatal'], 1, 'failed'] | [['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 / 7facf45d5b14e81aadecf93688e915aa019f2869af21639d60e1f87fb763f6d9
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='failed'; out.append('exhausted'); 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 | [['exhausted'], 1, 'failed'] | [['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 / 9ec6650c0d824fd7ce07a2c7d8174977f54e10cf6d9ca103e300bbb85de02b71
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.257550+00:00.
Case digest / 37c27352d61e51220e8007d0d3ddb18fba50d1ba73f944e255cc6311c197c256