{
  "id": "P159",
  "slug": "risk-flags",
  "key": "P159-risk-flags",
  "title": "Risk flags",
  "summary": "Accounts flagged from the signals against them, the flag stored bare and with its evidence: a withdrawn signal leaves the bare flag standing, and the evidence store recomputes just the flags that rested on it.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P159 - Risk flags\n\nAccounts flagged from the signals against them, the flag stored two ways:\nas a bare yes or no, and with the evidence it rests on.\n\n- `main.eml` - the menu, the accounts with their signals and both stored\n  flags, withdrawing a signal, adding one, the checks, and the nightly\n  recompute\n- `flags.eml` - the accounts and their signals, the rule, the evidence a\n  flag rests on, both stores built from the signals, and finding and\n  removing a signal\n\nHow each part works:\n\n- The rule: two or more signals against an account, or any one of severity\n  3. Five accounts start with seven signals - a chargeback on a-1, two\n  small signals on a-2, a report and a new device on a-4 - and a-1, a-2 and\n  a-4 are flagged.\n- As in the corpus case `flag-outlives-its-evidence`, a flag is computed\n  from what the system knew and stored. Withdraw the chargeback on a-1 and\n  the bare flag stays: nothing records that it came from that signal, so\n  nothing knows to clear it. The flag kept with its evidence rested on\n  that signal, so it is recomputed, and clears.\n- Withdrawing only ever makes a flag stale; a new signal recomputes its\n  account in both stores, so neither ever misses a flag. The checks compare\n  both stores with the rule on the signals on file, split by direction.\n- The nightly recompute rights the bare store, a day late and only by\n  recomputing every flag; the evidence store recomputes just the flags that\n  rested on what was withdrawn.\n\nWhat is checked: menu choices 1 to 6; a signal on file to withdraw; an\naccount from 1 to 5 and a severity from 1 to 3 for a new signal. An empty\nanswer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the accounts and the checks; s-1 and s-3 withdrawn; the accounts and the\n  checks again;\n- a severity-3 signal added on a-3; the accounts, the nightly recompute and\n  the checks.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- withdrawing: a cancel, s-99, then s-1 twice;\n- adding: a cancel and 6 for the account, then a-2 with a cancel, 4 and x\n  for the severity; a signal on a-5;\n- the nightly recompute twice, and the checks.\n\nBuilt on the verified corpus case `flag-outlives-its-evidence` (a stored\nflag with no link back to the evidence it was derived from, re-derived and\ncompared by direction).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P159 risk flags: the accounts with their signals and both stored flags,\n# withdrawing a signal, adding one, the nightly recompute, and the checks\n# against the flags re-derived now.\nimport flags\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 yes(b):\n    if b:\n        return \"flagged\"\n    return \"-\"\n\ndef listed(xs):\n    \"\" => out\n    for x in xs:\n        if out == \"\":\n            x => out\n        else:\n            out + \", \" + x => out\n    if out == \"\":\n        return \"none\"\n    return out\n\ndef show(signals, pl, pr):\n    (\"  \" + pad(\"account\", 9) + pad(\"signals\", 22) + pad(\"now\", 9) + pad(\"flag only\", 11) + \"flag with its evidence\") ^0\n    for a in flags.accounts():\n        [] => ids\n        for s in flags.against(signals, a):\n            ids + [s[0] + \":\" + str(s[2])] => ids\n        if len(pr[a]) > 0:\n            \"flagged (\" + listed(pr[a]) + \")\" => ev\n        else:\n            \"-\" => ev\n        (\"  \" + pad(a, 9) + pad(listed(ids), 22) + pad(yes(flags.derive(signals, a)), 9) + pad(yes(pl[a]), 11) + ev) ^0\n    \"  signal:severity; the rule: two or more signals, or any one of severity 3\" ^0\n\ndef withdraw(signals, pl, pr):\n    plain(input(\"signal to withdraw (e.g. s-1)> \")) => id\n    if id == \"\":\n        \"Cancelled.\" ^0\n        return [signals, pl, pr]\n    flags.find(signals, id) => k\n    if k < 0:\n        (\"There is no signal \" + id + \" on file.\") ^0\n        return [signals, pl, pr]\n    signals[k] => s\n    flags.without(signals, id) => after\n    (\"Signal \" + id + \" (\" + s[3] + \" on \" + s[1] + \") withdrawn.\") ^0\n    if pl[s[1]]:\n        (\"  flag only: \" + s[1] + \" stays flagged; nothing records that the flag came from \" + id + \".\") ^0\n    else:\n        (\"  flag only: \" + s[1] + \" was not flagged, and nothing changes.\") ^0\n    {} => npr\n    [] => touched\n    for a in flags.accounts():\n        False => rested\n        for e in pr[a]:\n            if e == id:\n                True => rested\n        if rested:\n            flags.evidence(after, a) => npr[a]\n            touched + [a] => touched\n        else:\n            pr[a] => npr[a]\n    if len(touched) == 0:\n        \"  with its evidence: no flag rested on it, and nothing is recomputed.\" ^0\n    for a in touched:\n        if len(npr[a]) == 0:\n            (\"  with its evidence: \" + a + \"'s flag rested on \" + id + \"; recomputed, it clears.\") ^0\n        else:\n            (\"  with its evidence: \" + a + \"'s flag rested on \" + id + \"; recomputed, it stands on \" + listed(npr[a]) + \".\") ^0\n    return [after, pl, npr]\n\ndef add(signals, pl, pr, n):\n    plain(input(\"account (1 to 5)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return [signals, pl, pr, n]\n    if t != \"1\" and t != \"2\" and t != \"3\" and t != \"4\" and t != \"5\":\n        \"Type an account from 1 to 5.\" ^0\n        return [signals, pl, pr, n]\n    flags.accounts()[int(t) - 1] => a\n    plain(input(\"severity (1 to 3)> \")) => v\n    if v == \"\":\n        \"Cancelled.\" ^0\n        return [signals, pl, pr, n]\n    if v != \"1\" and v != \"2\" and v != \"3\":\n        \"Type a severity from 1 to 3.\" ^0\n        return [signals, pl, pr, n]\n    \"s-\" + str(n) => id\n    signals + [[id, a, int(v), \"new signal\"]] => after\n    {} => npl\n    {} => npr\n    for x in flags.accounts():\n        pl[x] => npl[x]\n        pr[x] => npr[x]\n    flags.derive(after, a) => npl[a]\n    flags.evidence(after, a) => npr[a]\n    (\"Signal \" + id + \" of severity \" + v + \" added on \" + a + \"; both stores recompute \" + a + \": \" + yes(npl[a]) + \".\") ^0\n    return [after, npl, npr, n + 1]\n\ndef checks(signals, pl, pr):\n    [] => stale1\n    [] => missing1\n    [] => stale2\n    [] => missing2\n    for a in flags.accounts():\n        flags.derive(signals, a) => now\n        if pl[a] and not now:\n            stale1 + [a] => stale1\n        if not pl[a] and now:\n            missing1 + [a] => missing1\n        if len(pr[a]) > 0 and not now:\n            stale2 + [a] => stale2\n        if len(pr[a]) == 0 and now:\n            missing2 + [a] => missing2\n    \"Each stored flag against the flag the rule gives on the signals on file now:\" ^0\n    (\"  flag only:              should have cleared \" + listed(stale1) + \"; should have been raised \" + listed(missing1)) ^0\n    (\"  flag with its evidence: should have cleared \" + listed(stale2) + \"; should have been raised \" + listed(missing2)) ^0\n    if len(stale1) > 0:\n        \"A withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to.\" ^0\n\ndef nightly(signals, pl):\n    flags.plain_store(signals) => npl\n    0 => changed\n    for a in flags.accounts():\n        if npl[a] != pl[a]:\n            changed + 1 => changed\n    if changed == 0:\n        \"none changes\" => said\n    elif changed == 1:\n        \"1 changes\" => said\n    else:\n        str(changed) + \" change\" => said\n    (\"The nightly job recomputes all \" + str(len(flags.accounts())) + \" flags from the signals on file; \" + said + \".\") ^0\n    if changed > 0:\n        \"It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.\" ^0\n    else:\n        \"Nothing had gone stale since the last recompute.\" ^0\n    return npl\n\nflags.opening() => signals\nflags.plain_store(signals) => pl\nflags.proven_store(signals) => pr\n8 => next_id\n\n\"== Risk flags ==\" ^0\n\"Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(signals, pl, pr)\n    elif choice == \"2\":\n        withdraw(signals, pl, pr) => r\n        r[0] => signals\n        r[1] => pl\n        r[2] => pr\n    elif choice == \"3\":\n        add(signals, pl, pr, next_id) => r\n        r[0] => signals\n        r[1] => pl\n        r[2] => pr\n        r[3] => next_id\n    elif choice == \"4\":\n        checks(signals, pl, pr)\n    elif choice == \"5\":\n        nightly(signals, pl) => pl\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import flags\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 yes(b):\n    if b:\n        return \"flagged\"\n    return \"-\"\n\ndef listed(xs):\n    out = \"\"\n    for x in xs:\n        if out == \"\":\n            out = x\n        else:\n            out = out + \", \" + x\n    if out == \"\":\n        return \"none\"\n    return out\n\ndef show(signals, pl, pr):\n    print(\"  \" + pad(\"account\", 9) + pad(\"signals\", 22) + pad(\"now\", 9) + pad(\"flag only\", 11) + \"flag with its evidence\")\n    for a in flags.accounts():\n        ids = []\n        for s in flags.against(signals, a):\n            ids = ids + [s[0] + \":\" + str(s[2])]\n        if len(pr[a]) > 0:\n            ev = \"flagged (\" + listed(pr[a]) + \")\"\n        else:\n            ev = \"-\"\n        print(\"  \" + pad(a, 9) + pad(listed(ids), 22) + pad(yes(flags.derive(signals, a)), 9) + pad(yes(pl[a]), 11) + ev)\n    print(\"  signal:severity; the rule: two or more signals, or any one of severity 3\")\n\ndef withdraw(signals, pl, pr):\n    id = plain(input(\"signal to withdraw (e.g. s-1)> \"))\n    if id == \"\":\n        print(\"Cancelled.\")\n        return [signals, pl, pr]\n    k = flags.find(signals, id)\n    if k < 0:\n        print(\"There is no signal \" + id + \" on file.\")\n        return [signals, pl, pr]\n    s = signals[k]\n    after = flags.without(signals, id)\n    print(\"Signal \" + id + \" (\" + s[3] + \" on \" + s[1] + \") withdrawn.\")\n    if pl[s[1]]:\n        print(\"  flag only: \" + s[1] + \" stays flagged; nothing records that the flag came from \" + id + \".\")\n    else:\n        print(\"  flag only: \" + s[1] + \" was not flagged, and nothing changes.\")\n    npr = {}\n    touched = []\n    for a in flags.accounts():\n        rested = False\n        for e in pr[a]:\n            if e == id:\n                rested = True\n        if rested:\n            npr[a] = flags.evidence(after, a)\n            touched = touched + [a]\n        else:\n            npr[a] = pr[a]\n    if len(touched) == 0:\n        print(\"  with its evidence: no flag rested on it, and nothing is recomputed.\")\n    for a in touched:\n        if len(npr[a]) == 0:\n            print(\"  with its evidence: \" + a + \"'s flag rested on \" + id + \"; recomputed, it clears.\")\n        else:\n            print(\"  with its evidence: \" + a + \"'s flag rested on \" + id + \"; recomputed, it stands on \" + listed(npr[a]) + \".\")\n    return [after, pl, npr]\n\ndef add(signals, pl, pr, n):\n    t = plain(input(\"account (1 to 5)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return [signals, pl, pr, n]\n    if t != \"1\" and t != \"2\" and t != \"3\" and t != \"4\" and t != \"5\":\n        print(\"Type an account from 1 to 5.\")\n        return [signals, pl, pr, n]\n    a = flags.accounts()[int(t) - 1]\n    v = plain(input(\"severity (1 to 3)> \"))\n    if v == \"\":\n        print(\"Cancelled.\")\n        return [signals, pl, pr, n]\n    if v != \"1\" and v != \"2\" and v != \"3\":\n        print(\"Type a severity from 1 to 3.\")\n        return [signals, pl, pr, n]\n    id = \"s-\" + str(n)\n    after = signals + [[id, a, int(v), \"new signal\"]]\n    npl = {}\n    npr = {}\n    for x in flags.accounts():\n        npl[x] = pl[x]\n        npr[x] = pr[x]\n    npl[a] = flags.derive(after, a)\n    npr[a] = flags.evidence(after, a)\n    print(\"Signal \" + id + \" of severity \" + v + \" added on \" + a + \"; both stores recompute \" + a + \": \" + yes(npl[a]) + \".\")\n    return [after, npl, npr, n + 1]\n\ndef checks(signals, pl, pr):\n    stale1 = []\n    missing1 = []\n    stale2 = []\n    missing2 = []\n    for a in flags.accounts():\n        now = flags.derive(signals, a)\n        if pl[a] and not now:\n            stale1 = stale1 + [a]\n        if not pl[a] and now:\n            missing1 = missing1 + [a]\n        if len(pr[a]) > 0 and not now:\n            stale2 = stale2 + [a]\n        if len(pr[a]) == 0 and now:\n            missing2 = missing2 + [a]\n    print(\"Each stored flag against the flag the rule gives on the signals on file now:\")\n    print(\"  flag only:              should have cleared \" + listed(stale1) + \"; should have been raised \" + listed(missing1))\n    print(\"  flag with its evidence: should have cleared \" + listed(stale2) + \"; should have been raised \" + listed(missing2))\n    if len(stale1) > 0:\n        print(\"A withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to.\")\n\ndef nightly(signals, pl):\n    npl = flags.plain_store(signals)\n    changed = 0\n    for a in flags.accounts():\n        if npl[a] != pl[a]:\n            changed = changed + 1\n    if changed == 0:\n        said = \"none changes\"\n    elif changed == 1:\n        said = \"1 changes\"\n    else:\n        said = str(changed) + \" change\"\n    print(\"The nightly job recomputes all \" + str(len(flags.accounts())) + \" flags from the signals on file; \" + said + \".\")\n    if changed > 0:\n        print(\"It clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.\")\n    else:\n        print(\"Nothing had gone stale since the last recompute.\")\n    return npl\n\nsignals = flags.opening()\npl = flags.plain_store(signals)\npr = flags.proven_store(signals)\nnext_id = 8\nprint(\"== Risk flags ==\")\nprint(\"Accounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(signals, pl, pr)\n    elif choice == \"2\":\n        r = withdraw(signals, pl, pr)\n        signals = r[0]\n        pl = r[1]\n        pr = r[2]\n    elif choice == \"3\":\n        r = add(signals, pl, pr, next_id)\n        signals = r[0]\n        pl = r[1]\n        pr = r[2]\n        next_id = r[3]\n    elif choice == \"4\":\n        checks(signals, pl, pr)\n    elif choice == \"5\":\n        pl = nightly(signals, pl)\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "flags.eml",
      "eml": "# P159 risk flags - accounts flagged from the signals against them, the flag\n# stored two ways. As in the corpus case flag-outlives-its-evidence, a flag\n# is computed once from what the system knew and stored as a yes or no; the\n# signals it came from can be withdrawn - a chargeback reversed, a report\n# retracted - and with no record of which signals a flag came from, nothing\n# knows to clear it. The second store keeps each flag's evidence beside it,\n# so a withdrawn signal reaches exactly the flags that rested on it. A\n# signal is [id, account, severity, what]; the rule: two or more signals,\n# or any one of severity 3.\n\ndef accounts():\n    return [\"a-1\", \"a-2\", \"a-3\", \"a-4\", \"a-5\"]\n\ndef opening():\n    return [[\"s-1\", \"a-1\", 3, \"chargeback\"], [\"s-2\", \"a-2\", 1, \"address change\"], [\"s-3\", \"a-2\", 1, \"new device\"], [\"s-4\", \"a-3\", 2, \"velocity\"], [\"s-5\", \"a-4\", 1, \"new device\"], [\"s-6\", \"a-4\", 2, \"report\"], [\"s-7\", \"a-5\", 1, \"address change\"]]\n\ndef against(signals, account):\n    [] => out\n    for s in signals:\n        if s[1] == account:\n            out + [s] => out\n    return out\n\ndef derive(signals, account):\n    # the rule, as it lives in the code\n    against(signals, account) => mine\n    if len(mine) >= 2:\n        return True\n    for s in mine:\n        if s[2] >= 3:\n            return True\n    return False\n\ndef evidence(signals, account):\n    # the ids a flag rests on: the account's signals when the rule raises it\n    [] => ids\n    if derive(signals, account):\n        for s in against(signals, account):\n            ids + [s[0]] => ids\n    return ids\n\ndef plain_store(signals):\n    # account -> flag, computed from the signals as they are now\n    {} => st\n    for a in accounts():\n        derive(signals, a) => st[a]\n    return st\n\ndef proven_store(signals):\n    # account -> the evidence ids its flag rests on ([] for no flag)\n    {} => st\n    for a in accounts():\n        evidence(signals, a) => st[a]\n    return st\n\ndef find(signals, id):\n    0 => i\n    for s in signals:\n        if s[0] == id:\n            return i\n        i + 1 => i\n    return 0 - 1\n\ndef without(signals, id):\n    [] => out\n    for s in signals:\n        if s[0] != id:\n            out + [s] => out\n    return out\n",
      "python": "def accounts():\n    return [\"a-1\", \"a-2\", \"a-3\", \"a-4\", \"a-5\"]\n\ndef opening():\n    return [[\"s-1\", \"a-1\", 3, \"chargeback\"], [\"s-2\", \"a-2\", 1, \"address change\"], [\"s-3\", \"a-2\", 1, \"new device\"], [\"s-4\", \"a-3\", 2, \"velocity\"], [\"s-5\", \"a-4\", 1, \"new device\"], [\"s-6\", \"a-4\", 2, \"report\"], [\"s-7\", \"a-5\", 1, \"address change\"]]\n\ndef against(signals, account):\n    out = []\n    for s in signals:\n        if s[1] == account:\n            out = out + [s]\n    return out\n\ndef derive(signals, account):\n    mine = against(signals, account)\n    if len(mine) >= 2:\n        return True\n    for s in mine:\n        if s[2] >= 3:\n            return True\n    return False\n\ndef evidence(signals, account):\n    ids = []\n    if derive(signals, account):\n        for s in against(signals, account):\n            ids = ids + [s[0]]\n    return ids\n\ndef plain_store(signals):\n    st = {}\n    for a in accounts():\n        st[a] = derive(signals, a)\n    return st\n\ndef proven_store(signals):\n    st = {}\n    for a in accounts():\n        st[a] = evidence(signals, a)\n    return st\n\ndef find(signals, id):\n    i = 0\n    for s in signals:\n        if s[0] == id:\n            return i\n        i = i + 1\n    return 0 - 1\n\ndef without(signals, id):\n    out = []\n    for s in signals:\n        if s[0] != id:\n            out = out + [s]\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\ns-99\n2\ns-1\n2\ns-1\n3\n\n3\n6\n3\n2\n\n3\n2\n4\n3\n2\nx\n3\n5\n1\n5\n5\n4\n6\n",
      "screen": "== Risk flags ==\nAccounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> \nCancelled.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> s-99\nThere is no signal s-99 on file.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> s-1\nSignal s-1 (chargeback on a-1) withdrawn.\n  flag only: a-1 stays flagged; nothing records that the flag came from s-1.\n  with its evidence: a-1's flag rested on s-1; recomputed, it clears.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> s-1\nThere is no signal s-1 on file.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> \nCancelled.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 6\nType an account from 1 to 5.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 2\nseverity (1 to 3)> \nCancelled.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 2\nseverity (1 to 3)> 4\nType a severity from 1 to 3.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 2\nseverity (1 to 3)> x\nType a severity from 1 to 3.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 5\nseverity (1 to 3)> 1\nSignal s-8 of severity 1 added on a-5; both stores recompute a-5: flagged.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 5\nThe nightly job recomputes all 5 flags from the signals on file; 1 changes.\nIt clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 5\nThe nightly job recomputes all 5 flags from the signals on file; none changes.\nNothing had gone stale since the last recompute.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 4\nEach stored flag against the flag the rule gives on the signals on file now:\n  flag only:              should have cleared none; should have been raised none\n  flag with its evidence: should have cleared none; should have been raised none\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n4\n2\ns-1\n2\ns-3\n1\n4\n3\n3\n3\n1\n5\n4\n6\n",
      "screen": "== Risk flags ==\nAccounts flagged from the signals against them: a bare flag, and a flag that keeps its evidence.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 1\n  account  signals               now      flag only  flag with its evidence\n  a-1      s-1:3                 flagged  flagged    flagged (s-1)\n  a-2      s-2:1, s-3:1          flagged  flagged    flagged (s-2, s-3)\n  a-3      s-4:2                 -        -          -\n  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)\n  a-5      s-7:1                 -        -          -\n  signal:severity; the rule: two or more signals, or any one of severity 3\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 4\nEach stored flag against the flag the rule gives on the signals on file now:\n  flag only:              should have cleared none; should have been raised none\n  flag with its evidence: should have cleared none; should have been raised none\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> s-1\nSignal s-1 (chargeback on a-1) withdrawn.\n  flag only: a-1 stays flagged; nothing records that the flag came from s-1.\n  with its evidence: a-1's flag rested on s-1; recomputed, it clears.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 2\nsignal to withdraw (e.g. s-1)> s-3\nSignal s-3 (new device on a-2) withdrawn.\n  flag only: a-2 stays flagged; nothing records that the flag came from s-3.\n  with its evidence: a-2's flag rested on s-3; recomputed, it clears.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 1\n  account  signals               now      flag only  flag with its evidence\n  a-1      none                  -        flagged    -\n  a-2      s-2:1                 -        flagged    -\n  a-3      s-4:2                 -        -          -\n  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)\n  a-5      s-7:1                 -        -          -\n  signal:severity; the rule: two or more signals, or any one of severity 3\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 4\nEach stored flag against the flag the rule gives on the signals on file now:\n  flag only:              should have cleared a-1, a-2; should have been raised none\n  flag with its evidence: should have cleared none; should have been raised none\nA withdrawn signal leaves the bare flag standing: the answer survived, and nothing says what it was the answer to.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 3\naccount (1 to 5)> 3\nseverity (1 to 3)> 3\nSignal s-8 of severity 3 added on a-3; both stores recompute a-3: flagged.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 1\n  account  signals               now      flag only  flag with its evidence\n  a-1      none                  -        flagged    -\n  a-2      s-2:1                 -        flagged    -\n  a-3      s-4:2, s-8:3          flagged  flagged    flagged (s-4, s-8)\n  a-4      s-5:1, s-6:2          flagged  flagged    flagged (s-5, s-6)\n  a-5      s-7:1                 -        -          -\n  signal:severity; the rule: two or more signals, or any one of severity 3\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 5\nThe nightly job recomputes all 5 flags from the signals on file; 2 change.\nIt clears what a withdrawal left behind, a day late, and only because it recomputes every flag; the evidence store recomputed only the flags that rested on what was withdrawn.\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 4\nEach stored flag against the flag the rule gives on the signals on file now:\n  flag only:              should have cleared none; should have been raised none\n  flag with its evidence: should have cleared none; should have been raised none\n\n1) accounts  2) withdraw a signal  3) add a signal  4) checks  5) the nightly recompute  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "flag-outlives-its-evidence",
      "caseId": "297-flag-outlives-its-evidence",
      "title": "Flag outlives its evidence — the conclusion stayed and the link back was never stored"
    }
  ],
  "updated": "2026-10-18"
}
