{
  "id": "P131",
  "slug": "event-log",
  "key": "P131-event-log",
  "title": "Event log",
  "summary": "An account replayed from its events beside a stored balance column one write path forgets: a check that compares the column with itself, a replay that finds where they part, and how many orders of the events end where the log does.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P131 - Event log\n\nAn account kept two ways: the balance replayed from its events, and a\nstored balance column that each write path updates.\n\n- `main.eml` - the menu, the log with both balances, the two\n  reconciliations, every order of the events, and adding or removing one\n- `events.eml` - an event's effect, a replay, the ledger with the stored\n  column, every order, and which pairs of events commute\n\nHow each part works:\n\n- An event is a deposit, a withdrawal (refused if more than the balance),\n  a fee of a percentage of the balance (rounded down), or an adjustment of\n  either sign.\n- As in the corpus case `denormalized-total-drifts`, the adjustment's\n  write path forgets the stored column. The column is always the replayed\n  balance less the adjustments: after adjust +20 it is 20 short, and a\n  later withdrawal of 120 takes it to -7, while the replayed balance is\n  13.\n- The check the system has compares the column with a copy kept by the\n  same write paths, so it compares a number with itself and never finds a\n  difference. Replaying the events finds it, and where they part.\n- As in the corpus case `state-from-events-vs-stored`, replaying is right\n  only in the order the events happened: the 720 orders of the 6 events\n  end at 40 different balances, and only 3 of the 15 pairs give the same\n  balance in either order from every balance from 0 to 300.\n\nWhat is checked: menu choices 1 to 6; an event as deposit, withdraw, fee\nor adjust and a whole number up to 9999 (a fee from 1 to 100%, only an\nadjustment negative), at most 6 events. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the log, the reconciliations and every order;\n- the last event removed, and the same again;\n- fee 5% added at the end, and every order again.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- one event removed; then events: a cancel, pay 10, deposit alone, x, 0\n  and 10000, fee 101 and withdraw -5, then adjust -15, and a seventh;\n- the log emptied one event at a time, with every order, the log and the\n  reconciliations on the way and at the end.\n\nBuilt on the verified corpus cases `denormalized-total-drifts` (a stored\ntotal kept by every write path, and a reconciliation that compares it\nwith itself) and `state-from-events-vs-stored` (replaying events is right\nonly for some orders).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P131 event log: the log with both balances, two reconciliations, every\n# order of the events, and adding or removing events.\nimport events\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 show(log):\n    if len(log) == 0:\n        \"The log is empty.\" ^0\n        return\n    events.ledger(log) => rows\n    \"  #  event                              replayed  stored\" ^0\n    for k in [0:len(log) - 1]:\n        rows[k] => r\n        (\"  \" + str(k + 1) + \"  \" + pad(r[0], 33) + lpad(str(r[1]), 10) + lpad(str(r[2]), 8)) ^0\n\ndef reconcile(log):\n    if len(log) == 0:\n        \"The log is empty.\" ^0\n        return\n    events.ledger(log) => rows\n    rows[len(rows) - 1] => last\n    if last[2] == last[3]:\n        (\"The check the system has: the stored column, \" + str(last[2]) + \", against the copy the same write paths keep, \" + str(last[3]) + \": no difference.\") ^0\n    else:\n        (\"The check the system has finds a difference: \" + str(last[2]) + \" against \" + str(last[3]) + \".\") ^0\n    if last[1] == last[2]:\n        (\"Replaying the events: \" + str(last[1]) + \", the same as stored.\") ^0\n    else:\n        (\"Replaying the events: \" + str(last[1]) + \", and stored says \" + str(last[2]) + \": off by \" + events.signed(last[2] - last[1]) + \".\") ^0\n        0 => first\n        for k in [0:len(rows) - 1]:\n            if first == 0 and rows[k][1] != rows[k][2]:\n                k + 1 => first\n        (\"They part at event \" + str(first) + \", \" + rows[first - 1][0] + \": that write path never touches the stored column.\") ^0\n\ndef orders(log):\n    if len(log) < 2:\n        \"Two events or more are needed.\" ^0\n        return\n    events.orders(log) => o\n    events.pairs(log, 300) => p\n    (\"Every order of these \" + str(len(log)) + \" events, replayed: \" + str(o[0]) + \" orders, \" + str(o[1]) + \" different final balances; \" + str(o[2]) + \" end as the log does.\") ^0\n    (\"Of the \" + str(p[0]) + \" pairs of events, \" + str(p[1]) + \" give the same balance in either order from every starting balance from 0 to 300.\") ^0\n    if o[1] > 1:\n        \"Replaying gives the truth only in the order the events happened: the log's order is part of what it records.\" ^0\n\ndef add(log):\n    if len(log) >= 6:\n        \"6 events is the most.\" ^0\n        return log\n    \"An event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\" ^0\n    plain(input(\"event> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return log\n    \"\" => kind\n    \"\" => num\n    False => space\n    for c in t:\n        if c == \" \" and not space:\n            True => space\n        elif space:\n            num + c => num\n        else:\n            kind + c => kind\n    if kind != \"deposit\" and kind != \"withdraw\" and kind != \"fee\" and kind != \"adjust\":\n        \"Type deposit, withdraw, fee or adjust, then a number.\" ^0\n        return log\n    plain(num) => num\n    1 => sign\n    if len(num) > 0 and num[0] == \"-\" and kind == \"adjust\":\n        0 - 1 => sign\n        num[1:len(num)] => num\n    if len(num) == 0 or len(num) > 4:\n        \"Type a whole number up to 9999 (a fee from 1 to 100).\" ^0\n        return log\n    for c in num:\n        if c < \"0\" or c > \"9\":\n            \"Type a whole number up to 9999 (a fee from 1 to 100).\" ^0\n            return log\n    int(num) => n\n    if n == 0 or (kind == \"fee\" and n > 100):\n        \"Type a whole number up to 9999 (a fee from 1 to 100).\" ^0\n        return log\n    log + [[kind, sign * n]] => out\n    if kind == \"fee\":\n        (\"Added: fee \" + str(n) + \"%.\") ^0\n    elif kind == \"adjust\":\n        (\"Added: adjust \" + events.signed(sign * n) + \".\") ^0\n    else:\n        (\"Added: \" + kind + \" \" + str(n) + \".\") ^0\n    return out\n\nevents.default_log() => log\n\n\"== Event log ==\" ^0\n\"An account kept two ways: the balance replayed from its events, and a stored balance column that each write path updates.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(log)\n    elif choice == \"2\":\n        reconcile(log)\n    elif choice == \"3\":\n        orders(log)\n    elif choice == \"4\":\n        add(log) => log\n    elif choice == \"5\":\n        if len(log) == 0:\n            \"The log is empty.\" ^0\n        else:\n            log[0:len(log) - 1] => log\n            (\"Removed the last event; \" + str(len(log)) + \" left.\") ^0\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import events\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 show(log):\n    if len(log) == 0:\n        print(\"The log is empty.\")\n        return\n    rows = events.ledger(log)\n    print(\"  #  event                              replayed  stored\")\n    for k in range(0, len(log)):\n        r = rows[k]\n        print(\"  \" + str(k + 1) + \"  \" + pad(r[0], 33) + lpad(str(r[1]), 10) + lpad(str(r[2]), 8))\n\ndef reconcile(log):\n    if len(log) == 0:\n        print(\"The log is empty.\")\n        return\n    rows = events.ledger(log)\n    last = rows[len(rows) - 1]\n    if last[2] == last[3]:\n        print(\"The check the system has: the stored column, \" + str(last[2]) + \", against the copy the same write paths keep, \" + str(last[3]) + \": no difference.\")\n    else:\n        print(\"The check the system has finds a difference: \" + str(last[2]) + \" against \" + str(last[3]) + \".\")\n    if last[1] == last[2]:\n        print(\"Replaying the events: \" + str(last[1]) + \", the same as stored.\")\n    else:\n        print(\"Replaying the events: \" + str(last[1]) + \", and stored says \" + str(last[2]) + \": off by \" + events.signed(last[2] - last[1]) + \".\")\n        first = 0\n        for k in range(0, len(rows)):\n            if first == 0 and rows[k][1] != rows[k][2]:\n                first = k + 1\n        print(\"They part at event \" + str(first) + \", \" + rows[first - 1][0] + \": that write path never touches the stored column.\")\n\ndef orders(log):\n    if len(log) < 2:\n        print(\"Two events or more are needed.\")\n        return\n    o = events.orders(log)\n    p = events.pairs(log, 300)\n    print(\"Every order of these \" + str(len(log)) + \" events, replayed: \" + str(o[0]) + \" orders, \" + str(o[1]) + \" different final balances; \" + str(o[2]) + \" end as the log does.\")\n    print(\"Of the \" + str(p[0]) + \" pairs of events, \" + str(p[1]) + \" give the same balance in either order from every starting balance from 0 to 300.\")\n    if o[1] > 1:\n        print(\"Replaying gives the truth only in the order the events happened: the log's order is part of what it records.\")\n\ndef add(log):\n    if len(log) >= 6:\n        print(\"6 events is the most.\")\n        return log\n    print(\"An event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\")\n    t = plain(input(\"event> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return log\n    kind = \"\"\n    num = \"\"\n    space = False\n    for c in t:\n        if c == \" \" and not space:\n            space = True\n        elif space:\n            num = num + c\n        else:\n            kind = kind + c\n    if kind != \"deposit\" and kind != \"withdraw\" and kind != \"fee\" and kind != \"adjust\":\n        print(\"Type deposit, withdraw, fee or adjust, then a number.\")\n        return log\n    num = plain(num)\n    sign = 1\n    if len(num) > 0 and num[0] == \"-\" and kind == \"adjust\":\n        sign = 0 - 1\n        num = num[1:len(num)]\n    if len(num) == 0 or len(num) > 4:\n        print(\"Type a whole number up to 9999 (a fee from 1 to 100).\")\n        return log\n    for c in num:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number up to 9999 (a fee from 1 to 100).\")\n            return log\n    n = int(num)\n    if n == 0 or kind == \"fee\" and n > 100:\n        print(\"Type a whole number up to 9999 (a fee from 1 to 100).\")\n        return log\n    out = log + [[kind, sign * n]]\n    if kind == \"fee\":\n        print(\"Added: fee \" + str(n) + \"%.\")\n    elif kind == \"adjust\":\n        print(\"Added: adjust \" + events.signed(sign * n) + \".\")\n    else:\n        print(\"Added: \" + kind + \" \" + str(n) + \".\")\n    return out\n\nlog = events.default_log()\nprint(\"== Event log ==\")\nprint(\"An account kept two ways: the balance replayed from its events, and a stored balance column that each write path updates.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(log)\n    elif choice == \"2\":\n        reconcile(log)\n    elif choice == \"3\":\n        orders(log)\n    elif choice == \"4\":\n        log = add(log)\n    elif choice == \"5\":\n        if len(log) == 0:\n            print(\"The log is empty.\")\n        else:\n            log = log[0:len(log) - 1]\n            print(\"Removed the last event; \" + str(len(log)) + \" left.\")\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "events.eml",
      "eml": "# P131 event log - an account kept two ways: the balance replayed from its\n# events, and a stored balance column that each write path updates. As in\n# the corpus case denormalized-total-drifts, one path (an adjustment)\n# forgets the stored column, and a check that compares the column with a\n# copy kept by the same paths compares a number with itself. As in\n# state-from-events-vs-stored, replaying is right only for the order the\n# events happened in: a fee and a refused withdrawal do not commute with\n# the rest.\n#\n# An event is [kind, amount]: deposit n, withdraw n (refused if more than\n# the balance), fee p (p percent of the balance, rounded down), adjust n\n# (a correction, either sign).\n\ndef default_log():\n    return [[\"deposit\", 100], [\"withdraw\", 30], [\"fee\", 10], [\"deposit\", 50], [\"adjust\", 20], [\"withdraw\", 120]]\n\ndef div(p, q):\n    int(p / q) => f\n    while f * q > p:\n        f - 1 => f\n    while (f + 1) * q <= p:\n        f + 1 => f\n    return f\n\ndef apply(balance, ev):\n    # [balance after, what happened]\n    if ev[0] == \"deposit\":\n        return [balance + ev[1], \"deposit \" + str(ev[1])]\n    if ev[0] == \"withdraw\":\n        if ev[1] > balance:\n            return [balance, \"withdraw \" + str(ev[1]) + \" refused: only \" + str(balance)]\n        return [balance - ev[1], \"withdraw \" + str(ev[1])]\n    if ev[0] == \"fee\":\n        div(balance * ev[1], 100) => f\n        return [balance - f, \"fee \" + str(ev[1]) + \"% = \" + str(f)]\n    return [balance + ev[1], \"adjust \" + signed(ev[1])]\n\ndef signed(n):\n    if n < 0:\n        return str(n)\n    return \"+\" + str(n)\n\ndef replay(log):\n    0 => b\n    for ev in log:\n        apply(b, ev)[0] => b\n    return b\n\ndef ledger(log):\n    # per event [what happened, replayed balance, stored column, the copy\n    #  the check reads]; every path but adjust writes the column and the copy\n    0 => b\n    0 => stored\n    0 => copy\n    [] => out\n    for ev in log:\n        b => before\n        apply(b, ev) => r\n        r[0] => b\n        if ev[0] != \"adjust\":\n            stored + b - before => stored\n            stored => copy\n        out + [[r[1], b, stored, copy]] => out\n    return out\n\ndef permutations(xs):\n    if len(xs) <= 1:\n        return [xs]\n    [] => out\n    for i in [0:len(xs) - 1]:\n        [] => rest\n        for j in [0:len(xs) - 1]:\n            if j != i:\n                rest + [xs[j]] => rest\n        for p in permutations(rest):\n            out + [[xs[i]] + p] => out\n    return out\n\ndef orders(log):\n    # [orders, distinct final balances, orders ending as the log does]\n    replay(log) => right\n    {} => seen\n    0 => same\n    permutations(log) => ps\n    for p in ps:\n        replay(p) => b\n        1 => seen[b]\n        if b == right:\n            same + 1 => same\n    return [len(ps), len(seen), same]\n\ndef commutes(a, b, top):\n    # a then b equals b then a from every balance 0 to top\n    for x in [0:top]:\n        if apply(apply(x, a)[0], b)[0] != apply(apply(x, b)[0], a)[0]:\n            return False\n    return True\n\ndef pairs(log, top):\n    # [pairs, pairs that commute]\n    0 => n\n    0 => c\n    for i in [0:len(log) - 2]:\n        for j in [i + 1:len(log) - 1]:\n            n + 1 => n\n            if commutes(log[i], log[j], top):\n                c + 1 => c\n    return [n, c]\n",
      "python": "def default_log():\n    return [[\"deposit\", 100], [\"withdraw\", 30], [\"fee\", 10], [\"deposit\", 50], [\"adjust\", 20], [\"withdraw\", 120]]\n\ndef div(p, q):\n    f = int(p / q)\n    while f * q > p:\n        f = f - 1\n    while (f + 1) * q <= p:\n        f = f + 1\n    return f\n\ndef apply(balance, ev):\n    if ev[0] == \"deposit\":\n        return [balance + ev[1], \"deposit \" + str(ev[1])]\n    if ev[0] == \"withdraw\":\n        if ev[1] > balance:\n            return [balance, \"withdraw \" + str(ev[1]) + \" refused: only \" + str(balance)]\n        return [balance - ev[1], \"withdraw \" + str(ev[1])]\n    if ev[0] == \"fee\":\n        f = div(balance * ev[1], 100)\n        return [balance - f, \"fee \" + str(ev[1]) + \"% = \" + str(f)]\n    return [balance + ev[1], \"adjust \" + signed(ev[1])]\n\ndef signed(n):\n    if n < 0:\n        return str(n)\n    return \"+\" + str(n)\n\ndef replay(log):\n    b = 0\n    for ev in log:\n        b = apply(b, ev)[0]\n    return b\n\ndef ledger(log):\n    b = 0\n    stored = 0\n    copy = 0\n    out = []\n    for ev in log:\n        before = b\n        r = apply(b, ev)\n        b = r[0]\n        if ev[0] != \"adjust\":\n            stored = stored + b - before\n            copy = stored\n        out = out + [[r[1], b, stored, copy]]\n    return out\n\ndef permutations(xs):\n    if len(xs) <= 1:\n        return [xs]\n    out = []\n    for i in range(0, len(xs)):\n        rest = []\n        for j in range(0, len(xs)):\n            if j != i:\n                rest = rest + [xs[j]]\n        for p in permutations(rest):\n            out = out + [[xs[i]] + p]\n    return out\n\ndef orders(log):\n    right = replay(log)\n    seen = {}\n    same = 0\n    ps = permutations(log)\n    for p in ps:\n        b = replay(p)\n        seen[b] = 1\n        if b == right:\n            same = same + 1\n    return [len(ps), len(seen), same]\n\ndef commutes(a, b, top):\n    for x in range(0, top+1):\n        if apply(apply(x, a)[0], b)[0] != apply(apply(x, b)[0], a)[0]:\n            return False\n    return True\n\ndef pairs(log, top):\n    n = 0\n    c = 0\n    for i in range(0, len(log) - 2+1):\n        for j in range(i + 1, len(log)):\n            n = n + 1\n            if commutes(log[i], log[j], top):\n                c = c + 1\n    return [n, c]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n5\n4\n\n4\npay 10\n4\ndeposit\n4\ndeposit x\n4\ndeposit 0\n4\ndeposit 10000\n4\nfee 101\n4\nwithdraw -5\n4\nadjust -15\n4\n5\n5\n5\n5\n5\n3\n5\n1\n2\n3\n5\n6\n",
      "screen": "== Event log ==\nAn account kept two ways: the balance replayed from its events, and a stored balance column that each write path updates.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 5 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> \nCancelled.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> pay 10\nType deposit, withdraw, fee or adjust, then a number.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> deposit\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> deposit x\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> deposit 0\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> deposit 10000\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> fee 101\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> withdraw -5\nType a whole number up to 9999 (a fee from 1 to 100).\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> adjust -15\nAdded: adjust -15.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\n6 events is the most.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 5 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 4 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 3 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 2 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 1 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 3\nTwo events or more are needed.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 0 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 1\nThe log is empty.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 2\nThe log is empty.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 3\nTwo events or more are needed.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nThe log is empty.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n5\n1\n2\n3\n4\nfee 5\n3\n6\n",
      "screen": "== Event log ==\nAn account kept two ways: the balance replayed from its events, and a stored balance column that each write path updates.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 1\n  #  event                              replayed  stored\n  1  deposit 100                             100     100\n  2  withdraw 30                              70      70\n  3  fee 10% = 7                              63      63\n  4  deposit 50                              113     113\n  5  adjust +20                              133     113\n  6  withdraw 120                             13      -7\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 2\nThe check the system has: the stored column, -7, against the copy the same write paths keep, -7: no difference.\nReplaying the events: 13, and stored says -7: off by -20.\nThey part at event 5, adjust +20: that write path never touches the stored column.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 3\nEvery order of these 6 events, replayed: 720 orders, 40 different final balances; 8 end as the log does.\nOf the 15 pairs of events, 3 give the same balance in either order from every starting balance from 0 to 300.\nReplaying gives the truth only in the order the events happened: the log's order is part of what it records.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 5\nRemoved the last event; 5 left.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 1\n  #  event                              replayed  stored\n  1  deposit 100                             100     100\n  2  withdraw 30                              70      70\n  3  fee 10% = 7                              63      63\n  4  deposit 50                              113     113\n  5  adjust +20                              133     113\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 2\nThe check the system has: the stored column, 113, against the copy the same write paths keep, 113: no difference.\nReplaying the events: 133, and stored says 113: off by -20.\nThey part at event 5, adjust +20: that write path never touches the stored column.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 3\nEvery order of these 5 events, replayed: 120 orders, 19 different final balances; 6 end as the log does.\nOf the 10 pairs of events, 3 give the same balance in either order from every starting balance from 0 to 300.\nReplaying gives the truth only in the order the events happened: the log's order is part of what it records.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 4\nAn event: deposit n, withdraw n, fee p (percent), or adjust n (either sign), such as withdraw 25.\nevent> fee 5\nAdded: fee 5%.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 3\nEvery order of these 6 events, replayed: 720 orders, 47 different final balances; 17 end as the log does.\nOf the 15 pairs of events, 3 give the same balance in either order from every starting balance from 0 to 300.\nReplaying gives the truth only in the order the events happened: the log's order is part of what it records.\n\n1) the log  2) reconcile  3) every order  4) add an event  5) remove the last  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "denormalized-total-drifts",
      "caseId": "295-denormalized-total-drifts",
      "title": "Denormalized total drifts — the reconciliation compared a number with itself"
    },
    {
      "slug": "state-from-events-vs-stored",
      "caseId": "289-state-from-events-vs-stored",
      "title": "State from events vs stored — replay is correct only for some orders"
    }
  ],
  "updated": "2026-10-17"
}
