FAILURE MAP
← Case archive

FA-101 / Distributed coordination / Open access

An aborted transaction publishes an external event · case 01

An event escapes before its associated business state commits, or committed events disappear before dispatch.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Publication is coupled to in-progress transaction state rather than a durable committed outbox.

VERIFIED REPAIR

Commit the staged state and event together, then dispatch undelivered committed outbox entries.

Unsuccessful approach: Waiting until dispatch but retaining only the latest transaction's event loses earlier committed work.

Case contract

Actions are ['stage',ID,value], ['commit'], ['rollback'], or ['dispatch']. Stage replaces an uncommitted pending write; commit atomically stores its value and appends its event; rollback discards pending work. Dispatch publishes each locally pending committed event once. IDs are unique per committed transaction. Return [business value,published event IDs]. No transport crash or exactly-once external-delivery guarantee is modeled.

Why this case matters

Models the database-to-message-broker boundary and why a relay needs a committed durable outbox, while explicitly excluding the separate problem of duplicate deliveries after relay crashes.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(actions):
    pending, business, published = None, None, []
    for action in actions:
        if action[0] == 'stage':
            pending = action[1:]
            published.append(pending[0])
        elif action[0] == 'commit' and pending is not None:
            business = pending[1]
            pending = None
        elif action[0] == 'rollback':
            pending = None
    return [business, published]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('rollback cannot publish', solve([['stage', 'a', N], ['rollback'], ['dispatch']]), [None, []])
check('commit before publication', solve([['stage', 'a', N], ['dispatch']]), [None, []])
check('all committed events survive delay', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['commit'], ['dispatch']]), [N+1, ['a', 'b']])
check('single committed event', solve([['stage', 'a', N], ['commit'], ['dispatch']]), [N, ['a']])
check('repeat dispatch does not resend', solve([['stage', 'a', N], ['commit'], ['dispatch'], ['dispatch']]), [N, ['a']])
check('rollback preserves prior outbox', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['rollback'], ['dispatch']]), [N, ['a']])
check('empty transaction stream', solve([]), [None, []])
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 fixtureActualExpectedOutcome
rollback cannot publish[None, ['a']][None, []]Failed
commit before publication[None, ['a']][None, []]Failed
all committed events survive delay[2, ['a', 'b']][2, ['a', 'b']]Passed
single committed event[1, ['a']][1, ['a']]Passed
repeat dispatch does not resend[1, ['a']][1, ['a']]Passed
rollback preserves prior outbox[1, ['a', 'b']][1, ['a']]Failed
empty transaction stream[None, []][None, []]Passed

SHA-256 / e2eaf6cfb4857b60478876ef9e05381055cefa3ea899779364b94cf3707c019b

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(actions):
    pending, business, latest, published = None, None, None, []
    for action in actions:
        if action[0] == 'stage':
            pending = action[1:]
        elif action[0] == 'commit' and pending is not None:
            business, latest = pending[1], pending[0]
            pending = None
        elif action[0] == 'rollback':
            pending = None
        elif action[0] == 'dispatch' and latest is not None:
            published.append(latest)
            latest = None
    return [business, published]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('rollback cannot publish', solve([['stage', 'a', N], ['rollback'], ['dispatch']]), [None, []])
check('commit before publication', solve([['stage', 'a', N], ['dispatch']]), [None, []])
check('all committed events survive delay', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['commit'], ['dispatch']]), [N+1, ['a', 'b']])
check('single committed event', solve([['stage', 'a', N], ['commit'], ['dispatch']]), [N, ['a']])
check('repeat dispatch does not resend', solve([['stage', 'a', N], ['commit'], ['dispatch'], ['dispatch']]), [N, ['a']])
check('rollback preserves prior outbox', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['rollback'], ['dispatch']]), [N, ['a']])
check('empty transaction stream', solve([]), [None, []])
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 fixtureActualExpectedOutcome
rollback cannot publish[None, []][None, []]Passed
commit before publication[None, []][None, []]Passed
all committed events survive delay[2, ['b']][2, ['a', 'b']]Failed
single committed event[1, ['a']][1, ['a']]Passed
repeat dispatch does not resend[1, ['a']][1, ['a']]Passed
rollback preserves prior outbox[1, ['a']][1, ['a']]Passed
empty transaction stream[None, []][None, []]Passed

SHA-256 / 13b47db6a04e5f5fe92b0aafd2ee16bf958f52ceccdf4c688bb8ace86d5f6463

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(actions):
    pending, business, outbox, published = None, None, [], []
    for action in actions:
        if action[0] == 'stage':
            pending = action[1:]
        elif action[0] == 'commit' and pending is not None:
            business = pending[1]
            outbox.append(pending[0])
            pending = None
        elif action[0] == 'rollback':
            pending = None
        elif action[0] == 'dispatch':
            published.extend(outbox)
            outbox = []
    return [business, published]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('rollback cannot publish', solve([['stage', 'a', N], ['rollback'], ['dispatch']]), [None, []])
check('commit before publication', solve([['stage', 'a', N], ['dispatch']]), [None, []])
check('all committed events survive delay', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['commit'], ['dispatch']]), [N+1, ['a', 'b']])
check('single committed event', solve([['stage', 'a', N], ['commit'], ['dispatch']]), [N, ['a']])
check('repeat dispatch does not resend', solve([['stage', 'a', N], ['commit'], ['dispatch'], ['dispatch']]), [N, ['a']])
check('rollback preserves prior outbox', solve([['stage', 'a', N], ['commit'], ['stage', 'b', N+1], ['rollback'], ['dispatch']]), [N, ['a']])
check('empty transaction stream', solve([]), [None, []])
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 fixtureActualExpectedOutcome
rollback cannot publish[None, []][None, []]Passed
commit before publication[None, []][None, []]Passed
all committed events survive delay[2, ['a', 'b']][2, ['a', 'b']]Passed
single committed event[1, ['a']][1, ['a']]Passed
repeat dispatch does not resend[1, ['a']][1, ['a']]Passed
rollback preserves prior outbox[1, ['a']][1, ['a']]Passed
empty transaction stream[None, []][None, []]Passed

SHA-256 / 7ecc29ca30dfff0bd15f1b6bdb77bc49947185d74efe7e7c2eba96a98398a088

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:50.453163+00:00.

Case digest / e23424818803346e4159b7f3441d8e195651337bf05dcd679eec84b82e066437