FAILURE MAP
← Case archive

FA-34486 / Notification interfaces / Open access

Banner to inbox promotion: banner-mounted from repromoting · case 01

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

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

ROOT CAUSE

Successful promotion never activates the banner.

VERIFIED REPAIR

Commit the repromoting / banner-mounted transition to both; preserve the other explicitly stipulated transitions.

Unsuccessful approach: Showing only the banner removes the saved copy.

Case contract

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.

Why this case matters

Visual notification lifecycle ordering can leave an inbox or toast showing the wrong actionable state even when transport succeeds.

1 / The failure

Exit 1
"""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)
Boundary fixtureActualExpectedOutcome
banner / retain-in-inboxsavingsavingPassed
saving / save-successbothbothPassed
saving / save-failuresave-errorsave-errorPassed
both / banner-exposure-endinbox-onlyinbox-onlyPassed
save-error / retry-retentionsavingsavingPassed
saving / banner-exposure-endsaving-without-bannersaving-without-bannerPassed
saving-without-banner / save-successinbox-onlyinbox-onlyPassed
saving-without-banner / save-failurerecovery-bannerrecovery-bannerPassed
inbox-only / show-banner-againrepromotingrepromotingPassed
repromoting / banner-mountedinbox-onlybothFailed
older notification incarnationbannerbannerPassed
future notification incarnationbannerbannerPassed
unknown event is inertbannerbannerPassed
empty delivery batchbannerbannerPassed
N stale deliveries before current deliverysavingsavingPassed
trace retain-in-inbox then save-successbothbothPassed
trace retain-in-inbox then save-failuresave-errorsave-errorPassed
trace retain-in-inbox then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace save-success then banner-exposure-endinbox-onlyinbox-onlyPassed
trace save-failure then retry-retentionsavingsavingPassed
trace banner-exposure-end then show-banner-againrepromotingrepromotingPassed
trace retry-retention then save-successbothbothPassed
trace retry-retention then save-failuresave-errorsave-errorPassed
trace retry-retention then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace banner-exposure-end then save-successinbox-onlyinbox-onlyPassed
trace banner-exposure-end then save-failurerecovery-bannerrecovery-bannerPassed
trace save-success then show-banner-againrepromotingrepromotingPassed
trace show-banner-again then banner-mountedinbox-onlybothFailed
trace banner-mounted then banner-exposure-endinbox-onlyinbox-onlyPassed
domain state regression 1: inbox{'phase': 'saving', 'value': []}{'phase': 'saving', 'value': []}Passed
domain state regression 2: inbox{'phase': 'both', 'value': ['other', 'n']}{'phase': 'both', 'value': ['other', 'n']}Passed
domain state regression 3: inbox{'phase': 'inbox-only', 'value': ['n']}{'phase': 'inbox-only', 'value': ['n']}Passed
domain state regression 4: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 5: banner{'phase': 'recovery-banner', 'value': True}{'phase': 'recovery-banner', 'value': True}Passed
domain state regression 6: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 7: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed

SHA-256 / 66d09beead2a6ef3218d826a5daddd839bfa803fd92ca14cd011bc60a2ee9aed

2 / The unsuccessful fix

Exit 1
"""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)
Boundary fixtureActualExpectedOutcome
banner / retain-in-inboxsavingsavingPassed
saving / save-successbothbothPassed
saving / save-failuresave-errorsave-errorPassed
both / banner-exposure-endinbox-onlyinbox-onlyPassed
save-error / retry-retentionsavingsavingPassed
saving / banner-exposure-endsaving-without-bannersaving-without-bannerPassed
saving-without-banner / save-successinbox-onlyinbox-onlyPassed
saving-without-banner / save-failurerecovery-bannerrecovery-bannerPassed
inbox-only / show-banner-againrepromotingrepromotingPassed
repromoting / banner-mountedbannerbothFailed
older notification incarnationbannerbannerPassed
future notification incarnationbannerbannerPassed
unknown event is inertbannerbannerPassed
empty delivery batchbannerbannerPassed
N stale deliveries before current deliverysavingsavingPassed
trace retain-in-inbox then save-successbothbothPassed
trace retain-in-inbox then save-failuresave-errorsave-errorPassed
trace retain-in-inbox then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace save-success then banner-exposure-endinbox-onlyinbox-onlyPassed
trace save-failure then retry-retentionsavingsavingPassed
trace banner-exposure-end then show-banner-againrepromotingrepromotingPassed
trace retry-retention then save-successbothbothPassed
trace retry-retention then save-failuresave-errorsave-errorPassed
trace retry-retention then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace banner-exposure-end then save-successinbox-onlyinbox-onlyPassed
trace banner-exposure-end then save-failurerecovery-bannerrecovery-bannerPassed
trace save-success then show-banner-againrepromotingrepromotingPassed
trace show-banner-again then banner-mountedbannerbothFailed
trace banner-mounted then banner-exposure-endbannerinbox-onlyFailed
domain state regression 1: inbox{'phase': 'saving', 'value': []}{'phase': 'saving', 'value': []}Passed
domain state regression 2: inbox{'phase': 'both', 'value': ['other', 'n']}{'phase': 'both', 'value': ['other', 'n']}Passed
domain state regression 3: inbox{'phase': 'inbox-only', 'value': ['n']}{'phase': 'inbox-only', 'value': ['n']}Passed
domain state regression 4: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 5: banner{'phase': 'recovery-banner', 'value': True}{'phase': 'recovery-banner', 'value': True}Passed
domain state regression 6: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 7: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed

SHA-256 / 55f08aca4e2afaa045f6609b2439a9f0ecef2eedc89d9490768c3c31c151a7e0

3 / The verified repair

Exit 0
"""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'): '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)
Boundary fixtureActualExpectedOutcome
banner / retain-in-inboxsavingsavingPassed
saving / save-successbothbothPassed
saving / save-failuresave-errorsave-errorPassed
both / banner-exposure-endinbox-onlyinbox-onlyPassed
save-error / retry-retentionsavingsavingPassed
saving / banner-exposure-endsaving-without-bannersaving-without-bannerPassed
saving-without-banner / save-successinbox-onlyinbox-onlyPassed
saving-without-banner / save-failurerecovery-bannerrecovery-bannerPassed
inbox-only / show-banner-againrepromotingrepromotingPassed
repromoting / banner-mountedbothbothPassed
older notification incarnationbannerbannerPassed
future notification incarnationbannerbannerPassed
unknown event is inertbannerbannerPassed
empty delivery batchbannerbannerPassed
N stale deliveries before current deliverysavingsavingPassed
trace retain-in-inbox then save-successbothbothPassed
trace retain-in-inbox then save-failuresave-errorsave-errorPassed
trace retain-in-inbox then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace save-success then banner-exposure-endinbox-onlyinbox-onlyPassed
trace save-failure then retry-retentionsavingsavingPassed
trace banner-exposure-end then show-banner-againrepromotingrepromotingPassed
trace retry-retention then save-successbothbothPassed
trace retry-retention then save-failuresave-errorsave-errorPassed
trace retry-retention then banner-exposure-endsaving-without-bannersaving-without-bannerPassed
trace banner-exposure-end then save-successinbox-onlyinbox-onlyPassed
trace banner-exposure-end then save-failurerecovery-bannerrecovery-bannerPassed
trace save-success then show-banner-againrepromotingrepromotingPassed
trace show-banner-again then banner-mountedbothbothPassed
trace banner-mounted then banner-exposure-endinbox-onlyinbox-onlyPassed
domain state regression 1: inbox{'phase': 'saving', 'value': []}{'phase': 'saving', 'value': []}Passed
domain state regression 2: inbox{'phase': 'both', 'value': ['other', 'n']}{'phase': 'both', 'value': ['other', 'n']}Passed
domain state regression 3: inbox{'phase': 'inbox-only', 'value': ['n']}{'phase': 'inbox-only', 'value': ['n']}Passed
domain state regression 4: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 5: banner{'phase': 'recovery-banner', 'value': True}{'phase': 'recovery-banner', 'value': True}Passed
domain state regression 6: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed
domain state regression 7: banner{'phase': 'inbox-only', 'value': False}{'phase': 'inbox-only', 'value': False}Passed

SHA-256 / 4f191d03be26b507cfac945ebf5c759da0d7f67ccce099c8d24d9db4e1576db1

Verification & scope

Stipulated single-notification state and policy model only. Event delivery and acknowledgements are explicit test inputs. No DOM, accessibility announcements, live timers, networking, actual rendering, or production-platform conformance is simulated. 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:42:32.417244+00:00.

Case digest / b9738724d65cb8efa3b97bcbfdc8e22be145b618dad476bd3815f30ba9b5d2ae