broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'both', ('repromoting', 'banner-mounted'): 'both'}
    data = json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})) if payload is None else {**json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('save-success','save-failure') and arg.get('revision',data['save_revision']) != data['save_revision']: event = 'unknown-event'
            state = table.get((state, event), state)
            if state in ('both','inbox-only') and data['item'] not in data['inbox']: data['inbox'].append(data['item'])
            data['banner'] = state in ('banner','both','saving','save-error','recovery-banner')
            if state == 'gone': data['inbox'] = [x for x in data['inbox'] if x != data['item']]
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('banner / retain-in-inbox', solve('banner', [('retain-in-inbox', N)], N), 'saving')
check('saving / save-success', solve('saving', [('save-success', N)], N), 'both')
check('saving / save-failure', solve('saving', [('save-failure', N)], N), 'save-error')
check('both / banner-exposure-end', solve('both', [('banner-exposure-end', N)], N), 'inbox-only')
check('save-error / retry-retention', solve('save-error', [('retry-retention', N)], N), 'saving')
check('saving / banner-exposure-end', solve('saving', [('banner-exposure-end', N)], N), 'saving-without-banner')
check('saving-without-banner / save-success', solve('saving-without-banner', [('save-success', N)], N), 'inbox-only')
check('saving-without-banner / save-failure', solve('saving-without-banner', [('save-failure', N)], N), 'recovery-banner')
check('inbox-only / show-banner-again', solve('inbox-only', [('show-banner-again', N)], N), 'repromoting')
check('repromoting / banner-mounted', solve('repromoting', [('banner-mounted', N)], N), 'both')
check('older notification incarnation', solve('banner', [('retain-in-inbox', N - 1)], N), 'banner')
check('future notification incarnation', solve('banner', [('retain-in-inbox', N + 1)], N), 'banner')
check('unknown event is inert', solve('banner', [('unknown-event', N)], N), 'banner')
check('empty delivery batch', solve('banner', [], N), 'banner')
check('N stale deliveries before current delivery', solve('banner', [('retain-in-inbox', N - 1)] * N + [('retain-in-inbox', N)], N), 'saving')
check('trace retain-in-inbox then save-success', solve('banner', [('retain-in-inbox', N), ('save-success', N)], N), 'both')
check('trace retain-in-inbox then save-failure', solve('banner', [('retain-in-inbox', N), ('save-failure', N)], N), 'save-error')
check('trace retain-in-inbox then banner-exposure-end', solve('banner', [('retain-in-inbox', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace save-success then banner-exposure-end', solve('saving', [('save-success', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('trace save-failure then retry-retention', solve('saving', [('save-failure', N), ('retry-retention', N)], N), 'saving')
check('trace banner-exposure-end then show-banner-again', solve('both', [('banner-exposure-end', N), ('show-banner-again', N)], N), 'repromoting')
check('trace retry-retention then save-success', solve('save-error', [('retry-retention', N), ('save-success', N)], N), 'both')
check('trace retry-retention then save-failure', solve('save-error', [('retry-retention', N), ('save-failure', N)], N), 'save-error')
check('trace retry-retention then banner-exposure-end', solve('save-error', [('retry-retention', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace banner-exposure-end then save-success', solve('saving', [('banner-exposure-end', N), ('save-success', N)], N), 'inbox-only')
check('trace banner-exposure-end then save-failure', solve('saving', [('banner-exposure-end', N), ('save-failure', N)], N), 'recovery-banner')
check('trace save-success then show-banner-again', solve('saving-without-banner', [('save-success', N), ('show-banner-again', N)], N), 'repromoting')
check('trace show-banner-again then banner-mounted', solve('inbox-only', [('show-banner-again', N), ('banner-mounted', N)], N), 'both')
check('trace banner-mounted then banner-exposure-end', solve('repromoting', [('banner-mounted', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('domain state regression 1: inbox', solve('saving', [('save-success', N, {'revision': 1})], N, {}, 'inbox'), {'phase': 'saving', 'value': []})
check('domain state regression 2: inbox', solve('saving', [('save-success', N, {'revision': 2})], N, {'inbox': ['other']}, 'inbox'), {'phase': 'both', 'value': ['other', 'n']})
check('domain state regression 3: inbox', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'inbox'), {'phase': 'inbox-only', 'value': ['n']})
check('domain state regression 4: banner', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 5: banner', solve('saving-without-banner', [('save-failure', N, {})], N, {'banner': False}, 'banner'), {'phase': 'recovery-banner', 'value': True})
check('domain state regression 6: banner', solve('saving-without-banner', [('save-success', N, {})], N, {'banner': False}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 7: banner', solve('banner', [('retain-in-inbox', N, {}), ('banner-exposure-end', N, {}), ('save-success', N, {})], N, {}, 'banner'), {'phase': 'inbox-only', 'value': 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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'banner', ('repromoting', 'banner-mounted'): 'both'}
    data = json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})) if payload is None else {**json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('save-success','save-failure') and arg.get('revision',data['save_revision']) != data['save_revision']: event = 'unknown-event'
            state = table.get((state, event), state)
            if state in ('both','inbox-only') and data['item'] not in data['inbox']: data['inbox'].append(data['item'])
            data['banner'] = state in ('banner','both','saving','save-error','recovery-banner')
            if state == 'gone': data['inbox'] = [x for x in data['inbox'] if x != data['item']]
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('banner / retain-in-inbox', solve('banner', [('retain-in-inbox', N)], N), 'saving')
check('saving / save-success', solve('saving', [('save-success', N)], N), 'both')
check('saving / save-failure', solve('saving', [('save-failure', N)], N), 'save-error')
check('both / banner-exposure-end', solve('both', [('banner-exposure-end', N)], N), 'inbox-only')
check('save-error / retry-retention', solve('save-error', [('retry-retention', N)], N), 'saving')
check('saving / banner-exposure-end', solve('saving', [('banner-exposure-end', N)], N), 'saving-without-banner')
check('saving-without-banner / save-success', solve('saving-without-banner', [('save-success', N)], N), 'inbox-only')
check('saving-without-banner / save-failure', solve('saving-without-banner', [('save-failure', N)], N), 'recovery-banner')
check('inbox-only / show-banner-again', solve('inbox-only', [('show-banner-again', N)], N), 'repromoting')
check('repromoting / banner-mounted', solve('repromoting', [('banner-mounted', N)], N), 'both')
check('older notification incarnation', solve('banner', [('retain-in-inbox', N - 1)], N), 'banner')
check('future notification incarnation', solve('banner', [('retain-in-inbox', N + 1)], N), 'banner')
check('unknown event is inert', solve('banner', [('unknown-event', N)], N), 'banner')
check('empty delivery batch', solve('banner', [], N), 'banner')
check('N stale deliveries before current delivery', solve('banner', [('retain-in-inbox', N - 1)] * N + [('retain-in-inbox', N)], N), 'saving')
check('trace retain-in-inbox then save-success', solve('banner', [('retain-in-inbox', N), ('save-success', N)], N), 'both')
check('trace retain-in-inbox then save-failure', solve('banner', [('retain-in-inbox', N), ('save-failure', N)], N), 'save-error')
check('trace retain-in-inbox then banner-exposure-end', solve('banner', [('retain-in-inbox', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace save-success then banner-exposure-end', solve('saving', [('save-success', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('trace save-failure then retry-retention', solve('saving', [('save-failure', N), ('retry-retention', N)], N), 'saving')
check('trace banner-exposure-end then show-banner-again', solve('both', [('banner-exposure-end', N), ('show-banner-again', N)], N), 'repromoting')
check('trace retry-retention then save-success', solve('save-error', [('retry-retention', N), ('save-success', N)], N), 'both')
check('trace retry-retention then save-failure', solve('save-error', [('retry-retention', N), ('save-failure', N)], N), 'save-error')
check('trace retry-retention then banner-exposure-end', solve('save-error', [('retry-retention', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace banner-exposure-end then save-success', solve('saving', [('banner-exposure-end', N), ('save-success', N)], N), 'inbox-only')
check('trace banner-exposure-end then save-failure', solve('saving', [('banner-exposure-end', N), ('save-failure', N)], N), 'recovery-banner')
check('trace save-success then show-banner-again', solve('saving-without-banner', [('save-success', N), ('show-banner-again', N)], N), 'repromoting')
check('trace show-banner-again then banner-mounted', solve('inbox-only', [('show-banner-again', N), ('banner-mounted', N)], N), 'both')
check('trace banner-mounted then banner-exposure-end', solve('repromoting', [('banner-mounted', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('domain state regression 1: inbox', solve('saving', [('save-success', N, {'revision': 1})], N, {}, 'inbox'), {'phase': 'saving', 'value': []})
check('domain state regression 2: inbox', solve('saving', [('save-success', N, {'revision': 2})], N, {'inbox': ['other']}, 'inbox'), {'phase': 'both', 'value': ['other', 'n']})
check('domain state regression 3: inbox', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'inbox'), {'phase': 'inbox-only', 'value': ['n']})
check('domain state regression 4: banner', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 5: banner', solve('saving-without-banner', [('save-failure', N, {})], N, {'banner': False}, 'banner'), {'phase': 'recovery-banner', 'value': True})
check('domain state regression 6: banner', solve('saving-without-banner', [('save-success', N, {})], N, {'banner': False}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 7: banner', solve('banner', [('retain-in-inbox', N, {}), ('banner-exposure-end', N, {}), ('save-success', N, {})], N, {}, 'banner'), {'phase': 'inbox-only', 'value': 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)

prompt:
The show-banner-again event leaves a notification in both instead of repromoting.

Banner to inbox promotion is a bounded visual-notification workflow with mutable policy state {'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2}. Its default transition relation is {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'repromoting', ('repromoting', 'banner-mounted'): 'both'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'repromoting', ('repromoting', 'banner-mounted'): 'inbox-only'}
    data = json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})) if payload is None else {**json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('save-success','save-failure') and arg.get('revision',data['save_revision']) != data['save_revision']: event = 'unknown-event'
            state = table.get((state, event), state)
            if state in ('both','inbox-only') and data['item'] not in data['inbox']: data['inbox'].append(data['item'])
            data['banner'] = state in ('banner','both','saving','save-error','recovery-banner')
            if state == 'gone': data['inbox'] = [x for x in data['inbox'] if x != data['item']]
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('banner / retain-in-inbox', solve('banner', [('retain-in-inbox', N)], N), 'saving')
check('saving / save-success', solve('saving', [('save-success', N)], N), 'both')
check('saving / save-failure', solve('saving', [('save-failure', N)], N), 'save-error')
check('both / banner-exposure-end', solve('both', [('banner-exposure-end', N)], N), 'inbox-only')
check('save-error / retry-retention', solve('save-error', [('retry-retention', N)], N), 'saving')
check('saving / banner-exposure-end', solve('saving', [('banner-exposure-end', N)], N), 'saving-without-banner')
check('saving-without-banner / save-success', solve('saving-without-banner', [('save-success', N)], N), 'inbox-only')
check('saving-without-banner / save-failure', solve('saving-without-banner', [('save-failure', N)], N), 'recovery-banner')
check('inbox-only / show-banner-again', solve('inbox-only', [('show-banner-again', N)], N), 'repromoting')
check('repromoting / banner-mounted', solve('repromoting', [('banner-mounted', N)], N), 'both')
check('older notification incarnation', solve('banner', [('retain-in-inbox', N - 1)], N), 'banner')
check('future notification incarnation', solve('banner', [('retain-in-inbox', N + 1)], N), 'banner')
check('unknown event is inert', solve('banner', [('unknown-event', N)], N), 'banner')
check('empty delivery batch', solve('banner', [], N), 'banner')
check('N stale deliveries before current delivery', solve('banner', [('retain-in-inbox', N - 1)] * N + [('retain-in-inbox', N)], N), 'saving')
check('trace retain-in-inbox then save-success', solve('banner', [('retain-in-inbox', N), ('save-success', N)], N), 'both')
check('trace retain-in-inbox then save-failure', solve('banner', [('retain-in-inbox', N), ('save-failure', N)], N), 'save-error')
check('trace retain-in-inbox then banner-exposure-end', solve('banner', [('retain-in-inbox', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace save-success then banner-exposure-end', solve('saving', [('save-success', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('trace save-failure then retry-retention', solve('saving', [('save-failure', N), ('retry-retention', N)], N), 'saving')
check('trace banner-exposure-end then show-banner-again', solve('both', [('banner-exposure-end', N), ('show-banner-again', N)], N), 'repromoting')
check('trace retry-retention then save-success', solve('save-error', [('retry-retention', N), ('save-success', N)], N), 'both')
check('trace retry-retention then save-failure', solve('save-error', [('retry-retention', N), ('save-failure', N)], N), 'save-error')
check('trace retry-retention then banner-exposure-end', solve('save-error', [('retry-retention', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace banner-exposure-end then save-success', solve('saving', [('banner-exposure-end', N), ('save-success', N)], N), 'inbox-only')
check('trace banner-exposure-end then save-failure', solve('saving', [('banner-exposure-end', N), ('save-failure', N)], N), 'recovery-banner')
check('trace save-success then show-banner-again', solve('saving-without-banner', [('save-success', N), ('show-banner-again', N)], N), 'repromoting')
check('trace show-banner-again then banner-mounted', solve('inbox-only', [('show-banner-again', N), ('banner-mounted', N)], N), 'both')
check('trace banner-mounted then banner-exposure-end', solve('repromoting', [('banner-mounted', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('domain state regression 1: inbox', solve('saving', [('save-success', N, {'revision': 1})], N, {}, 'inbox'), {'phase': 'saving', 'value': []})
check('domain state regression 2: inbox', solve('saving', [('save-success', N, {'revision': 2})], N, {'inbox': ['other']}, 'inbox'), {'phase': 'both', 'value': ['other', 'n']})
check('domain state regression 3: inbox', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'inbox'), {'phase': 'inbox-only', 'value': ['n']})
check('domain state regression 4: banner', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 5: banner', solve('saving-without-banner', [('save-failure', N, {})], N, {'banner': False}, 'banner'), {'phase': 'recovery-banner', 'value': True})
check('domain state regression 6: banner', solve('saving-without-banner', [('save-success', N, {})], N, {'banner': False}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 7: banner', solve('banner', [('retain-in-inbox', N, {}), ('banner-exposure-end', N, {}), ('save-success', N, {})], N, {}, 'banner'), {'phase': 'inbox-only', 'value': 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)

meaning: Fixture-count band, not measured model difficulty

deps: stdlib

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'repromoting', ('repromoting', 'banner-mounted'): 'banner'}
    data = json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})) if payload is None else {**json.loads(json.dumps({'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('save-success','save-failure') and arg.get('revision',data['save_revision']) != data['save_revision']: event = 'unknown-event'
            state = table.get((state, event), state)
            if state in ('both','inbox-only') and data['item'] not in data['inbox']: data['inbox'].append(data['item'])
            data['banner'] = state in ('banner','both','saving','save-error','recovery-banner')
            if state == 'gone': data['inbox'] = [x for x in data['inbox'] if x != data['item']]
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('banner / retain-in-inbox', solve('banner', [('retain-in-inbox', N)], N), 'saving')
check('saving / save-success', solve('saving', [('save-success', N)], N), 'both')
check('saving / save-failure', solve('saving', [('save-failure', N)], N), 'save-error')
check('both / banner-exposure-end', solve('both', [('banner-exposure-end', N)], N), 'inbox-only')
check('save-error / retry-retention', solve('save-error', [('retry-retention', N)], N), 'saving')
check('saving / banner-exposure-end', solve('saving', [('banner-exposure-end', N)], N), 'saving-without-banner')
check('saving-without-banner / save-success', solve('saving-without-banner', [('save-success', N)], N), 'inbox-only')
check('saving-without-banner / save-failure', solve('saving-without-banner', [('save-failure', N)], N), 'recovery-banner')
check('inbox-only / show-banner-again', solve('inbox-only', [('show-banner-again', N)], N), 'repromoting')
check('repromoting / banner-mounted', solve('repromoting', [('banner-mounted', N)], N), 'both')
check('older notification incarnation', solve('banner', [('retain-in-inbox', N - 1)], N), 'banner')
check('future notification incarnation', solve('banner', [('retain-in-inbox', N + 1)], N), 'banner')
check('unknown event is inert', solve('banner', [('unknown-event', N)], N), 'banner')
check('empty delivery batch', solve('banner', [], N), 'banner')
check('N stale deliveries before current delivery', solve('banner', [('retain-in-inbox', N - 1)] * N + [('retain-in-inbox', N)], N), 'saving')
check('trace retain-in-inbox then save-success', solve('banner', [('retain-in-inbox', N), ('save-success', N)], N), 'both')
check('trace retain-in-inbox then save-failure', solve('banner', [('retain-in-inbox', N), ('save-failure', N)], N), 'save-error')
check('trace retain-in-inbox then banner-exposure-end', solve('banner', [('retain-in-inbox', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace save-success then banner-exposure-end', solve('saving', [('save-success', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('trace save-failure then retry-retention', solve('saving', [('save-failure', N), ('retry-retention', N)], N), 'saving')
check('trace banner-exposure-end then show-banner-again', solve('both', [('banner-exposure-end', N), ('show-banner-again', N)], N), 'repromoting')
check('trace retry-retention then save-success', solve('save-error', [('retry-retention', N), ('save-success', N)], N), 'both')
check('trace retry-retention then save-failure', solve('save-error', [('retry-retention', N), ('save-failure', N)], N), 'save-error')
check('trace retry-retention then banner-exposure-end', solve('save-error', [('retry-retention', N), ('banner-exposure-end', N)], N), 'saving-without-banner')
check('trace banner-exposure-end then save-success', solve('saving', [('banner-exposure-end', N), ('save-success', N)], N), 'inbox-only')
check('trace banner-exposure-end then save-failure', solve('saving', [('banner-exposure-end', N), ('save-failure', N)], N), 'recovery-banner')
check('trace save-success then show-banner-again', solve('saving-without-banner', [('save-success', N), ('show-banner-again', N)], N), 'repromoting')
check('trace show-banner-again then banner-mounted', solve('inbox-only', [('show-banner-again', N), ('banner-mounted', N)], N), 'both')
check('trace banner-mounted then banner-exposure-end', solve('repromoting', [('banner-mounted', N), ('banner-exposure-end', N)], N), 'inbox-only')
check('domain state regression 1: inbox', solve('saving', [('save-success', N, {'revision': 1})], N, {}, 'inbox'), {'phase': 'saving', 'value': []})
check('domain state regression 2: inbox', solve('saving', [('save-success', N, {'revision': 2})], N, {'inbox': ['other']}, 'inbox'), {'phase': 'both', 'value': ['other', 'n']})
check('domain state regression 3: inbox', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'inbox'), {'phase': 'inbox-only', 'value': ['n']})
check('domain state regression 4: banner', solve('both', [('banner-exposure-end', N, {})], N, {'inbox': ['n']}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 5: banner', solve('saving-without-banner', [('save-failure', N, {})], N, {'banner': False}, 'banner'), {'phase': 'recovery-banner', 'value': True})
check('domain state regression 6: banner', solve('saving-without-banner', [('save-success', N, {})], N, {'banner': False}, 'banner'), {'phase': 'inbox-only', 'value': False})
check('domain state regression 7: banner', solve('banner', [('retain-in-inbox', N, {}), ('banner-exposure-end', N, {}), ('save-success', N, {})], N, {}, 'banner'), {'phase': 'inbox-only', 'value': 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)

prompt:
The banner-mounted event leaves a notification in inbox-only instead of both.

Banner to inbox promotion is a bounded visual-notification workflow with mutable policy state {'banner': True, 'inbox': [], 'item': 'n', 'save_revision': 2}. Its default transition relation is {('banner', 'retain-in-inbox'): 'saving', ('saving', 'save-success'): 'both', ('saving', 'save-failure'): 'save-error', ('both', 'banner-exposure-end'): 'inbox-only', ('save-error', 'retry-retention'): 'saving', ('saving', 'banner-exposure-end'): 'saving-without-banner', ('saving-without-banner', 'save-success'): 'inbox-only', ('saving-without-banner', 'save-failure'): 'recovery-banner', ('inbox-only', 'show-banner-again'): 'repromoting', ('repromoting', 'banner-mounted'): 'both'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dismissed', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'returning', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

prompt:
The drag-start event leaves a notification in dismissed instead of dragging.

Swipe-to-dismiss notification feedback is a bounded visual-notification workflow with mutable policy state {'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False}. Its default transition relation is {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'dismissed', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'committing', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

prompt:
The below-threshold-release event leaves a notification in dismissed instead of returning.

Swipe-to-dismiss notification feedback is a bounded visual-notification workflow with mutable policy state {'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False}. Its default transition relation is {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'dismissed', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'returning', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

prompt:
The above-threshold-release event leaves a notification in dismissed instead of committing.

Swipe-to-dismiss notification feedback is a bounded visual-notification workflow with mutable policy state {'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False}. Its default transition relation is {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'dismissed', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(initial, events, generation, payload=None, observe=None):
    table = {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'dragging', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}
    data = json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})) if payload is None else {**json.loads(json.dumps({'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False})), **json.loads(json.dumps(payload))}
    state = initial
    for delivery in events:
        event, event_generation = delivery[:2]
        arg = delivery[2] if len(delivery) > 2 else {}
        if event_generation == generation:
            previous = state
            if event in ('below-threshold-release','above-threshold-release','pointer-cancel') and arg.get('pointer',data['pointer']) != data['pointer']: event = 'unknown-event'
            if event in ('below-threshold-release','above-threshold-release') and 'offset' in arg:
                data['offset'] = arg['offset']
                event = 'above-threshold-release' if abs(data['offset']) >= data['threshold'] else 'below-threshold-release'
            state = table.get((state, event), state)
            if state == 'resting': data['offset'] = 0
            if state == 'dismissed': data['removed'] = True
    return {'phase': state, 'value': data[observe]} if observe else state
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('resting / drag-start', solve('resting', [('drag-start', N)], N), 'dragging')
check('dragging / below-threshold-release', solve('dragging', [('below-threshold-release', N)], N), 'returning')
check('dragging / above-threshold-release', solve('dragging', [('above-threshold-release', N)], N), 'committing')
check('returning / snapback-finished', solve('returning', [('snapback-finished', N)], N), 'resting')
check('committing / dismiss-accepted', solve('committing', [('dismiss-accepted', N)], N), 'exit-animating')
check('committing / dismiss-rejected', solve('committing', [('dismiss-rejected', N)], N), 'returning')
check('exit-animating / animation-finished', solve('exit-animating', [('animation-finished', N)], N), 'dismissed')
check('dragging / pointer-cancel', solve('dragging', [('pointer-cancel', N)], N), 'returning')
check('exit-animating / reduced-motion-enabled', solve('exit-animating', [('reduced-motion-enabled', N)], N), 'dismissed')
check('committing / second-pointer-down', solve('committing', [('second-pointer-down', N)], N), 'committing')
check('older notification incarnation', solve('resting', [('drag-start', N - 1)], N), 'resting')
check('future notification incarnation', solve('resting', [('drag-start', N + 1)], N), 'resting')
check('unknown event is inert', solve('resting', [('unknown-event', N)], N), 'resting')
check('empty delivery batch', solve('resting', [], N), 'resting')
check('N stale deliveries before current delivery', solve('resting', [('drag-start', N - 1)] * N + [('drag-start', N)], N), 'dragging')
check('trace drag-start then below-threshold-release', solve('resting', [('drag-start', N), ('below-threshold-release', N)], N), 'returning')
check('trace drag-start then above-threshold-release', solve('resting', [('drag-start', N), ('above-threshold-release', N)], N), 'committing')
check('trace drag-start then pointer-cancel', solve('resting', [('drag-start', N), ('pointer-cancel', N)], N), 'returning')
check('trace below-threshold-release then snapback-finished', solve('dragging', [('below-threshold-release', N), ('snapback-finished', N)], N), 'resting')
check('trace above-threshold-release then dismiss-accepted', solve('dragging', [('above-threshold-release', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace above-threshold-release then dismiss-rejected', solve('dragging', [('above-threshold-release', N), ('dismiss-rejected', N)], N), 'returning')
check('trace above-threshold-release then second-pointer-down', solve('dragging', [('above-threshold-release', N), ('second-pointer-down', N)], N), 'committing')
check('trace snapback-finished then drag-start', solve('returning', [('snapback-finished', N), ('drag-start', N)], N), 'dragging')
check('trace dismiss-accepted then animation-finished', solve('committing', [('dismiss-accepted', N), ('animation-finished', N)], N), 'dismissed')
check('trace dismiss-accepted then reduced-motion-enabled', solve('committing', [('dismiss-accepted', N), ('reduced-motion-enabled', N)], N), 'dismissed')
check('trace dismiss-rejected then snapback-finished', solve('committing', [('dismiss-rejected', N), ('snapback-finished', N)], N), 'resting')
check('trace pointer-cancel then snapback-finished', solve('dragging', [('pointer-cancel', N), ('snapback-finished', N)], N), 'resting')
check('trace second-pointer-down then dismiss-accepted', solve('committing', [('second-pointer-down', N), ('dismiss-accepted', N)], N), 'exit-animating')
check('trace second-pointer-down then dismiss-rejected', solve('committing', [('second-pointer-down', N), ('dismiss-rejected', N)], N), 'returning')
check('trace second-pointer-down then second-pointer-down', solve('committing', [('second-pointer-down', N), ('second-pointer-down', N)], N), 'committing')
check('domain state regression 1: offset', solve('dragging', [('above-threshold-release', N, {'offset': 39})], N, {}, 'offset'), {'phase': 'returning', 'value': 39})
check('domain state regression 2: offset', solve('dragging', [('below-threshold-release', N, {'offset': -40})], N, {}, 'offset'), {'phase': 'committing', 'value': -40})
check('domain state regression 3: offset', solve('dragging', [('above-threshold-release', N, {'pointer': 6, 'offset': 90})], N, {}, 'offset'), {'phase': 'dragging', 'value': 0})
check('domain state regression 4: offset', solve('returning', [('snapback-finished', N, {})], N, {'offset': 25}, 'offset'), {'phase': 'resting', 'value': 0})
check('domain state regression 5: removed', solve('exit-animating', [('reduced-motion-enabled', N, {})], N, {}, 'removed'), {'phase': 'dismissed', 'value': True})
check('domain state regression 6: removed', solve('committing', [('dismiss-rejected', N, {})], N, {'offset': 50}, 'removed'), {'phase': 'returning', 'value': False})
check('domain state regression 7: offset', solve('resting', [('drag-start', N, {}), ('above-threshold-release', N, {'offset': 39}), ('snapback-finished', N, {})], N, {}, 'offset'), {'phase': 'resting', 'value': 0})
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)

prompt:
The snapback-finished event leaves a notification in dismissed instead of resting.

Swipe-to-dismiss notification feedback is a bounded visual-notification workflow with mutable policy state {'offset': 0, 'threshold': 40, 'pointer': 7, 'removed': False}. Its default transition relation is {('resting', 'drag-start'): 'dragging', ('dragging', 'below-threshold-release'): 'returning', ('dragging', 'above-threshold-release'): 'committing', ('returning', 'snapback-finished'): 'resting', ('committing', 'dismiss-accepted'): 'exit-animating', ('committing', 'dismiss-rejected'): 'returning', ('exit-animating', 'animation-finished'): 'dismissed', ('dragging', 'pointer-cancel'): 'returning', ('exit-animating', 'reduced-motion-enabled'): 'dismissed', ('committing', 'second-pointer-down'): 'committing'}; domain inputs can suppress or redirect transitions and update policy fields, as specified in solve. Different-generation events are inert. Batches apply in order. Optional observe returns the selected policy field alongside the phase.

split: open-access