{"abstract":"A client that announces it is leaving stays visible until it times out.","category":"CRDT convergence","checks":9,"contract":"Presence updates [\"update\", client, clock, state|None, now] are accepted when clock exceeds the last accepted clock for that client, or when it equals it, the state is None and the client is present. Accepted updates record the clock; a None state removes the client (logging a removal only if it was present); otherwise [state, now] is stored. [\"tick\", now] removes, in client order, every client other than self_id whose last update is at least timeout old. Return present [client, state] pairs and the removal log.","evaluation_group":"w2-crdt-convergence-awareness-presence","failed_approach":"Accepting every same-clock update also lets duplicate state changes overwrite the stored state.","family":"w2-crdt-convergence-awareness-presence-same-clock-offline","id":"FA-75276","implementations":{"attempt":{"sha256":"ac5432ffba34fbc50f13f7384e0be96abfe301f8923a9ef1ee3a80ed96a6383b","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(events, self_id, timeout):\n    table = {}\n    clocks = {}\n    removed = []\n    for ev in events:\n        if ev[0] == 'update':\n            _, c, clk, st, now = ev\n            cur = clocks.get(c, -1)\n            if clk >= cur:\n                clocks[c] = clk\n                if st is None:\n                    if table.pop(c, None) is not None:\n                        removed.append(c)\n                else:\n                    table[c] = [st, now]\n        else:\n            now = ev[1]\n            for c in sorted(table):\n                if c != self_id and now - table[c][1] >= timeout:\n                    del table[c]\n                    removed.append(c)\n    return {'present': sorted([c, v[0]] for c, v in table.items()), 'removed': removed}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\ncases = {\n    1: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me1', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me1', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, None, 1]], 'me1', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, 'changed', 1]], 'me1', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me1', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me1', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me1', 30], {'present': [['me1', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me1', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me1', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 101, 's', 3], ['tick', 5], ['tick', 13]], 'me1', 10], {'present': [], 'removed': ['c8']})],\n    2: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me2', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me2', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, None, 1]], 'me2', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, 'changed', 1]], 'me2', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me2', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me2', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me2', 30], {'present': [['me2', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me2', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me2', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 102, 's', 3], ['tick', 5], ['tick', 13]], 'me2', 10], {'present': [], 'removed': ['c8']})],\n    3: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me3', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me3', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, None, 1]], 'me3', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, 'changed', 1]], 'me3', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me3', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me3', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me3', 30], {'present': [['me3', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me3', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me3', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 103, 's', 3], ['tick', 5], ['tick', 13]], 'me3', 10], {'present': [], 'removed': ['c8']})],\n    4: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me4', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me4', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, None, 1]], 'me4', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, 'changed', 1]], 'me4', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me4', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me4', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me4', 30], {'present': [['me4', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me4', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me4', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 104, 's', 3], ['tick', 5], ['tick', 13]], 'me4', 10], {'present': [], 'removed': ['c8']})],\n    5: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me5', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me5', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, None, 1]], 'me5', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, 'changed', 1]], 'me5', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me5', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me5', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me5', 30], {'present': [['me5', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me5', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me5', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 105, 's', 3], ['tick', 5], ['tick', 13]], 'me5', 10], {'present': [], 'removed': ['c8']})],\n}[N]\nfor label, args, expected in cases:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"broken":{"sha256":"fe2f8a4cfdeeba9a2a8e5165b2e2221d8a1dbe3d170b3a56558e214357ca8676","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(events, self_id, timeout):\n    table = {}\n    clocks = {}\n    removed = []\n    for ev in events:\n        if ev[0] == 'update':\n            _, c, clk, st, now = ev\n            cur = clocks.get(c, -1)\n            if clk > cur:\n                clocks[c] = clk\n                if st is None:\n                    if table.pop(c, None) is not None:\n                        removed.append(c)\n                else:\n                    table[c] = [st, now]\n        else:\n            now = ev[1]\n            for c in sorted(table):\n                if c != self_id and now - table[c][1] >= timeout:\n                    del table[c]\n                    removed.append(c)\n    return {'present': sorted([c, v[0]] for c, v in table.items()), 'removed': removed}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\ncases = {\n    1: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me1', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me1', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, None, 1]], 'me1', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, 'changed', 1]], 'me1', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me1', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me1', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me1', 30], {'present': [['me1', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me1', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me1', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 101, 's', 3], ['tick', 5], ['tick', 13]], 'me1', 10], {'present': [], 'removed': ['c8']})],\n    2: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me2', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me2', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, None, 1]], 'me2', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, 'changed', 1]], 'me2', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me2', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me2', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me2', 30], {'present': [['me2', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me2', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me2', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 102, 's', 3], ['tick', 5], ['tick', 13]], 'me2', 10], {'present': [], 'removed': ['c8']})],\n    3: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me3', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me3', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, None, 1]], 'me3', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, 'changed', 1]], 'me3', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me3', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me3', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me3', 30], {'present': [['me3', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me3', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me3', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 103, 's', 3], ['tick', 5], ['tick', 13]], 'me3', 10], {'present': [], 'removed': ['c8']})],\n    4: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me4', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me4', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, None, 1]], 'me4', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, 'changed', 1]], 'me4', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me4', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me4', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me4', 30], {'present': [['me4', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me4', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me4', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 104, 's', 3], ['tick', 5], ['tick', 13]], 'me4', 10], {'present': [], 'removed': ['c8']})],\n    5: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me5', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me5', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, None, 1]], 'me5', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, 'changed', 1]], 'me5', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me5', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me5', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me5', 30], {'present': [['me5', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me5', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me5', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 105, 's', 3], ['tick', 5], ['tick', 13]], 'me5', 10], {'present': [], 'removed': ['c8']})],\n}[N]\nfor label, args, expected in cases:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"fixed":{"sha256":"7a90f56e2d1048b0ab250e1c6cb00437e5785e7438fb263581d1fa05010dcdc5","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(events, self_id, timeout):\n    table = {}\n    clocks = {}\n    removed = []\n    for ev in events:\n        if ev[0] == 'update':\n            _, c, clk, st, now = ev\n            cur = clocks.get(c, -1)\n            if clk > cur or (clk == cur and st is None and c in table):\n                clocks[c] = clk\n                if st is None:\n                    if table.pop(c, None) is not None:\n                        removed.append(c)\n                else:\n                    table[c] = [st, now]\n        else:\n            now = ev[1]\n            for c in sorted(table):\n                if c != self_id and now - table[c][1] >= timeout:\n                    del table[c]\n                    removed.append(c)\n    return {'present': sorted([c, v[0]] for c, v in table.items()), 'removed': removed}\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\ncases = {\n    1: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me1', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me1', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, None, 1]], 'me1', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 1, 'on', 0], ['update', 'c2', 1, 'changed', 1]], 'me1', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me1', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me1', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me1', 30], {'present': [['me1', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me1', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me1', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 101, 's', 3], ['tick', 5], ['tick', 13]], 'me1', 10], {'present': [], 'removed': ['c8']})],\n    2: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me2', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me2', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, None, 1]], 'me2', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 2, 'on', 0], ['update', 'c2', 2, 'changed', 1]], 'me2', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me2', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me2', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me2', 30], {'present': [['me2', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me2', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me2', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 102, 's', 3], ['tick', 5], ['tick', 13]], 'me2', 10], {'present': [], 'removed': ['c8']})],\n    3: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me3', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me3', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, None, 1]], 'me3', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 3, 'on', 0], ['update', 'c2', 3, 'changed', 1]], 'me3', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me3', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me3', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me3', 30], {'present': [['me3', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me3', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me3', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 103, 's', 3], ['tick', 5], ['tick', 13]], 'me3', 10], {'present': [], 'removed': ['c8']})],\n    4: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me4', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me4', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, None, 1]], 'me4', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 4, 'on', 0], ['update', 'c2', 4, 'changed', 1]], 'me4', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me4', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me4', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me4', 30], {'present': [['me4', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me4', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me4', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 104, 's', 3], ['tick', 5], ['tick', 13]], 'me4', 10], {'present': [], 'removed': ['c8']})],\n    5: [('newer update replaces', [[['update', 'c1', 1, 'idle', 0], ['update', 'c1', 2, 'typing', 5]], 'me5', 30], {'present': [['c1', 'typing']], 'removed': []}), ('stale update after timeout is rejected', [[['update', 'c1', 3, 'on', 0], ['tick', 30], ['update', 'c1', 3, 'on', 31], ['update', 'c1', 2, 'old', 32]], 'me5', 30], {'present': [], 'removed': ['c1']}), ('offline notice at the same clock removes', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, None, 1]], 'me5', 30], {'present': [], 'removed': ['c2']}), ('same-clock state change is ignored', [[['update', 'c2', 5, 'on', 0], ['update', 'c2', 5, 'changed', 1]], 'me5', 30], {'present': [['c2', 'on']], 'removed': []}), ('timeout boundary', [[['update', 'c3', 1, 'x', 10], ['tick', 39], ['update', 'c4', 1, 'y', 20], ['tick', 40]], 'me5', 30], {'present': [['c4', 'y']], 'removed': ['c3']}), ('own state never expires', [[['update', 'me5', 1, 'self', 0], ['tick', 500], ['update', 'me', 7, 'other', 490], ['tick', 600]], 'me5', 30], {'present': [['me5', 'self']], 'removed': ['me']}), ('activity refreshes the timer', [[['update', 'c5', 1, 'a', 0], ['update', 'c5', 2, 'b', 25], ['tick', 40]], 'me5', 30], {'present': [['c5', 'b']], 'removed': []}), ('offline notice for an unknown client', [[['update', 'c6', 4, None, 0], ['update', 'c6', 3, 'late', 1], ['update', 'c7', 1, 'on', 2], ['update', 'c7', 2, None, 3], ['update', 'c7', 2, None, 4]], 'me5', 30], {'present': [], 'removed': ['c7']}), ('large clocks and short timeout', [[['update', 'c8', 105, 's', 3], ['tick', 5], ['tick', 13]], 'me5', 10], {'present': [], 'removed': ['c8']})],\n}[N]\nfor label, args, expected in cases:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"}},"limitations":"A deterministic, bounded teaching model of one replicated data type with stipulated operation and merge rules; it is not a production CRDT library and makes no claim of conformance to any specific published design. 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.","method":"Deterministic executable model with adversarial boundary fixtures.","provenance":{"created_by":"Failure Map","dependencies":"Python standard library","family":"w2-crdt-convergence-awareness-presence-same-clock-offline","generated_at":"2026-09-29T14:49:04.725576+00:00","license":"CC0-1.0","python":"3.12.14","seed":1,"split":"open-access"},"relevance":"Ephemeral presence rides alongside document CRDTs and must neither resurrect departed peers nor expire the local user.","repair":"Also accept a None state at the current clock while the client is present.","root_cause":"Acceptance requires a strictly newer clock, so the same-clock offline notice is dropped.","sha256":"e38e2c8689e1ffa067a41beae5c5da25074424542968d14b02fad001a047996c","title":"Awareness presence table: an offline notice with the current clock is ignored · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verification":{"attempt":{"elapsed_ms":48.994,"exit_code":1,"observations":[{"actual":{"present":[["c1","typing"]],"removed":[]},"check":"newer update replaces","expected":{"present":[["c1","typing"]],"removed":[]},"passed":true},{"actual":{"present":[["c1","on"]],"removed":["c1"]},"check":"stale update after timeout is rejected","expected":{"present":[],"removed":["c1"]},"passed":false},{"actual":{"present":[],"removed":["c2"]},"check":"offline notice at the same clock removes","expected":{"present":[],"removed":["c2"]},"passed":true},{"actual":{"present":[["c2","changed"]],"removed":[]},"check":"same-clock state change is ignored","expected":{"present":[["c2","on"]],"removed":[]},"passed":false},{"actual":{"present":[["c4","y"]],"removed":["c3"]},"check":"timeout boundary","expected":{"present":[["c4","y"]],"removed":["c3"]},"passed":true},{"actual":{"present":[["me1","self"]],"removed":["me"]},"check":"own state never expires","expected":{"present":[["me1","self"]],"removed":["me"]},"passed":true},{"actual":{"present":[["c5","b"]],"removed":[]},"check":"activity refreshes the timer","expected":{"present":[["c5","b"]],"removed":[]},"passed":true},{"actual":{"present":[],"removed":["c7"]},"check":"offline notice for an unknown client","expected":{"present":[],"removed":["c7"]},"passed":true},{"actual":{"present":[],"removed":["c8"]},"check":"large clocks and short timeout","expected":{"present":[],"removed":["c8"]},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"newer update replaces\", \"actual\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"stale update after timeout is rejected\", \"actual\": {\"present\": [[\"c1\", \"on\"]], \"removed\": [\"c1\"]}, \"expected\": {\"present\": [], \"removed\": [\"c1\"]}, \"passed\": false}, {\"check\": \"offline notice at the same clock removes\", \"actual\": {\"present\": [], \"removed\": [\"c2\"]}, \"expected\": {\"present\": [], \"removed\": [\"c2\"]}, \"passed\": true}, {\"check\": \"same-clock state change is ignored\", \"actual\": {\"present\": [[\"c2\", \"changed\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"passed\": false}, {\"check\": \"timeout boundary\", \"actual\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"expected\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"passed\": true}, {\"check\": \"own state never expires\", \"actual\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"expected\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"passed\": true}, {\"check\": \"activity refreshes the timer\", \"actual\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"offline notice for an unknown client\", \"actual\": {\"present\": [], \"removed\": [\"c7\"]}, \"expected\": {\"present\": [], \"removed\": [\"c7\"]}, \"passed\": true}, {\"check\": \"large clocks and short timeout\", \"actual\": {\"present\": [], \"removed\": [\"c8\"]}, \"expected\": {\"present\": [], \"removed\": [\"c8\"]}, \"passed\": true}], \"passed\": false}\n"},"broken":{"elapsed_ms":49.122,"exit_code":1,"observations":[{"actual":{"present":[["c1","typing"]],"removed":[]},"check":"newer update replaces","expected":{"present":[["c1","typing"]],"removed":[]},"passed":true},{"actual":{"present":[],"removed":["c1"]},"check":"stale update after timeout is rejected","expected":{"present":[],"removed":["c1"]},"passed":true},{"actual":{"present":[["c2","on"]],"removed":[]},"check":"offline notice at the same clock removes","expected":{"present":[],"removed":["c2"]},"passed":false},{"actual":{"present":[["c2","on"]],"removed":[]},"check":"same-clock state change is ignored","expected":{"present":[["c2","on"]],"removed":[]},"passed":true},{"actual":{"present":[["c4","y"]],"removed":["c3"]},"check":"timeout boundary","expected":{"present":[["c4","y"]],"removed":["c3"]},"passed":true},{"actual":{"present":[["me1","self"]],"removed":["me"]},"check":"own state never expires","expected":{"present":[["me1","self"]],"removed":["me"]},"passed":true},{"actual":{"present":[["c5","b"]],"removed":[]},"check":"activity refreshes the timer","expected":{"present":[["c5","b"]],"removed":[]},"passed":true},{"actual":{"present":[],"removed":["c7"]},"check":"offline notice for an unknown client","expected":{"present":[],"removed":["c7"]},"passed":true},{"actual":{"present":[],"removed":["c8"]},"check":"large clocks and short timeout","expected":{"present":[],"removed":["c8"]},"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"newer update replaces\", \"actual\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"stale update after timeout is rejected\", \"actual\": {\"present\": [], \"removed\": [\"c1\"]}, \"expected\": {\"present\": [], \"removed\": [\"c1\"]}, \"passed\": true}, {\"check\": \"offline notice at the same clock removes\", \"actual\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"expected\": {\"present\": [], \"removed\": [\"c2\"]}, \"passed\": false}, {\"check\": \"same-clock state change is ignored\", \"actual\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"timeout boundary\", \"actual\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"expected\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"passed\": true}, {\"check\": \"own state never expires\", \"actual\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"expected\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"passed\": true}, {\"check\": \"activity refreshes the timer\", \"actual\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"offline notice for an unknown client\", \"actual\": {\"present\": [], \"removed\": [\"c7\"]}, \"expected\": {\"present\": [], \"removed\": [\"c7\"]}, \"passed\": true}, {\"check\": \"large clocks and short timeout\", \"actual\": {\"present\": [], \"removed\": [\"c8\"]}, \"expected\": {\"present\": [], \"removed\": [\"c8\"]}, \"passed\": true}], \"passed\": false}\n"},"fixed":{"elapsed_ms":82.62,"exit_code":0,"observations":[{"actual":{"present":[["c1","typing"]],"removed":[]},"check":"newer update replaces","expected":{"present":[["c1","typing"]],"removed":[]},"passed":true},{"actual":{"present":[],"removed":["c1"]},"check":"stale update after timeout is rejected","expected":{"present":[],"removed":["c1"]},"passed":true},{"actual":{"present":[],"removed":["c2"]},"check":"offline notice at the same clock removes","expected":{"present":[],"removed":["c2"]},"passed":true},{"actual":{"present":[["c2","on"]],"removed":[]},"check":"same-clock state change is ignored","expected":{"present":[["c2","on"]],"removed":[]},"passed":true},{"actual":{"present":[["c4","y"]],"removed":["c3"]},"check":"timeout boundary","expected":{"present":[["c4","y"]],"removed":["c3"]},"passed":true},{"actual":{"present":[["me1","self"]],"removed":["me"]},"check":"own state never expires","expected":{"present":[["me1","self"]],"removed":["me"]},"passed":true},{"actual":{"present":[["c5","b"]],"removed":[]},"check":"activity refreshes the timer","expected":{"present":[["c5","b"]],"removed":[]},"passed":true},{"actual":{"present":[],"removed":["c7"]},"check":"offline notice for an unknown client","expected":{"present":[],"removed":["c7"]},"passed":true},{"actual":{"present":[],"removed":["c8"]},"check":"large clocks and short timeout","expected":{"present":[],"removed":["c8"]},"passed":true}],"passed":true,"stderr":"","stdout":"{\"observations\": [{\"check\": \"newer update replaces\", \"actual\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c1\", \"typing\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"stale update after timeout is rejected\", \"actual\": {\"present\": [], \"removed\": [\"c1\"]}, \"expected\": {\"present\": [], \"removed\": [\"c1\"]}, \"passed\": true}, {\"check\": \"offline notice at the same clock removes\", \"actual\": {\"present\": [], \"removed\": [\"c2\"]}, \"expected\": {\"present\": [], \"removed\": [\"c2\"]}, \"passed\": true}, {\"check\": \"same-clock state change is ignored\", \"actual\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c2\", \"on\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"timeout boundary\", \"actual\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"expected\": {\"present\": [[\"c4\", \"y\"]], \"removed\": [\"c3\"]}, \"passed\": true}, {\"check\": \"own state never expires\", \"actual\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"expected\": {\"present\": [[\"me1\", \"self\"]], \"removed\": [\"me\"]}, \"passed\": true}, {\"check\": \"activity refreshes the timer\", \"actual\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"expected\": {\"present\": [[\"c5\", \"b\"]], \"removed\": []}, \"passed\": true}, {\"check\": \"offline notice for an unknown client\", \"actual\": {\"present\": [], \"removed\": [\"c7\"]}, \"expected\": {\"present\": [], \"removed\": [\"c7\"]}, \"passed\": true}, {\"check\": \"large clocks and short timeout\", \"actual\": {\"present\": [], \"removed\": [\"c8\"]}, \"expected\": {\"present\": [], \"removed\": [\"c8\"]}, \"passed\": true}], \"passed\": true}\n"}},"verified":true,"visibility":"public"}