{
  "id": "P117",
  "slug": "message-queue",
  "key": "P117-message-queue",
  "title": "Message queue",
  "summary": "A broker that sends a message again when its acknowledgement is lost, four handlers over the same deliveries - add, replay, check and a window of keys - and every choice of lost acks counted, with a key reused for another request refused.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P117 - Message queue\n\nA broker delivers every message at least once. When a consumer's ack is\nlost, the broker sends the message again, and the work is done twice\nunless the handler notices. Each message carries an idempotency key.\n\n- `main.eml` - the menu, the messages, the deliveries with four handlers\n  side by side, every choice of lost acks, and the settings\n- `broker.eml` - the messages, the right result, the broker's order of\n  deliveries, the four handlers, and the count over every choice\n\nHow each part works:\n\n- A message is a key, an account and an amount; every account starts at\n  0. A lost ack sends the message again a set number of deliveries later,\n  or sooner when nothing else is waiting. The ack can be lost again.\n- The right result applies each key's first message once. A later message\n  with the same key and the same request is a retry, answered done; with\n  another request, it is refused.\n- The four handlers, as in the corpus cases `at-least-once-delivery` and\n  `idempotency-key-reuse`:\n  - add applies every delivery, so a duplicate is applied again;\n  - replay keeps every key and answers a key it has seen with the first\n    answer: the balances are right, but a reused key is told done;\n  - check keeps every key with its request and refuses another request\n    under it: right on every run, and it keeps every key;\n  - a window keeps only the last few keys, so it is right only while\n    every duplicate comes back before its key has left. When a key has\n    left and come back with another request, the window can even refuse\n    a right one.\n- Every choice: each message loses its first ack or not, 128 runs for the\n  seven messages. With a message sent again 3 deliveries later, the first\n  window right on every run keeps 3 keys; 6 deliveries later, 4. For\n  every delay from 1 to 9 it is 2, 3, 3, 4, 4, 4, 5, 5 and 5 keys.\n\nWhat is checked: menu choices 1 to 7; lost acks as message numbers, at\nmost 3 for one message, or none; a key of up to 8 letters and digits, an\naccount of up to 10 lowercase letters, an amount from -999 to 999 but not\n0, and at most 8 messages; a delay and a window from 1 to 9. An empty\nanswer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the messages, with one key reused for another request and one retry;\n- deliveries losing the acks of messages 2 and 5, of message 1 (the window\n  of 2 is too small), and of none;\n- every choice at a delay of 3, and of 6;\n- a window of 4, with message 1's ack lost once and message 2's twice;\n- message 8, a late retry of message 1, after which only a window of all\n  5 keys is right on every run.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- lost acks: a cancel, 0, 8, x, 2,5 and 1 four times;\n- new messages: a cancel, the keys k 1, toolongkey and k-1, a cancel, the\n  accounts Bob and averyverylongname, a cancel, the amounts 0, 1000, +-3\n  and x, then dave -15, and a ninth message;\n- the delay: a cancel, 0, 10 and x; the window: a cancel and 0.\n\nBuilt on the verified corpus cases `at-least-once-delivery` (duplicates\nare the handler's problem, and a bounded window is an assumption about\nlateness) and `idempotency-key-reuse` (a reused key with another request\nis refused only when the request is kept).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P117 message queue: a broker that sends a message again when its\n# acknowledgement is lost, four handlers side by side over the same\n# deliveries, and every choice of lost acknowledgements counted.\nimport broker\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if c < \"0\" or c > \"9\":\n            return False\n    return True\n\ndef numbers_text(ns):\n    \"\" => out\n    for i in [0:len(ns) - 1]:\n        if i > 0 and i == len(ns) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + str(ns[i]) => out\n    return out\n\ndef balances_text(msgs, bal):\n    \"\" => out\n    for a in broker.accounts(msgs):\n        if out != \"\":\n            out + \", \" => out\n        out + a + \" \" + str(bal[a]) => out\n    return out\n\ndef note(msgs, i):\n    broker.first_with_key(msgs, i) => f\n    if f == i:\n        return \"\"\n    if broker.same_request(msgs[f], msgs[i]):\n        return \"a retry of message \" + str(f + 1)\n    return \"the key of message \" + str(f + 1) + \", for another request\"\n\ndef widths(msgs):\n    0 => kw\n    0 => bw\n    for m in msgs:\n        if len(m[0]) > kw:\n            len(m[0]) => kw\n        if len(broker.body_text(m)) > bw:\n            len(broker.body_text(m)) => bw\n    return [kw, bw]\n\ndef right_text(msgs):\n    broker.expected(msgs) => want\n    [] => refused\n    for i in [0:len(msgs) - 1]:\n        if want[1][i] == \"refused\":\n            refused + [i + 1] => refused\n    \"Right: \" + balances_text(msgs, want[0]) => out\n    if len(refused) == 1:\n        out + \"; message \" + numbers_text(refused) + \" refused\" => out\n    elif len(refused) > 1:\n        out + \"; messages \" + numbers_text(refused) + \" refused\" => out\n    return out + \".\"\n\ndef show(msgs, delay, k):\n    (\"Messages (a message whose ack is lost is sent again \" + str(delay) + \" deliveries later; the window keeps \" + str(k) + \" keys):\") ^0\n    widths(msgs) => w\n    for i in [0:len(msgs) - 1]:\n        msgs[i] => m\n        \"  \" + lpad(str(i + 1), 2) + \"  \" + pad(m[0], w[0]) + \"  \" => line\n        note(msgs, i) => n\n        if n == \"\":\n            (line + broker.body_text(m)) ^0\n        else:\n            (line + pad(broker.body_text(m), w[1]) + \"  \" + n) ^0\n    right_text(msgs) ^0\n\ndef handler_line(msgs, name, kind, k, seq):\n    broker.handle(kind, k, msgs, seq) => r\n    broker.problems(msgs, r) => p\n    [] => parts\n    if p[0] > 0:\n        parts + [str(p[0]) + \" applied again\"] => parts\n    for x in p[1]:\n        \"message \" + str(x[0] + 1) => who\n        if x[1] == \"applied\":\n            parts + [who + \" applied under message \" + str(x[2] + 1) + \"'s key\"] => parts\n        elif x[1] == \"told\":\n            parts + [who + \" told done with message \" + str(x[2] + 1) + \"'s answer\"] => parts\n        else:\n            parts + [who + \" wrongly refused\"] => parts\n    if len(parts) == 0 and not broker.balances_right(msgs, r):\n        parts + [\"balances wrong\"] => parts\n    \"\" => verdict\n    if len(parts) == 0:\n        \"right\" => verdict\n        if kind == \"replay\":\n            verdict + \", keeping \" + str(r[4]) + \" keys\" => verdict\n        elif kind != \"add\":\n            verdict + \", keeping \" + str(r[4]) + \" keys with their requests\" => verdict\n    else:\n        for q in parts:\n            if verdict != \"\":\n                verdict + \"; \" => verdict\n            verdict + q => verdict\n    (\"  \" + pad(name, 10) + balances_text(msgs, r[0]) + \": \" + verdict + \".\") ^0\n\ndef deliver(msgs, delay, k):\n    (\"Acks lost: message numbers from 1 to \" + str(len(msgs)) + \" such as 2 5 (a number twice loses two), or none.\") ^0\n    plain(input(\"acks lost> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    [] => lost\n    for m in msgs:\n        lost + [0] => lost\n    if t != \"none\":\n        \"\" => w\n        for c in t + \" \":\n            if c == \" \":\n                if w != \"\":\n                    if not is_digits(w) or len(w) > 2 or int(w) < 1 or int(w) > len(msgs):\n                        (\"Type message numbers from 1 to \" + str(len(msgs)) + \", or none.\") ^0\n                        return\n                    lost[int(w) - 1] + 1 => lost[int(w) - 1]\n                    if lost[int(w) - 1] > 3:\n                        \"At most 3 lost acks for one message.\" ^0\n                        return\n                \"\" => w\n            else:\n                w + c => w\n    broker.deliveries(msgs, lost, delay) => seq\n    widths(msgs) => wd\n    \"Deliveries:\" ^0\n    for d in [0:len(seq) - 1]:\n        seq[d] => e\n        msgs[e[0]] => m\n        \"\" => tag\n        if e[2]:\n            \"sent again\" => tag\n        if e[1]:\n            if tag != \"\":\n                tag + \", \" => tag\n            tag + \"ack lost\" => tag\n        \"  \" + lpad(str(d + 1), 2) + \"  message \" + str(e[0] + 1) + \"  \" + pad(m[0], wd[0]) + \"  \" => line\n        if tag == \"\":\n            (line + broker.body_text(m)) ^0\n        else:\n            (line + pad(broker.body_text(m), wd[1]) + \"  \" + tag) ^0\n    right_text(msgs) ^0\n    handler_line(msgs, \"add\", \"add\", k, seq)\n    handler_line(msgs, \"replay\", \"replay\", k, seq)\n    handler_line(msgs, \"check\", \"check\", k, seq)\n    handler_line(msgs, \"window \" + str(k), \"window\", k, seq)\n\ndef every(msgs, delay):\n    broker.census(msgs, delay) => c\n    (\"Every choice of which messages lose their first ack: \" + str(c[0]) + \" runs, each lost one sent again \" + str(delay) + \" deliveries later, or sooner at the end.\") ^0\n    \"  handler      right  applied twice  reuse mishandled  keys kept\" ^0\n    0 => kept\n    for row in c[1]:\n        (\"  \" + pad(row[0], 10) + lpad(str(row[1]), 8) + lpad(str(row[2]), 15) + lpad(str(row[3]), 18) + lpad(str(row[4]), 11)) ^0\n        if row[0] == \"check\":\n            row[4] => kept\n    \"  applied twice: some request was applied more than once.\" ^0\n    \"  reuse mishandled: a key reused for another request was not refused, or a message was wrongly refused.\" ^0\n    c[1][len(c[1]) - 1] => last\n    (\"The first window right on every run keeps \" + str(last[4]) + \" keys; check keeps \" + str(kept) + \". How small a window can be depends on how late a message comes back.\") ^0\n\ndef add_message(msgs):\n    if len(msgs) >= 8:\n        \"8 messages is the most.\" ^0\n        return msgs\n    plain(input(\"key (up to 8 letters and digits)> \")) => key\n    if key == \"\":\n        \"Cancelled.\" ^0\n        return msgs\n    if len(key) > 8:\n        \"Type a key of up to 8 letters and digits.\" ^0\n        return msgs\n    for c in key:\n        if not ((c >= \"a\" and c <= \"z\") or (c >= \"A\" and c <= \"Z\") or (c >= \"0\" and c <= \"9\")):\n            \"Type a key of up to 8 letters and digits.\" ^0\n            return msgs\n    plain(input(\"account (up to 10 lowercase letters)> \")) => acct\n    if acct == \"\":\n        \"Cancelled.\" ^0\n        return msgs\n    if len(acct) > 10:\n        \"Type an account of up to 10 lowercase letters.\" ^0\n        return msgs\n    for c in acct:\n        if c < \"a\" or c > \"z\":\n            \"Type an account of up to 10 lowercase letters.\" ^0\n            return msgs\n    plain(input(\"amount (a whole number from -999 to 999, not 0)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return msgs\n    1 => sign\n    t => digits\n    if t[0] == \"-\" or t[0] == \"+\":\n        if t[0] == \"-\":\n            0 - 1 => sign\n        t[1:len(t)] => digits\n    if not is_digits(digits) or len(digits) > 3 or int(digits) == 0:\n        \"Type a whole number from -999 to 999, not 0.\" ^0\n        return msgs\n    msgs + [[key, acct, sign * int(digits)]] => out\n    note(out, len(out) - 1) => n\n    if n == \"\":\n        (\"Added message \" + str(len(out)) + \": \" + key + \" \" + broker.body_text(out[len(out) - 1]) + \".\") ^0\n    else:\n        (\"Added message \" + str(len(out)) + \": \" + key + \" \" + broker.body_text(out[len(out) - 1]) + \", \" + n + \".\") ^0\n    return out\n\ndef one_to_nine(prompt):\n    plain(input(prompt)) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return 0\n    if len(t) != 1 or t < \"1\" or t > \"9\":\n        \"Type a number from 1 to 9.\" ^0\n        return 0\n    return int(t)\n\nbroker.default_messages() => msgs\n3 => delay\n2 => k\n\n\"== Message queue ==\" ^0\n\"A broker delivers every message at least once: when an ack is lost, it sends the message again. Each message carries an idempotency key.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(msgs, delay, k)\n    elif choice == \"2\":\n        deliver(msgs, delay, k)\n    elif choice == \"3\":\n        every(msgs, delay)\n    elif choice == \"4\":\n        add_message(msgs) => msgs\n    elif choice == \"5\":\n        one_to_nine(\"sent again after (1 to 9 deliveries)> \") => n\n        if n > 0:\n            n => delay\n            (\"A message whose ack is lost is now sent again \" + str(delay) + \" deliveries later.\") ^0\n    elif choice == \"6\":\n        one_to_nine(\"window (1 to 9 keys)> \") => n\n        if n > 0:\n            n => k\n            (\"The window now keeps \" + str(k) + \" keys.\") ^0\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import broker\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if c < \"0\" or c > \"9\":\n            return False\n    return True\n\ndef numbers_text(ns):\n    out = \"\"\n    for i in range(0, len(ns)):\n        if i > 0 and i == len(ns) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + str(ns[i])\n    return out\n\ndef balances_text(msgs, bal):\n    out = \"\"\n    for a in broker.accounts(msgs):\n        if out != \"\":\n            out = out + \", \"\n        out = out + a + \" \" + str(bal[a])\n    return out\n\ndef note(msgs, i):\n    f = broker.first_with_key(msgs, i)\n    if f == i:\n        return \"\"\n    if broker.same_request(msgs[f], msgs[i]):\n        return \"a retry of message \" + str(f + 1)\n    return \"the key of message \" + str(f + 1) + \", for another request\"\n\ndef widths(msgs):\n    kw = 0\n    bw = 0\n    for m in msgs:\n        if len(m[0]) > kw:\n            kw = len(m[0])\n        if len(broker.body_text(m)) > bw:\n            bw = len(broker.body_text(m))\n    return [kw, bw]\n\ndef right_text(msgs):\n    want = broker.expected(msgs)\n    refused = []\n    for i in range(0, len(msgs)):\n        if want[1][i] == \"refused\":\n            refused = refused + [i + 1]\n    out = \"Right: \" + balances_text(msgs, want[0])\n    if len(refused) == 1:\n        out = out + \"; message \" + numbers_text(refused) + \" refused\"\n    elif len(refused) > 1:\n        out = out + \"; messages \" + numbers_text(refused) + \" refused\"\n    return out + \".\"\n\ndef show(msgs, delay, k):\n    print(\"Messages (a message whose ack is lost is sent again \" + str(delay) + \" deliveries later; the window keeps \" + str(k) + \" keys):\")\n    w = widths(msgs)\n    for i in range(0, len(msgs)):\n        m = msgs[i]\n        line = \"  \" + lpad(str(i + 1), 2) + \"  \" + pad(m[0], w[0]) + \"  \"\n        n = note(msgs, i)\n        if n == \"\":\n            print(line + broker.body_text(m))\n        else:\n            print(line + pad(broker.body_text(m), w[1]) + \"  \" + n)\n    print(right_text(msgs))\n\ndef handler_line(msgs, name, kind, k, seq):\n    r = broker.handle(kind, k, msgs, seq)\n    p = broker.problems(msgs, r)\n    parts = []\n    if p[0] > 0:\n        parts = parts + [str(p[0]) + \" applied again\"]\n    for x in p[1]:\n        who = \"message \" + str(x[0] + 1)\n        if x[1] == \"applied\":\n            parts = parts + [who + \" applied under message \" + str(x[2] + 1) + \"'s key\"]\n        elif x[1] == \"told\":\n            parts = parts + [who + \" told done with message \" + str(x[2] + 1) + \"'s answer\"]\n        else:\n            parts = parts + [who + \" wrongly refused\"]\n    if len(parts) == 0 and not broker.balances_right(msgs, r):\n        parts = parts + [\"balances wrong\"]\n    verdict = \"\"\n    if len(parts) == 0:\n        verdict = \"right\"\n        if kind == \"replay\":\n            verdict = verdict + \", keeping \" + str(r[4]) + \" keys\"\n        elif kind != \"add\":\n            verdict = verdict + \", keeping \" + str(r[4]) + \" keys with their requests\"\n    else:\n        for q in parts:\n            if verdict != \"\":\n                verdict = verdict + \"; \"\n            verdict = verdict + q\n    print(\"  \" + pad(name, 10) + balances_text(msgs, r[0]) + \": \" + verdict + \".\")\n\ndef deliver(msgs, delay, k):\n    print(\"Acks lost: message numbers from 1 to \" + str(len(msgs)) + \" such as 2 5 (a number twice loses two), or none.\")\n    t = plain(input(\"acks lost> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    lost = []\n    for m in msgs:\n        lost = lost + [0]\n    if t != \"none\":\n        w = \"\"\n        for c in t + \" \":\n            if c == \" \":\n                if w != \"\":\n                    if not is_digits(w) or len(w) > 2 or int(w) < 1 or int(w) > len(msgs):\n                        print(\"Type message numbers from 1 to \" + str(len(msgs)) + \", or none.\")\n                        return\n                    lost[int(w) - 1] = lost[int(w) - 1] + 1\n                    if lost[int(w) - 1] > 3:\n                        print(\"At most 3 lost acks for one message.\")\n                        return\n                w = \"\"\n            else:\n                w = w + c\n    seq = broker.deliveries(msgs, lost, delay)\n    wd = widths(msgs)\n    print(\"Deliveries:\")\n    for d in range(0, len(seq)):\n        e = seq[d]\n        m = msgs[e[0]]\n        tag = \"\"\n        if e[2]:\n            tag = \"sent again\"\n        if e[1]:\n            if tag != \"\":\n                tag = tag + \", \"\n            tag = tag + \"ack lost\"\n        line = \"  \" + lpad(str(d + 1), 2) + \"  message \" + str(e[0] + 1) + \"  \" + pad(m[0], wd[0]) + \"  \"\n        if tag == \"\":\n            print(line + broker.body_text(m))\n        else:\n            print(line + pad(broker.body_text(m), wd[1]) + \"  \" + tag)\n    print(right_text(msgs))\n    handler_line(msgs, \"add\", \"add\", k, seq)\n    handler_line(msgs, \"replay\", \"replay\", k, seq)\n    handler_line(msgs, \"check\", \"check\", k, seq)\n    handler_line(msgs, \"window \" + str(k), \"window\", k, seq)\n\ndef every(msgs, delay):\n    c = broker.census(msgs, delay)\n    print(\"Every choice of which messages lose their first ack: \" + str(c[0]) + \" runs, each lost one sent again \" + str(delay) + \" deliveries later, or sooner at the end.\")\n    print(\"  handler      right  applied twice  reuse mishandled  keys kept\")\n    kept = 0\n    for row in c[1]:\n        print(\"  \" + pad(row[0], 10) + lpad(str(row[1]), 8) + lpad(str(row[2]), 15) + lpad(str(row[3]), 18) + lpad(str(row[4]), 11))\n        if row[0] == \"check\":\n            kept = row[4]\n    print(\"  applied twice: some request was applied more than once.\")\n    print(\"  reuse mishandled: a key reused for another request was not refused, or a message was wrongly refused.\")\n    last = c[1][len(c[1]) - 1]\n    print(\"The first window right on every run keeps \" + str(last[4]) + \" keys; check keeps \" + str(kept) + \". How small a window can be depends on how late a message comes back.\")\n\ndef add_message(msgs):\n    if len(msgs) >= 8:\n        print(\"8 messages is the most.\")\n        return msgs\n    key = plain(input(\"key (up to 8 letters and digits)> \"))\n    if key == \"\":\n        print(\"Cancelled.\")\n        return msgs\n    if len(key) > 8:\n        print(\"Type a key of up to 8 letters and digits.\")\n        return msgs\n    for c in key:\n        if not (c >= \"a\" and c <= \"z\" or c >= \"A\" and c <= \"Z\" or c >= \"0\" and c <= \"9\"):\n            print(\"Type a key of up to 8 letters and digits.\")\n            return msgs\n    acct = plain(input(\"account (up to 10 lowercase letters)> \"))\n    if acct == \"\":\n        print(\"Cancelled.\")\n        return msgs\n    if len(acct) > 10:\n        print(\"Type an account of up to 10 lowercase letters.\")\n        return msgs\n    for c in acct:\n        if c < \"a\" or c > \"z\":\n            print(\"Type an account of up to 10 lowercase letters.\")\n            return msgs\n    t = plain(input(\"amount (a whole number from -999 to 999, not 0)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return msgs\n    sign = 1\n    digits = t\n    if t[0] == \"-\" or t[0] == \"+\":\n        if t[0] == \"-\":\n            sign = 0 - 1\n        digits = t[1:len(t)]\n    if not is_digits(digits) or len(digits) > 3 or int(digits) == 0:\n        print(\"Type a whole number from -999 to 999, not 0.\")\n        return msgs\n    out = msgs + [[key, acct, sign * int(digits)]]\n    n = note(out, len(out) - 1)\n    if n == \"\":\n        print(\"Added message \" + str(len(out)) + \": \" + key + \" \" + broker.body_text(out[len(out) - 1]) + \".\")\n    else:\n        print(\"Added message \" + str(len(out)) + \": \" + key + \" \" + broker.body_text(out[len(out) - 1]) + \", \" + n + \".\")\n    return out\n\ndef one_to_nine(prompt):\n    t = plain(input(prompt))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return 0\n    if len(t) != 1 or t < \"1\" or t > \"9\":\n        print(\"Type a number from 1 to 9.\")\n        return 0\n    return int(t)\n\nmsgs = broker.default_messages()\ndelay = 3\nk = 2\nprint(\"== Message queue ==\")\nprint(\"A broker delivers every message at least once: when an ack is lost, it sends the message again. Each message carries an idempotency key.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(msgs, delay, k)\n    elif choice == \"2\":\n        deliver(msgs, delay, k)\n    elif choice == \"3\":\n        every(msgs, delay)\n    elif choice == \"4\":\n        msgs = add_message(msgs)\n    elif choice == \"5\":\n        n = one_to_nine(\"sent again after (1 to 9 deliveries)> \")\n        if n > 0:\n            delay = n\n            print(\"A message whose ack is lost is now sent again \" + str(delay) + \" deliveries later.\")\n    elif choice == \"6\":\n        n = one_to_nine(\"window (1 to 9 keys)> \")\n        if n > 0:\n            k = n\n            print(\"The window now keeps \" + str(k) + \" keys.\")\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "broker.eml",
      "eml": "# P117 message queue - a broker that delivers every message at least once:\n# when a consumer's acknowledgement is lost, the broker sends the message\n# again a few deliveries later. As in the corpus case at-least-once-delivery,\n# what a duplicate does is up to the handler. Each message carries an\n# idempotency key, and as in the corpus case idempotency-key-reuse, a client\n# that reuses a key for another request has to be refused, which needs the\n# request kept beside the key.\n#\n# A message is [key, account, amount]; every account starts at 0.\n\ndef default_messages():\n    return [[\"k1\", \"alice\", 30], [\"k2\", \"bob\", 20], [\"k3\", \"alice\", 0 - 10], [\"k2\", \"bob\", 25], [\"k4\", \"carol\", 50], [\"k4\", \"carol\", 50], [\"k5\", \"bob\", 0 - 5]]\n\ndef signed(n):\n    if n < 0:\n        return \"-\" + str(0 - n)\n    return \"+\" + str(n)\n\ndef body_text(m):\n    return m[1] + \" \" + signed(m[2])\n\ndef same_request(a, b):\n    return a[1] == b[1] and a[2] == b[2]\n\ndef member(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef accounts(msgs):\n    [] => out\n    for m in msgs:\n        if not member(out, m[1]):\n            out + [m[1]] => out\n    return out\n\ndef first_with_key(msgs, i):\n    # the first message carrying message i's key\n    for j in [0:i]:\n        if msgs[j][0] == msgs[i][0]:\n            return j\n    return i\n\ndef expected(msgs):\n    # each key's first message applied once; a later message with the same\n    # key and the same request is a retry and is answered done; with another\n    # request it is refused: [balances, answer per message]\n    {} => bal\n    for a in accounts(msgs):\n        0 => bal[a]\n    [] => answers\n    for i in [0:len(msgs) - 1]:\n        msgs[i] => m\n        first_with_key(msgs, i) => f\n        if f == i:\n            bal[m[1]] + m[2] => bal[m[1]]\n            answers + [\"done\"] => answers\n        elif same_request(msgs[f], m):\n            answers + [\"done\"] => answers\n        else:\n            answers + [\"refused\"] => answers\n    return [bal, answers]\n\ndef deliveries(msgs, lost, delay):\n    # the broker's deliveries, in order, as [message, ack lost, sent before];\n    # lost[i] is how many of message i's acks are lost. A message whose ack\n    # is lost is sent again delay deliveries later, or sooner when nothing\n    # else is waiting.\n    [] => pending\n    [] => left\n    for i in [0:len(msgs) - 1]:\n        pending + [i] => pending\n        left + [lost[i]] => left\n    [] => again\n    [] => sent\n    [] => out\n    0 => d\n    while len(pending) > 0 or len(again) > 0:\n        d + 1 => d\n        0 - 1 => best\n        for j in [0:len(again) - 1]:\n            if best == 0 - 1 or again[j][0] < again[best][0]:\n                j => best\n        if best != 0 - 1 and (again[best][0] <= d or len(pending) == 0):\n            again[best][1] => m\n            [] => rest\n            for j in [0:len(again) - 1]:\n                if j != best:\n                    rest + [again[j]] => rest\n            rest => again\n        else:\n            pending[0] => m\n            pending[1:len(pending)] => pending\n        left[m] > 0 => lose\n        if lose:\n            left[m] - 1 => left[m]\n            again + [[d + delay, m]] => again\n        out + [[m, lose, member(sent, m)]] => out\n        sent + [m] => sent\n    return out\n\ndef handle(kind, k, msgs, seq):\n    # one handler over the deliveries:\n    #   add     applies every delivery;\n    #   replay  keeps every key, and answers a key it has seen with the\n    #           first answer, applying nothing;\n    #   check   keeps every key with its request: the same request again is\n    #           answered done, another request under it is refused;\n    #   window  like check, but keeps only the last k keys it applied.\n    # [balances, applied again, times applied per message, last answer per\n    #  message, keys kept]\n    {} => bal\n    for a in accounts(msgs):\n        0 => bal[a]\n    {} => kept\n    [] => window\n    [] => applied\n    0 => again\n    [] => times\n    [] => answers\n    for m in msgs:\n        times + [0] => times\n        answers + [\"\"] => answers\n    for e in seq:\n        e[0] => i\n        msgs[i] => m\n        \"apply\" => act\n        if kind == \"replay\" or kind == \"check\":\n            if m[0] in kept:\n                if same_request(msgs[kept[m[0]]], m):\n                    \"repeat\" => act\n                elif kind == \"replay\":\n                    \"told\" => act\n                else:\n                    \"refused\" => act\n        elif kind == \"window\":\n            for w in window:\n                if w[0] == m[0]:\n                    if same_request(msgs[w[1]], m):\n                        \"repeat\" => act\n                    else:\n                        \"refused\" => act\n        if act == \"apply\":\n            if member(applied, [m[0], m[1], m[2]]):\n                again + 1 => again\n            applied + [[m[0], m[1], m[2]]] => applied\n            bal[m[1]] + m[2] => bal[m[1]]\n            times[i] + 1 => times[i]\n            \"done\" => answers[i]\n            if kind == \"replay\" or kind == \"check\":\n                i => kept[m[0]]\n            if kind == \"window\":\n                window + [[m[0], i]] => window\n                if len(window) > k:\n                    window[1:len(window)] => window\n        elif act == \"repeat\":\n            \"done\" => answers[i]\n        else:\n            act => answers[i]\n    0 => n\n    if kind == \"window\":\n        len(window) => n\n    else:\n        len(kept) => n\n    return [bal, again, times, answers, n]\n\ndef problems(msgs, r):\n    # what went wrong, as [applied again, list of [message, what, first]]\n    expected(msgs) => want\n    [] => out\n    for i in [0:len(msgs) - 1]:\n        first_with_key(msgs, i) => f\n        if want[1][i] == \"refused\":\n            if r[2][i] > 0:\n                out + [[i, \"applied\", f]] => out\n            elif r[3][i] == \"told\":\n                out + [[i, \"told\", f]] => out\n        elif r[3][i] == \"refused\":\n            out + [[i, \"refused\", f]] => out\n    return [r[1], out]\n\ndef balances_right(msgs, r):\n    expected(msgs)[0] => want\n    for a in accounts(msgs):\n        if r[0][a] != want[a]:\n            return False\n    return True\n\ndef right(msgs, r):\n    problems(msgs, r) => p\n    return p[0] == 0 and len(p[1]) == 0 and balances_right(msgs, r)\n\ndef lost_choices(n):\n    # every choice of which messages lose their first ack\n    [[]] => out\n    for i in [1:n]:\n        [] => more\n        for c in out:\n            more + [c + [0]] => more\n            more + [c + [1]] => more\n        more => out\n    return out\n\ndef census(msgs, delay):\n    # over every choice of lost first acks, for add, replay, check and the\n    # windows from 1 key up to the first that is right on every run:\n    # [runs, rows of [name, right, applied twice, reuse mishandled, kept]]\n    lost_choices(len(msgs)) => choices\n    [] => runs\n    for c in choices:\n        runs + [deliveries(msgs, c, delay)] => runs\n    [] => rows\n    [\"add\", \"replay\", \"check\"] => kinds\n    0 => k\n    True => going\n    while going:\n        if len(kinds) > 0:\n            kinds[0] => kind\n            kinds[1:len(kinds)] => kinds\n            kind => name\n        else:\n            \"window\" => kind\n            k + 1 => k\n            \"window \" + str(k) => name\n        0 => good\n        0 => twice\n        0 => reuse\n        0 => most\n        for seq in runs:\n            handle(kind, k, msgs, seq) => r\n            problems(msgs, r) => p\n            if p[0] == 0 and len(p[1]) == 0 and balances_right(msgs, r):\n                good + 1 => good\n            if p[0] > 0:\n                twice + 1 => twice\n            if len(p[1]) > 0:\n                reuse + 1 => reuse\n            if r[4] > most:\n                r[4] => most\n        rows + [[name, good, twice, reuse, most]] => rows\n        if kind == \"window\" and good == len(runs):\n            False => going\n    return [len(runs), rows]\n",
      "python": "def default_messages():\n    return [[\"k1\", \"alice\", 30], [\"k2\", \"bob\", 20], [\"k3\", \"alice\", 0 - 10], [\"k2\", \"bob\", 25], [\"k4\", \"carol\", 50], [\"k4\", \"carol\", 50], [\"k5\", \"bob\", 0 - 5]]\n\ndef signed(n):\n    if n < 0:\n        return \"-\" + str(0 - n)\n    return \"+\" + str(n)\n\ndef body_text(m):\n    return m[1] + \" \" + signed(m[2])\n\ndef same_request(a, b):\n    return a[1] == b[1] and a[2] == b[2]\n\ndef member(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef accounts(msgs):\n    out = []\n    for m in msgs:\n        if not member(out, m[1]):\n            out = out + [m[1]]\n    return out\n\ndef first_with_key(msgs, i):\n    for j in range(0, i+1):\n        if msgs[j][0] == msgs[i][0]:\n            return j\n    return i\n\ndef expected(msgs):\n    bal = {}\n    for a in accounts(msgs):\n        bal[a] = 0\n    answers = []\n    for i in range(0, len(msgs)):\n        m = msgs[i]\n        f = first_with_key(msgs, i)\n        if f == i:\n            bal[m[1]] = bal[m[1]] + m[2]\n            answers = answers + [\"done\"]\n        elif same_request(msgs[f], m):\n            answers = answers + [\"done\"]\n        else:\n            answers = answers + [\"refused\"]\n    return [bal, answers]\n\ndef deliveries(msgs, lost, delay):\n    pending = []\n    left = []\n    for i in range(0, len(msgs)):\n        pending = pending + [i]\n        left = left + [lost[i]]\n    again = []\n    sent = []\n    out = []\n    d = 0\n    while len(pending) > 0 or len(again) > 0:\n        d = d + 1\n        best = 0 - 1\n        for j in range(0, len(again)):\n            if best == 0 - 1 or again[j][0] < again[best][0]:\n                best = j\n        if best != 0 - 1 and (again[best][0] <= d or len(pending) == 0):\n            m = again[best][1]\n            rest = []\n            for j in range(0, len(again)):\n                if j != best:\n                    rest = rest + [again[j]]\n            again = rest\n        else:\n            m = pending[0]\n            pending = pending[1:len(pending)]\n        lose = left[m] > 0\n        if lose:\n            left[m] = left[m] - 1\n            again = again + [[d + delay, m]]\n        out = out + [[m, lose, member(sent, m)]]\n        sent = sent + [m]\n    return out\n\ndef handle(kind, k, msgs, seq):\n    bal = {}\n    for a in accounts(msgs):\n        bal[a] = 0\n    kept = {}\n    window = []\n    applied = []\n    again = 0\n    times = []\n    answers = []\n    for m in msgs:\n        times = times + [0]\n        answers = answers + [\"\"]\n    for e in seq:\n        i = e[0]\n        m = msgs[i]\n        act = \"apply\"\n        if kind == \"replay\" or kind == \"check\":\n            if m[0] in kept:\n                if same_request(msgs[kept[m[0]]], m):\n                    act = \"repeat\"\n                elif kind == \"replay\":\n                    act = \"told\"\n                else:\n                    act = \"refused\"\n        elif kind == \"window\":\n            for w in window:\n                if w[0] == m[0]:\n                    if same_request(msgs[w[1]], m):\n                        act = \"repeat\"\n                    else:\n                        act = \"refused\"\n        if act == \"apply\":\n            if member(applied, [m[0], m[1], m[2]]):\n                again = again + 1\n            applied = applied + [[m[0], m[1], m[2]]]\n            bal[m[1]] = bal[m[1]] + m[2]\n            times[i] = times[i] + 1\n            answers[i] = \"done\"\n            if kind == \"replay\" or kind == \"check\":\n                kept[m[0]] = i\n            if kind == \"window\":\n                window = window + [[m[0], i]]\n                if len(window) > k:\n                    window = window[1:len(window)]\n        elif act == \"repeat\":\n            answers[i] = \"done\"\n        else:\n            answers[i] = act\n    n = 0\n    if kind == \"window\":\n        n = len(window)\n    else:\n        n = len(kept)\n    return [bal, again, times, answers, n]\n\ndef problems(msgs, r):\n    want = expected(msgs)\n    out = []\n    for i in range(0, len(msgs)):\n        f = first_with_key(msgs, i)\n        if want[1][i] == \"refused\":\n            if r[2][i] > 0:\n                out = out + [[i, \"applied\", f]]\n            elif r[3][i] == \"told\":\n                out = out + [[i, \"told\", f]]\n        elif r[3][i] == \"refused\":\n            out = out + [[i, \"refused\", f]]\n    return [r[1], out]\n\ndef balances_right(msgs, r):\n    want = expected(msgs)[0]\n    for a in accounts(msgs):\n        if r[0][a] != want[a]:\n            return False\n    return True\n\ndef right(msgs, r):\n    p = problems(msgs, r)\n    return p[0] == 0 and len(p[1]) == 0 and balances_right(msgs, r)\n\ndef lost_choices(n):\n    out = [[]]\n    for i in range(1, n+1):\n        more = []\n        for c in out:\n            more = more + [c + [0]]\n            more = more + [c + [1]]\n        out = more\n    return out\n\ndef census(msgs, delay):\n    choices = lost_choices(len(msgs))\n    runs = []\n    for c in choices:\n        runs = runs + [deliveries(msgs, c, delay)]\n    rows = []\n    kinds = [\"add\", \"replay\", \"check\"]\n    k = 0\n    going = True\n    while going:\n        if len(kinds) > 0:\n            kind = kinds[0]\n            kinds = kinds[1:len(kinds)]\n            name = kind\n        else:\n            kind = \"window\"\n            k = k + 1\n            name = \"window \" + str(k)\n        good = 0\n        twice = 0\n        reuse = 0\n        most = 0\n        for seq in runs:\n            r = handle(kind, k, msgs, seq)\n            p = problems(msgs, r)\n            if p[0] == 0 and len(p[1]) == 0 and balances_right(msgs, r):\n                good = good + 1\n            if p[0] > 0:\n                twice = twice + 1\n            if len(p[1]) > 0:\n                reuse = reuse + 1\n            if r[4] > most:\n                most = r[4]\n        rows = rows + [[name, good, twice, reuse, most]]\n        if kind == \"window\" and good == len(runs):\n            going = False\n    return [len(runs), rows]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\n0\n2\n8\n2\nx\n2\n2,5\n2\n1 1 1 1\n4\n\n4\nk 1\n4\ntoolongkey\n4\nk-1\n4\nk9\n\n4\nk9\nBob\n4\nk9\naveryverylongname\n4\nk9\ndave\n\n4\nk9\ndave\n0\n4\nk9\ndave\n1000\n4\nk9\ndave\n+-3\n4\nk9\ndave\nx\n4\nk9\ndave\n-15\n4\n5\n\n5\n0\n5\n10\n5\nx\n6\n\n6\n0\n7\n",
      "screen": "== Message queue ==\nA broker delivers every message at least once: when an ack is lost, it sends the message again. Each message carries an idempotency key.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 0\nType message numbers from 1 to 7, or none.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 8\nType message numbers from 1 to 7, or none.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> x\nType message numbers from 1 to 7, or none.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 2,5\nType message numbers from 1 to 7, or none.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 1 1 1 1\nAt most 3 lost acks for one message.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k 1\nType a key of up to 8 letters and digits.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> toolongkey\nType a key of up to 8 letters and digits.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k-1\nType a key of up to 8 letters and digits.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> Bob\nType an account of up to 10 lowercase letters.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> averyverylongname\nType an account of up to 10 lowercase letters.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> 0\nType a whole number from -999 to 999, not 0.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> 1000\nType a whole number from -999 to 999, not 0.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> +-3\nType a whole number from -999 to 999, not 0.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> x\nType a whole number from -999 to 999, not 0.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k9\naccount (up to 10 lowercase letters)> dave\namount (a whole number from -999 to 999, not 0)> -15\nAdded message 8: k9 dave -15.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\n8 messages is the most.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 5\nsent again after (1 to 9 deliveries)> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 5\nsent again after (1 to 9 deliveries)> 0\nType a number from 1 to 9.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 5\nsent again after (1 to 9 deliveries)> 10\nType a number from 1 to 9.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 5\nsent again after (1 to 9 deliveries)> x\nType a number from 1 to 9.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 6\nwindow (1 to 9 keys)> \nCancelled.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 6\nwindow (1 to 9 keys)> 0\nType a number from 1 to 9.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n2 5\n2\n1\n2\nnone\n3\n5\n6\n3\n6\n4\n2\n1 2 2\n4\nk1\nalice\n30\n1\n3\n7\n",
      "screen": "== Message queue ==\nA broker delivers every message at least once: when an ack is lost, it sends the message again. Each message carries an idempotency key.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 1\nMessages (a message whose ack is lost is sent again 3 deliveries later; the window keeps 2 keys):\n   1  k1  alice +30\n   2  k2  bob +20\n   3  k3  alice -10\n   4  k2  bob +25    the key of message 2, for another request\n   5  k4  carol +50\n   6  k4  carol +50  a retry of message 5\n   7  k5  bob -5\nRight: alice 20, bob 15, carol 50; message 4 refused.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 2 5\nDeliveries:\n   1  message 1  k1  alice +30\n   2  message 2  k2  bob +20    ack lost\n   3  message 3  k3  alice -10\n   4  message 4  k2  bob +25\n   5  message 2  k2  bob +20    sent again\n   6  message 5  k4  carol +50  ack lost\n   7  message 6  k4  carol +50\n   8  message 7  k5  bob -5\n   9  message 5  k4  carol +50  sent again\nRight: alice 20, bob 15, carol 50; message 4 refused.\n  add       alice 20, bob 60, carol 150: 3 applied again; message 4 applied under message 2's key.\n  replay    alice 20, bob 15, carol 50: message 4 told done with message 2's answer.\n  check     alice 20, bob 15, carol 50: right, keeping 5 keys with their requests.\n  window 2  alice 20, bob 15, carol 50: right, keeping 2 keys with their requests.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 1\nDeliveries:\n   1  message 1  k1  alice +30  ack lost\n   2  message 2  k2  bob +20\n   3  message 3  k3  alice -10\n   4  message 1  k1  alice +30  sent again\n   5  message 4  k2  bob +25\n   6  message 5  k4  carol +50\n   7  message 6  k4  carol +50\n   8  message 7  k5  bob -5\nRight: alice 20, bob 15, carol 50; message 4 refused.\n  add       alice 50, bob 40, carol 100: 2 applied again; message 4 applied under message 2's key.\n  replay    alice 20, bob 15, carol 50: message 4 told done with message 2's answer.\n  check     alice 20, bob 15, carol 50: right, keeping 5 keys with their requests.\n  window 2  alice 50, bob 40, carol 50: 1 applied again; message 4 applied under message 2's key.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> none\nDeliveries:\n   1  message 1  k1  alice +30\n   2  message 2  k2  bob +20\n   3  message 3  k3  alice -10\n   4  message 4  k2  bob +25\n   5  message 5  k4  carol +50\n   6  message 6  k4  carol +50\n   7  message 7  k5  bob -5\nRight: alice 20, bob 15, carol 50; message 4 refused.\n  add       alice 20, bob 40, carol 100: 1 applied again; message 4 applied under message 2's key.\n  replay    alice 20, bob 15, carol 50: message 4 told done with message 2's answer.\n  check     alice 20, bob 15, carol 50: right, keeping 5 keys with their requests.\n  window 2  alice 20, bob 15, carol 50: right, keeping 2 keys with their requests.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 3\nEvery choice of which messages lose their first ack: 128 runs, each lost one sent again 3 deliveries later, or sooner at the end.\n  handler      right  applied twice  reuse mishandled  keys kept\n  add              0            128               128          0\n  replay           0              0               128          5\n  check          128              0                 0          5\n  window 1         0            124               120          1\n  window 2        40             74                64          2\n  window 3       128              0                 0          3\n  applied twice: some request was applied more than once.\n  reuse mishandled: a key reused for another request was not refused, or a message was wrongly refused.\nThe first window right on every run keeps 3 keys; check keeps 5. How small a window can be depends on how late a message comes back.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 5\nsent again after (1 to 9 deliveries)> 6\nA message whose ack is lost is now sent again 6 deliveries later.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 3\nEvery choice of which messages lose their first ack: 128 runs, each lost one sent again 6 deliveries later, or sooner at the end.\n  handler      right  applied twice  reuse mishandled  keys kept\n  add              0            128               128          0\n  replay           0              0               128          5\n  check          128              0                 0          5\n  window 1         0            126               128          1\n  window 2         8            118                32          2\n  window 3        16             96                32          3\n  window 4       128              0                 0          4\n  applied twice: some request was applied more than once.\n  reuse mishandled: a key reused for another request was not refused, or a message was wrongly refused.\nThe first window right on every run keeps 4 keys; check keeps 5. How small a window can be depends on how late a message comes back.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 6\nwindow (1 to 9 keys)> 4\nThe window now keeps 4 keys.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 2\nAcks lost: message numbers from 1 to 7 such as 2 5 (a number twice loses two), or none.\nacks lost> 1 2 2\nDeliveries:\n   1  message 1  k1  alice +30  ack lost\n   2  message 2  k2  bob +20    ack lost\n   3  message 3  k3  alice -10\n   4  message 4  k2  bob +25\n   5  message 5  k4  carol +50\n   6  message 6  k4  carol +50\n   7  message 1  k1  alice +30  sent again\n   8  message 2  k2  bob +20    sent again, ack lost\n   9  message 7  k5  bob -5\n  10  message 2  k2  bob +20    sent again\nRight: alice 20, bob 15, carol 50; message 4 refused.\n  add       alice 50, bob 80, carol 100: 4 applied again; message 4 applied under message 2's key.\n  replay    alice 20, bob 15, carol 50: message 4 told done with message 2's answer.\n  check     alice 20, bob 15, carol 50: right, keeping 5 keys with their requests.\n  window 4  alice 20, bob 15, carol 50: right, keeping 4 keys with their requests.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 4\nkey (up to 8 letters and digits)> k1\naccount (up to 10 lowercase letters)> alice\namount (a whole number from -999 to 999, not 0)> 30\nAdded message 8: k1 alice +30, a retry of message 1.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 1\nMessages (a message whose ack is lost is sent again 6 deliveries later; the window keeps 4 keys):\n   1  k1  alice +30\n   2  k2  bob +20\n   3  k3  alice -10\n   4  k2  bob +25    the key of message 2, for another request\n   5  k4  carol +50\n   6  k4  carol +50  a retry of message 5\n   7  k5  bob -5\n   8  k1  alice +30  a retry of message 1\nRight: alice 20, bob 15, carol 50; message 4 refused.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 3\nEvery choice of which messages lose their first ack: 256 runs, each lost one sent again 6 deliveries later, or sooner at the end.\n  handler      right  applied twice  reuse mishandled  keys kept\n  add              0            256               256          0\n  replay           0              0               256          5\n  check          256              0                 0          5\n  window 1         0            256               256          1\n  window 2         0            256                64          2\n  window 3         0            256                64          3\n  window 4         0            256                64          4\n  window 5       256              0                 0          5\n  applied twice: some request was applied more than once.\n  reuse mishandled: a key reused for another request was not refused, or a message was wrongly refused.\nThe first window right on every run keeps 5 keys; check keeps 5. How small a window can be depends on how late a message comes back.\n\n1) messages  2) deliver  3) every lost ack  4) add a message  5) redelivery  6) window  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "at-least-once-delivery",
      "caseId": "215-at-least-once-delivery",
      "title": "Duplicates are a property of the handler"
    },
    {
      "slug": "idempotency-key-reuse",
      "caseId": "281-idempotency-key-reuse",
      "title": "Idempotency key reuse — the key says retry and the body says otherwise"
    }
  ],
  "updated": "2026-10-16"
}
