FA-76141 / Chat ordering and read receipts / Open access
Render who is typing from start and stop events: start order · case 01
Typers are listed alphabetically instead of by who started first.
ROOT CAUSE
The start order decision evaluates `who = sorted(names.get(u, u) for _, u in active)` where the contract requires `who = [names.get(u, u) for _, u in active]`.
VERIFIED REPAIR
Use `who = [names.get(u, u) for _, u in active]` for the start order decision and keep every other rule of the model unchanged.
Unsuccessful approach: Reversing lists the most recent typer first. The attempted `who = [names.get(u, u) for _, u in reversed(active)]` still disagrees with a fixture.
Case contract
events [ts_ms, user, kind] with kind start, stop or message are applied in (ts, user, kind) order. A user is typing when their latest start has no later stop or message and now - start < 6000 ms (a start stamped in the future by a skewed clock stays active). The viewer is excluded. Typers are listed by start time: one -> "A is typing", two -> "A and B are typing", more -> "Several people are typing", none -> "". names maps ids to display names.
Why this case matters
Typing indicators are ephemeral presence; stale or misordered indicators mislead users about who is replying.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, me, now, names):
last = {}
for ts, user, kind in sorted(events):
if kind == 'start':
last[user] = ts
else:
last.pop(user, None)
active = sorted((ts, user) for user, ts in last.items() if user != me and now - ts < 6000)
who = sorted(names.get(u, u) for _, u in active)
if not who:
return ''
if len(who) == 1:
return who[0] + ' is typing'
if len(who) == 2:
return who[0] + ' and ' + who[1] + ' are typing'
return 'Several people are typing'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('exact expiry', ([[1000, 'bo', 'start']], 'me', 7000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50001, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [301, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [701, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 11, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2501, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 21, {}), '')], 2: [('exact expiry', ([[2000, 'bo', 'start']], 'me', 8000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50002, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [302, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [702, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 12, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2502, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 22, {}), '')], 3: [('exact expiry', ([[3000, 'bo', 'start']], 'me', 9000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50003, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [303, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [703, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 13, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2503, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 23, {}), '')], 4: [('exact expiry', ([[4000, 'bo', 'start']], 'me', 10000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50004, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [304, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [704, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 14, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2504, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 24, {}), '')], 5: [('exact expiry', ([[5000, 'bo', 'start']], 'me', 11000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50005, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [305, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [705, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 15, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2505, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 25, {}), '')]}
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| exact expiry | | | Passed |
| future stamped start | bo is typing | bo is typing | Passed |
| message clears typing | | | Passed |
| start order | Amy and Zed are typing | Zed and Amy are typing | Failed |
| three typers | Several people are typing | Several people are typing | Passed |
| stop overtaken in transit | Cy is typing | Cy is typing | Passed |
| self typing hidden | | | Passed |
SHA-256 / be8f0cdbb5c80551327ad4c7e28299a1429221523019f253b5d40a4b4656c282
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, me, now, names):
last = {}
for ts, user, kind in sorted(events):
if kind == 'start':
last[user] = ts
else:
last.pop(user, None)
active = sorted((ts, user) for user, ts in last.items() if user != me and now - ts < 6000)
who = [names.get(u, u) for _, u in reversed(active)]
if not who:
return ''
if len(who) == 1:
return who[0] + ' is typing'
if len(who) == 2:
return who[0] + ' and ' + who[1] + ' are typing'
return 'Several people are typing'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('exact expiry', ([[1000, 'bo', 'start']], 'me', 7000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50001, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [301, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [701, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 11, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2501, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 21, {}), '')], 2: [('exact expiry', ([[2000, 'bo', 'start']], 'me', 8000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50002, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [302, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [702, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 12, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2502, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 22, {}), '')], 3: [('exact expiry', ([[3000, 'bo', 'start']], 'me', 9000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50003, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [303, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [703, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 13, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2503, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 23, {}), '')], 4: [('exact expiry', ([[4000, 'bo', 'start']], 'me', 10000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50004, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [304, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [704, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 14, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2504, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 24, {}), '')], 5: [('exact expiry', ([[5000, 'bo', 'start']], 'me', 11000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50005, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [305, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [705, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 15, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2505, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 25, {}), '')]}
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| exact expiry | | | Passed |
| future stamped start | bo is typing | bo is typing | Passed |
| message clears typing | | | Passed |
| start order | Amy and Zed are typing | Zed and Amy are typing | Failed |
| three typers | Several people are typing | Several people are typing | Passed |
| stop overtaken in transit | Cy is typing | Cy is typing | Passed |
| self typing hidden | | | Passed |
SHA-256 / a33e838066b19aa06f380be782b70a076ab871c2203f1bb2adc5874a86222e3f
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, me, now, names):
last = {}
for ts, user, kind in sorted(events):
if kind == 'start':
last[user] = ts
else:
last.pop(user, None)
active = sorted((ts, user) for user, ts in last.items() if user != me and now - ts < 6000)
who = [names.get(u, u) for _, u in active]
if not who:
return ''
if len(who) == 1:
return who[0] + ' is typing'
if len(who) == 2:
return who[0] + ' and ' + who[1] + ' are typing'
return 'Several people are typing'
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
_CASES = {1: [('exact expiry', ([[1000, 'bo', 'start']], 'me', 7000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50001, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [301, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [701, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 11, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2501, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 21, {}), '')], 2: [('exact expiry', ([[2000, 'bo', 'start']], 'me', 8000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50002, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [302, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [702, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 12, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2502, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 22, {}), '')], 3: [('exact expiry', ([[3000, 'bo', 'start']], 'me', 9000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50003, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [303, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [703, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 13, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2503, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 23, {}), '')], 4: [('exact expiry', ([[4000, 'bo', 'start']], 'me', 10000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50004, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [304, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [704, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 14, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2504, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 24, {}), '')], 5: [('exact expiry', ([[5000, 'bo', 'start']], 'me', 11000, {'bo': 'Bo'}), ''), ('future stamped start', ([[50005, 'bo', 'start']], 'me', 40000, {}), 'bo is typing'), ('message clears typing', ([[100, 'bo', 'start'], [305, 'bo', 'message']], 'me', 1000, {'bo': 'Bo'}), ''), ('start order', ([[500, 'zed', 'start'], [705, 'amy', 'start']], 'me', 2000, {'zed': 'Zed', 'amy': 'Amy'}), 'Zed and Amy are typing'), ('three typers', ([[1, 'a', 'start'], [2, 'b', 'start'], [3, 'c', 'start']], 'me', 15, {'a': 'A', 'b': 'B', 'c': 'C'}), 'Several people are typing'), ('stop overtaken in transit', ([[2505, 'bo', 'stop'], [2100, 'bo', 'start'], [2600, 'cy', 'start']], 'me', 3000, {'cy': 'Cy'}), 'Cy is typing'), ('self typing hidden', ([[10, 'me', 'start']], 'me', 25, {}), '')]}
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| exact expiry | | | Passed |
| future stamped start | bo is typing | bo is typing | Passed |
| message clears typing | | | Passed |
| start order | Zed and Amy are typing | Zed and Amy are typing | Passed |
| three typers | Several people are typing | Several people are typing | Passed |
| stop overtaken in transit | Cy is typing | Cy is typing | Passed |
| self typing hidden | | | Passed |
SHA-256 / 436320fe63e8d4316a628d05523e6fa46b0984afaf1fae68c8bbd49e810ac9df
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.522797+00:00.
Case digest / 9858c4b0d771ef1434bc341b41d7d6eb433ddab4ab536693051efaeedf3fb138