FA-1086 / Reliability / Open access
Promote a standby after primary failure: A read-only standby is advertised as write primary · case 01
The failover promote operation is admitted even though a read-only standby is advertised as write primary.
ROOT CAUSE
The admission path omits the standby writable invariant while validating the other operation preconditions.
VERIFIED REPAIR
Require r['standby_writable'] is True together with every other stated precondition before accepting the operation.
Unsuccessful approach: Adding the standby writable check repairs the reported defect, but replacing the adjacent promotion epoch check loses that independent invariant.
Case contract
Return a Boolean admission decision for promote a standby after primary failure. The record r must satisfy all of: r['primary_fenced'] is True; r['rpo_bound'][0]-r['rpo_bound'][1] <= r['rpo_bound'][2]; r['standby_writable'] is True; r['promotion_epoch'][0] > r['promotion_epoch'][1]; r['routing_switched'][0] == r['routing_switched'][1]. Extra tracing fields are ignored; validation does not mutate the record.
Why this case matters
A deterministic local contract for reliability. Each negative fixture violates exactly one invariant. No transport timing, persistence, cryptographic verification, or full protocol implementation is claimed.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(r):
return (r['primary_fenced'] is True) and (r['rpo_bound'][0]-r['rpo_bound'][1] <= r['rpo_bound'][2]) and (r['promotion_epoch'][0] > r['promotion_epoch'][1]) and (r['routing_switched'][0] == r['routing_switched'][1])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
r = {'primary_fenced': True, 'rpo_bound': [10, 9, 1], 'standby_writable': True, 'promotion_epoch': [6, 5], 'routing_switched': ['new', 'new']}
check('valid operation', solve(r), True)
check('Promotion permits two writable primaries', solve(dict(r, **{'primary_fenced': False})), False)
check('Standby lag exceeds the declared data-loss allowance', solve(dict(r, **{'rpo_bound': [10, 8, 1]})), False)
check('A read-only standby is advertised as write primary', solve(dict(r, **{'standby_writable': False})), False)
check('A delayed promotion reverses a newer failover', solve(dict(r, **{'promotion_epoch': [5, 5]})), False)
check('Promotion succeeds while writes still route to the old primary', solve(dict(r, **{'routing_switched': ['old', 'new']})), False)
check('unrelated tracing metadata', solve(dict(r, trace='run-'+str(N))), True)
check('repeat validation is pure', solve(r), True)
invalid = {'primary_fenced': False, 'rpo_bound': [10, 8, 1], 'standby_writable': False, 'promotion_epoch': [5, 5], 'routing_switched': ['old', 'new']}
keys = list(invalid)
pair = {keys[N % len(keys)]: invalid[keys[N % len(keys)]], keys[(N+1) % len(keys)]: invalid[keys[(N+1) % len(keys)]]}
check('two independent violations in variant', solve(dict(r, **pair)), False)
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 |
|---|---|---|---|
| valid operation | True | True | Passed |
| Promotion permits two writable primaries | False | False | Passed |
| Standby lag exceeds the declared data-loss allowance | False | False | Passed |
| A read-only standby is advertised as write primary | True | False | Failed |
| A delayed promotion reverses a newer failover | False | False | Passed |
| Promotion succeeds while writes still route to the old primary | False | False | Passed |
| unrelated tracing metadata | True | True | Passed |
| repeat validation is pure | True | True | Passed |
| two independent violations in variant | False | False | Passed |
SHA-256 / ee4cce68b9bff2a6c33625b9efd9ab8e345b2814f2c1d215d12912e1494b67f7
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(r):
return (r['primary_fenced'] is True) and (r['rpo_bound'][0]-r['rpo_bound'][1] <= r['rpo_bound'][2]) and (r['standby_writable'] is True) and (r['routing_switched'][0] == r['routing_switched'][1])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
r = {'primary_fenced': True, 'rpo_bound': [10, 9, 1], 'standby_writable': True, 'promotion_epoch': [6, 5], 'routing_switched': ['new', 'new']}
check('valid operation', solve(r), True)
check('Promotion permits two writable primaries', solve(dict(r, **{'primary_fenced': False})), False)
check('Standby lag exceeds the declared data-loss allowance', solve(dict(r, **{'rpo_bound': [10, 8, 1]})), False)
check('A read-only standby is advertised as write primary', solve(dict(r, **{'standby_writable': False})), False)
check('A delayed promotion reverses a newer failover', solve(dict(r, **{'promotion_epoch': [5, 5]})), False)
check('Promotion succeeds while writes still route to the old primary', solve(dict(r, **{'routing_switched': ['old', 'new']})), False)
check('unrelated tracing metadata', solve(dict(r, trace='run-'+str(N))), True)
check('repeat validation is pure', solve(r), True)
invalid = {'primary_fenced': False, 'rpo_bound': [10, 8, 1], 'standby_writable': False, 'promotion_epoch': [5, 5], 'routing_switched': ['old', 'new']}
keys = list(invalid)
pair = {keys[N % len(keys)]: invalid[keys[N % len(keys)]], keys[(N+1) % len(keys)]: invalid[keys[(N+1) % len(keys)]]}
check('two independent violations in variant', solve(dict(r, **pair)), False)
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 |
|---|---|---|---|
| valid operation | True | True | Passed |
| Promotion permits two writable primaries | False | False | Passed |
| Standby lag exceeds the declared data-loss allowance | False | False | Passed |
| A read-only standby is advertised as write primary | False | False | Passed |
| A delayed promotion reverses a newer failover | True | False | Failed |
| Promotion succeeds while writes still route to the old primary | False | False | Passed |
| unrelated tracing metadata | True | True | Passed |
| repeat validation is pure | True | True | Passed |
| two independent violations in variant | False | False | Passed |
SHA-256 / 6a545fa2ceb0418f818b5fb5f27a1989856606adafbcaf2668947b2208d1af49
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(r):
return (r['primary_fenced'] is True) and (r['rpo_bound'][0]-r['rpo_bound'][1] <= r['rpo_bound'][2]) and (r['standby_writable'] is True) and (r['promotion_epoch'][0] > r['promotion_epoch'][1]) and (r['routing_switched'][0] == r['routing_switched'][1])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
r = {'primary_fenced': True, 'rpo_bound': [10, 9, 1], 'standby_writable': True, 'promotion_epoch': [6, 5], 'routing_switched': ['new', 'new']}
check('valid operation', solve(r), True)
check('Promotion permits two writable primaries', solve(dict(r, **{'primary_fenced': False})), False)
check('Standby lag exceeds the declared data-loss allowance', solve(dict(r, **{'rpo_bound': [10, 8, 1]})), False)
check('A read-only standby is advertised as write primary', solve(dict(r, **{'standby_writable': False})), False)
check('A delayed promotion reverses a newer failover', solve(dict(r, **{'promotion_epoch': [5, 5]})), False)
check('Promotion succeeds while writes still route to the old primary', solve(dict(r, **{'routing_switched': ['old', 'new']})), False)
check('unrelated tracing metadata', solve(dict(r, trace='run-'+str(N))), True)
check('repeat validation is pure', solve(r), True)
invalid = {'primary_fenced': False, 'rpo_bound': [10, 8, 1], 'standby_writable': False, 'promotion_epoch': [5, 5], 'routing_switched': ['old', 'new']}
keys = list(invalid)
pair = {keys[N % len(keys)]: invalid[keys[N % len(keys)]], keys[(N+1) % len(keys)]: invalid[keys[(N+1) % len(keys)]]}
check('two independent violations in variant', solve(dict(r, **pair)), False)
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 |
|---|---|---|---|
| valid operation | True | True | Passed |
| Promotion permits two writable primaries | False | False | Passed |
| Standby lag exceeds the declared data-loss allowance | False | False | Passed |
| A read-only standby is advertised as write primary | False | False | Passed |
| A delayed promotion reverses a newer failover | False | False | Passed |
| Promotion succeeds while writes still route to the old primary | False | False | Passed |
| unrelated tracing metadata | True | True | Passed |
| repeat validation is pure | True | True | Passed |
| two independent violations in variant | False | False | Passed |
SHA-256 / 4d5271ee2d95b6384e0ebb8d873ed589261c47bd05cf7abcb05be97b66331b75
Verification & scope
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:36:58.544502+00:00.
Case digest / 4e28013ebcfa5a2542b1881f7373ba2effd510af314af0a37a94cdec13a20f1c