FAILURE MAP
← Case archive

FA-76106 / Chat ordering and read receipts / Open access

Aggregate emoji reactions with a per-user limit: seq order · case 01

A remove that overtook its add leaves the reaction stuck on the message.

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

ROOT CAUSE

The seq order decision evaluates `events` where the contract requires `sorted(events)`.

THE FAILURE

The seq order decision evaluates `events` where the contract requires `sorted(events)`.

Unsuccessful approach: Applying all adds before removes misorders a user who removes and re-adds. The attempted `sorted(events, key=lambda e: e[3])` still disagrees with a fixture.

Case contract

events [seq, user, emoji, action] are applied in seq order. A user holds each emoji at most once and at most `limit` distinct emoji per message; an add beyond the limit is ignored. A remove only releases a reaction the user holds. Result rows [emoji, count, mine] for emoji with at least one user, ordered by the seq at which the emoji most recently went from zero to one user.

Why this case matters

Reaction chips reorder and recount live; wrong state handling shows phantom or misordered reactions.

1 / The failure

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

N = 1
observations = []
def solve(events, viewer, limit):
    current = {}
    first = {}
    per_user = {}
    for seq, user, emoji, action in events:
        users = current.get(emoji, set())
        if action == 'add':
            if user in users or per_user.get(user, 0) >= limit:
                continue
            if not users:
                first[emoji] = seq
            users.add(user)
            current[emoji] = users
            per_user[user] = per_user.get(user, 0) + 1
        elif user in users:
            users.discard(user)
            per_user[user] = per_user.get(user, 0) - 1
    rows = [[e, len(u), viewer in u] for e, u in current.items() if u]
    rows.sort(key=lambda r: first[r[0]])
    return rows
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('remove delivered before add', ([[3, 'bo', 'up', 'remove'], [2, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [5, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 2: [('remove delivered before add', ([[4, 'bo', 'up', 'remove'], [3, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [6, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 3: [('remove delivered before add', ([[5, 'bo', 'up', 'remove'], [4, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [7, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 4: [('remove delivered before add', ([[6, 'bo', 'up', 'remove'], [5, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [8, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 5: [('remove delivered before add', ([[7, 'bo', 'up', 'remove'], [6, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [9, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])]}
for _label, _args, _expected in _CASES[N]:
    check(_label, solve(*_args), _expected)
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
remove delivered before add[['up', 1, False]][]Failed
emoji vanishes and returns[['ok', 1, False], ['up', 1, False]][['ok', 1, False], ['up', 1, False]]Passed
second user joins an emoji[['up', 2, True], ['ok', 1, False]][['up', 2, True], ['ok', 1, False]]Passed
only reaction removed[][]Passed
limit reached[['a', 1, True], ['b', 1, True]][['a', 1, True], ['b', 1, True]]Passed
stray remove does not free a slot[['a', 1, False]][['a', 1, False]]Passed
removal frees a slot[['b', 1, True]][['b', 1, True]]Passed

SHA-256 / 172b85910d6c2675859435c65f2f809a4a6db6487b23cfe51893220cd45937ee

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(events, viewer, limit):
    current = {}
    first = {}
    per_user = {}
    for seq, user, emoji, action in sorted(events, key=lambda e: e[3]):
        users = current.get(emoji, set())
        if action == 'add':
            if user in users or per_user.get(user, 0) >= limit:
                continue
            if not users:
                first[emoji] = seq
            users.add(user)
            current[emoji] = users
            per_user[user] = per_user.get(user, 0) + 1
        elif user in users:
            users.discard(user)
            per_user[user] = per_user.get(user, 0) - 1
    rows = [[e, len(u), viewer in u] for e, u in current.items() if u]
    rows.sort(key=lambda r: first[r[0]])
    return rows
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('remove delivered before add', ([[3, 'bo', 'up', 'remove'], [2, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [5, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 2: [('remove delivered before add', ([[4, 'bo', 'up', 'remove'], [3, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [6, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 3: [('remove delivered before add', ([[5, 'bo', 'up', 'remove'], [4, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [7, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 4: [('remove delivered before add', ([[6, 'bo', 'up', 'remove'], [5, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [8, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])], 5: [('remove delivered before add', ([[7, 'bo', 'up', 'remove'], [6, 'bo', 'up', 'add']], 'me', 3), []), ('emoji vanishes and returns', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'bo', 'up', 'remove'], [9, 'di', 'up', 'add']], 'me', 3), [['ok', 1, False], ['up', 1, False]]), ('second user joins an emoji', ([[1, 'bo', 'up', 'add'], [2, 'cy', 'ok', 'add'], [3, 'di', 'up', 'add']], 'di', 3), [['up', 2, True], ['ok', 1, False]]), ('only reaction removed', ([[1, 'me', 'up', 'add'], [2, 'me', 'up', 'remove']], 'me', 2), []), ('limit reached', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'b', 'add'], [3, 'bo', 'c', 'add']], 'bo', 2), [['a', 1, True], ['b', 1, True]]), ('stray remove does not free a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'z', 'remove'], [3, 'bo', 'b', 'add']], 'me', 1), [['a', 1, False]]), ('removal frees a slot', ([[1, 'bo', 'a', 'add'], [2, 'bo', 'a', 'remove'], [3, 'bo', 'b', 'add']], 'bo', 1), [['b', 1, True]])]}
for _label, _args, _expected in _CASES[N]:
    check(_label, solve(*_args), _expected)
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
remove delivered before add[][]Passed
emoji vanishes and returns[['up', 1, False], ['ok', 1, False]][['ok', 1, False], ['up', 1, False]]Failed
second user joins an emoji[['up', 2, True], ['ok', 1, False]][['up', 2, True], ['ok', 1, False]]Passed
only reaction removed[][]Passed
limit reached[['a', 1, True], ['b', 1, True]][['a', 1, True], ['b', 1, True]]Passed
stray remove does not free a slot[['a', 1, False]][['a', 1, False]]Passed
removal frees a slot[][['b', 1, True]]Failed

SHA-256 / 0d086efddb384e553efd149722fcbafa84fbcdbdf3383e12eab79bc1c6327076

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 7 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

Stipulated offline chat model; not a complete messaging protocol, client or server implementation. 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:49:13.395512+00:00.

Case digest / 2b9c645a9f9036e73e7343d10ee15436dd12e452113d21a874826caa2b5f74e3