diff --git a/BACKLOG.md b/BACKLOG.md new file mode 100644 index 0000000..5b61f99 --- /dev/null +++ b/BACKLOG.md @@ -0,0 +1,116 @@ +# Backlog + +Things deliberately deferred, with enough context to pick them up cold. Not a +wish list — everything here has been considered and consciously postponed, and +each entry says what would trigger doing it. + +Last reviewed: 2026-08-10 + +--- + +## Consolidating MCP tools + +**Trigger: the model starts picking the wrong tool, or `tools/list` overhead becomes measurable.** + +There are 23 tools. The often-quoted ceiling of ~20 is a rule of thumb, not a +threshold, and mis-selection is caused by tools that resemble each other far more +than by raw count — these are mostly distinct. It has been working well in +practice, so this is not currently a problem. + +The question that prompted this: could one `modify_drink` take an `action` of +create/update/delete and replace three tools? Technically yes, but it is the +wrong merge: + +- **Annotations stop meaning anything.** A tool that *can* delete has to be + marked `destructiveHint`, so a client that confirms destructive actions would + start prompting when you add a drink — or you mark it false and lose the + prompt when it matters. +- **Scope enforcement gets coarser.** Reads need `drinks:read` and writes need + `drinks:write`. A merged tool must demand the union (a read-only token loses + read access) or branch internally, at which point the declared scope no longer + describes the tool and the consent screen misleads. +- **The schema gets harder for the model.** `id` is required for update/delete + and meaningless for create. That is a discriminated union, which converts to + `anyOf` — exactly the shape models fill in wrong. +- **The audit trail gets vaguer.** `McpAuditLog` records the tool name; + `delete_drink` says what happened, `modify_drink` does not. + +The rule worth keeping: **merge when it is the same gesture with a different +parameter; keep separate when the intent or the risk differs.** `upsert_bar_item` +("I bought gin" / "I finished the gin") and `promote_recipe` (drink vs wishlist) +are good merges. CRUD is not. + +Cheaper levers if the count ever needs to come down: + +1. `MCP_CHATGPT_TOOLS=0` drops the `search`/`fetch` aliases: 23 → 21, free if + ChatGPT is unused. +2. Remove low-value tools rather than merging good ones. `update_rating` is the + weakest — a drink can simply be re-rated. +3. Sharpen descriptions. Selection failures are usually a description problem. + +Worth measuring the real `tools/list` payload cost before spending effort here. + +--- + +## Refresh-token grace window + +**Trigger: a client is forced to re-authorize after a dropped network response.** + +Reuse of any refresh generation currently revokes the whole family, with no +grace. That is correct against theft but harsh on a client whose response was +lost in transit — it replays legitimately and destroys its own grant. + +The audit's advice was to build durable lineage first and only then consider +grace. Lineage now exists (`OAuthRefreshToken`, one row per generation, atomic +consume), so this is a safe change to make if it ever bites. Auth0 disables +leeway by default; Okta defaults to 30 seconds, configurable to 60. A 5–10 +second window limited to the immediately preceding generation would be +reasonable — anything older must still revoke the family. + +--- + +## Encrypt backup dumps + +**Trigger: backups leave the LAN, for any reason.** + +`pg_dump` output goes to TrueNAS unencrypted and contains every user's data plus +bcrypt password hashes. Defensible on a NAS you control on your own network; +not defensible in cloud storage, a friend's NAS, or off-site replication. + +`gpg` or `age` before the `scp` in `/usr/local/bin/drinktracker-backup` is a +couple of lines. Much easier to add before there is history to re-encrypt. + +Related: the off-site copy is in the same building, so it survives losing the +LXC or the Proxmox host but not fire or theft. + +--- + +## Purge the retained Docker artifacts + +**Trigger: 2026-09-07.** + +Docker is installed but disabled on the LXC; old containers and volumes were +kept as a rollback path after the move to native systemd. Nothing has needed +them. Remove after that date. + +--- + +## Establish why Docker actually failed in this LXC + +**Trigger: only if Docker is ever wanted on this host again.** + +`deploy/README.md` originally blamed unprivileged-LXC nesting. That is wrong — +`/proc/self/uid_map` reads `0 0 4294967295`, an identity mapping, so the +container is privileged. The AppArmor and `ip_unprivileged_port_start` failures +were real and reproduced, but the cause was never established. The doc now says +so rather than substituting a fresh guess. + +--- + +## Rotate the TrueNAS account password + +**Trigger: do it whenever convenient.** + +`getent passwd` run as root on TrueNAS prints the SHA-512 hash, and one ended up +in a session transcript. Salted and not trivially reversible, but crackable +offline against a weak password.