Lane 10 PR 5: bot commands and /concern (#736) #885

Merged
claude-bot merged 7 commits from build/lane10-5-commands into main 2026-09-27 21:37:44 +00:00
Collaborator

Part of #736 (lane 10). Stacked on #883 (4b). No migration.

What it builds

  • /recap by number (O9). GET /api/bot/guilds/{guild_id}/sessions/by-number/{n} is new, and the UUID form still works.
  • Which campaign a command means. Guild reads (/next-session, /quests, /sessions/list, /sessions/ask) now take channel_id/campaign_id. When it's ambiguous you get a 409 naming the campaigns, and the bot offers a campaign: choice on /next, /quests, /recap, /history and /ask.
  • /history and /ask answer privately. /history lists sessions by number, with a real "… and N more".
  • /ask cites its sources (O16). The server computes sources from the sessions it actually gave the model, never from numbers the AI writes. The reply says "From Sessions 12 and 13. It can be wrong: check the session if it matters."
  • Words and labels:
    • /note and /help follow D97/D106;
    • /record start's picker labels lead with "Session N ·";
    • /next shows RSVP counts.
  • Presence (O11) and the join welcome were already built in 4a/4b; I checked them.
  • /concern (O13, D160, IS-211), in a new cogs/safety.py. It's an ephemeral modal that posts to a new POST /api/bot/safety/submissions, which writes to the same anonymous queue as the web's Table rules & safety flag.
    • Nothing reaches a channel. Only the sender sees the modal and its reply, and nothing is posted anywhere else.
    • Nothing identifies the sender. discord_user_id is only used to resolve the campaign and check membership. It isn't stored (the row has no sender column), and the route is kept out of the access log.
    • A membership check follows any campaign resolution, whether by guild, DM or explicit choice.

Bot contract

Additive; it stays at v2. The changes are new routes, new optional params and new optional response fields, and check_version_sync.py passes. The bot client's models also gain number fields the backend was already sending.

Checks

  • Bot suite: 655 passed.
  • Backend: 165 passed across the touched and calling modules, including the new test_bot_safety_submission.py (13 cases, among them "the row carries no sender" and "kept out of the access log"). CI runs the full suite.
  • ruff: clean. The bot/ directory was not reformatted.
  • Changelog: a changelog.d/ fragment.

Departures (for review)

  1. /concern in DMs is native slash-command behaviour. No separate "DM helper" was needed. Whether Discord offers the command in DMs depends on the application's install-context settings in the Developer Portal, so that needs a manual check on a test server.
  2. /note's confirmation says "Your GM" rather than naming one. A campaign can have several GMs.
  3. The shared "Quest Board did not answer" fallback strings weren't reworded to the drawing's exact words. That's left for a later tidy.
  4. Choosing a campaign explicitly (campaign_id) was added beyond the survey's channel_id, to finish O9's "it asks which campaign" loop.

🤖 Generated with Claude Code

Part of #736 (lane 10). **Stacked on #883 (4b).** No migration. ## What it builds - **`/recap` by number (O9).** `GET /api/bot/guilds/{guild_id}/sessions/by-number/{n}` is new, and the UUID form still works. - **Which campaign a command means.** Guild reads (`/next-session`, `/quests`, `/sessions/list`, `/sessions/ask`) now take `channel_id`/`campaign_id`. When it's ambiguous you get a 409 naming the campaigns, and the bot offers a `campaign:` choice on `/next`, `/quests`, `/recap`, `/history` and `/ask`. - **`/history` and `/ask` answer privately.** `/history` lists sessions by number, with a real "… and N more". - **`/ask` cites its sources (O16).** The server computes `sources` from the sessions it actually gave the model, never from numbers the AI writes. The reply says "From Sessions 12 and 13. It can be wrong: check the session if it matters." - **Words and labels:** - `/note` and `/help` follow D97/D106; - `/record start`'s picker labels lead with "Session N ·"; - `/next` shows RSVP counts. - **Presence (O11) and the join welcome** were already built in 4a/4b; I checked them. - **`/concern` (O13, D160, IS-211)**, in a new `cogs/safety.py`. It's an ephemeral modal that posts to a new `POST /api/bot/safety/submissions`, which writes to the **same anonymous queue** as the web's Table rules & safety flag. - **Nothing reaches a channel.** Only the sender sees the modal and its reply, and nothing is posted anywhere else. - **Nothing identifies the sender.** `discord_user_id` is only used to resolve the campaign and check membership. It isn't stored (the row has no sender column), and the route is kept out of the access log. - **A membership check follows** any campaign resolution, whether by guild, DM or explicit choice. ## Bot contract **Additive; it stays at v2.** The changes are new routes, new optional params and new optional response fields, and `check_version_sync.py` passes. The bot client's models also gain `number` fields the backend was already sending. ## Checks - **Bot suite:** 655 passed. - **Backend:** 165 passed across the touched and calling modules, including the new `test_bot_safety_submission.py` (13 cases, among them "the row carries no sender" and "kept out of the access log"). CI runs the full suite. - **ruff:** clean. The `bot/` directory was not reformatted. - **Changelog:** a `changelog.d/` fragment. ## Departures (for review) 1. **`/concern` in DMs is native slash-command behaviour.** No separate "DM helper" was needed. Whether Discord offers the command in DMs depends on the application's install-context settings in the Developer Portal, so that needs a manual check on a test server. 2. **`/note`'s confirmation says "Your GM" rather than naming one.** A campaign can have several GMs. 3. **The shared "Quest Board did not answer" fallback strings weren't reworded** to the drawing's exact words. That's left for a later tidy. 4. **Choosing a campaign explicitly** (`campaign_id`) was added beyond the survey's `channel_id`, to finish O9's "it asks which campaign" loop. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- /recap now also takes a session's number, resolved to the right campaign
  when a guild binds more than one (O9); the same channel/campaign resolution
  is added to /next, /quests, /history and /ask, each with a campaign:
  autocomplete for when it must ask.
- /history and /ask now answer privately; /history lists session numbers
  instead of UUIDs, with a real "… and N more" count; /ask names the sessions
  its answer was drawn from, worked out by Quest Board rather than the model
  (O16). /next shows RSVP counts.
- /note and /help's words follow D97/D106; the "no campaign linked" and
  /record start picker wording follow the drawing.
- New /concern (O13, D160, IS-211): a private Discord form, in a server or a
  DM, that reaches only the campaign's GMs through the same anonymous queue
  as the web's Table rules & safety flag. Never echoed into a channel, never
  logged.
- Backend: GET .../sessions/by-number/{n} (additive); channel_id/campaign_id
  on the guild-scoped reads, 409 with campaign names when ambiguous;
  /sessions/ask gains `sources`; POST /api/bot/safety/submissions is
  /concern's door into the existing safety queue, kept out of the access log.
  No migration. BOT_CONTRACT_VERSION stays at 2 on both sides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claude-bot changed target branch from build/lane10-4b-reactions to main 2026-09-27 20:39:22 +00:00
Merge main into build/lane10-5-commands
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Bot tests and audit (pull_request) Successful in 2m8s
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Bot/backend version sync (pull_request) Successful in 25s
CI / Feedback site tests (pull_request) Successful in 41s
CI / Docker image build (pull_request) Successful in 29s
CI / Does this change touch the recording path? (pull_request) Successful in 24s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 14m16s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 22m34s
CI / Backend migration, tests, and audit (pull_request) Successful in 46m49s
CI / Frontend tests, audit, and build (pull_request) Successful in 50m40s
63cebad0e6
Sign in to join this conversation.
No description provided.