feat(bot): a self-diagnosing bot, a /record start that knows the session, and linking that names the way out (phase 5 lane B) #521

Merged
claude-bot merged 5 commits from feat/phase5b-bot-self-diagnosis into main 2026-09-06 03:39:44 +00:00
Collaborator

Closes #373, closes #393, closes #392. Phase 5 lane B of the v4.3.0 build order (#514). Five commits; every API change is additive (new endpoints, new defaulted fields), so no BOT_CONTRACT_VERSION bump. No migration.

#373 self-diagnosis. api_client raises QuestBoardApiError(status, detail) (a subclass of httpx.HTTPStatusError, so the startup handshake and existing handlers are untouched); cogs show the backend's own sentence for a 4xx or a 503 (the named acceptance case, "LLM endpoint not configured", is a 503 that always carries actionable prose) and the generic message for a 500 or network failure. /next, /quests, /history distinguish "no campaign is bound to this server" from "nothing scheduled" via a new GET /api/bot/guilds/{id}/campaigns, consulted only on the empty path. /ask gets an additive reason: "no_transcripts" rather than a null answer that would crash an older bot. New Help cog: /help (a test asserts it covers every registered command), an on_guild_join welcome with the three setup steps and the guild ID pasted in, a presence set in the constructor so it survives reconnects. Also command_prefix=commands.when_mentioned, the one prefix discord.py exempts from the "message content intent is missing" warning seen on prod after v4.2.3, with a test.

#393 /record start. Reconciled with #399, which is left intact: the audio was already rescued on restart, but everything needed to say so died with the process. The active recording is now written to Redis DB 0 (qb:bot:recording:<guild_id>, 7-day TTL, best-effort), and announce_interrupted_recordings runs after #399's recovery to report which of three things happened (recovered and being transcribed; kept but not yet submitted; nothing captured). /record start's session argument autocompletes against a new GET /api/bot/guilds/{id}/sessions/recordable; the slash path sets session_title (the "Unknown Session" bug) with the regression test nothing had; a definite 404 refuses the start while a timeout still starts, since capture cannot be redone; defer() precedes the metadata fetch; /record stop fulfils its interaction; failure strings are written for a hosted customer with the exception in the log and a retry link.

#392 linking dead ends. Two small self-contained HTML pages replace the raw JSON 400/409, each naming the next step (/link again vs sign in as the other account and /unlink). /auth/link records the outcome and /api/bot/link-status/{token} gains an additive state (pending|linked|expired|conflict), so the poller speaks as soon as the outcome is known and names /unlink on a conflict. Closed DMs fall back to a self-deleting channel mention in both the linking poller and the pre-vote prompt. The vote reminder tells unlinked players to run /link; Profile.jsx names the command. Review note: the backend's SecurityHeadersMiddleware now emits default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none' for text/html responses only (the pages contain no user-controlled text); JSON responses keep the strict policy verbatim, with a test.

Bot 398 passed (+80), backend full 2378 passed / 13 skipped, frontend 514 passed, ruff and version-sync clean. One adjacent data-loss finding filed as #520 (raw tracks deleted on a mid-conversion failure).

🤖 Generated with Claude Code

Closes #373, closes #393, closes #392. Phase 5 lane B of the v4.3.0 build order (#514). Five commits; every API change is additive (new endpoints, new defaulted fields), so no `BOT_CONTRACT_VERSION` bump. No migration. **#373 self-diagnosis.** `api_client` raises `QuestBoardApiError(status, detail)` (a subclass of `httpx.HTTPStatusError`, so the startup handshake and existing handlers are untouched); cogs show the backend's own sentence for a 4xx or a 503 (the named acceptance case, "LLM endpoint not configured", is a 503 that always carries actionable prose) and the generic message for a 500 or network failure. `/next`, `/quests`, `/history` distinguish "no campaign is bound to this server" from "nothing scheduled" via a new `GET /api/bot/guilds/{id}/campaigns`, consulted only on the empty path. `/ask` gets an additive `reason: "no_transcripts"` rather than a null answer that would crash an older bot. New `Help` cog: `/help` (a test asserts it covers every registered command), an `on_guild_join` welcome with the three setup steps and the guild ID pasted in, a presence set in the constructor so it survives reconnects. Also `command_prefix=commands.when_mentioned`, the one prefix discord.py exempts from the "message content intent is missing" warning seen on prod after v4.2.3, with a test. **#393 `/record start`.** Reconciled with #399, which is left intact: the audio was already rescued on restart, but everything needed to *say so* died with the process. The active recording is now written to Redis DB 0 (`qb:bot:recording:<guild_id>`, 7-day TTL, best-effort), and `announce_interrupted_recordings` runs after #399's recovery to report which of three things happened (recovered and being transcribed; kept but not yet submitted; nothing captured). `/record start`'s session argument autocompletes against a new `GET /api/bot/guilds/{id}/sessions/recordable`; the slash path sets `session_title` (the "Unknown Session" bug) with the regression test nothing had; a definite 404 refuses the start while a timeout still starts, since capture cannot be redone; `defer()` precedes the metadata fetch; `/record stop` fulfils its interaction; failure strings are written for a hosted customer with the exception in the log and a retry link. **#392 linking dead ends.** Two small self-contained HTML pages replace the raw JSON 400/409, each naming the next step (`/link` again vs sign in as the other account and `/unlink`). `/auth/link` records the outcome and `/api/bot/link-status/{token}` gains an additive `state` (`pending|linked|expired|conflict`), so the poller speaks as soon as the outcome is known and names `/unlink` on a conflict. Closed DMs fall back to a self-deleting channel mention in both the linking poller and the pre-vote prompt. The vote reminder tells unlinked players to run `/link`; `Profile.jsx` names the command. **Review note:** the backend's `SecurityHeadersMiddleware` now emits `default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'` for `text/html` responses only (the pages contain no user-controlled text); JSON responses keep the strict policy verbatim, with a test. Bot 398 passed (+80), backend full 2378 passed / 13 skipped, frontend 514 passed, ruff and version-sync clean. One adjacent data-loss finding filed as #520 (raw tracks deleted on a mid-conversion failure). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-09-06 03:04:07 +00:00
rbrooks force-pushed feat/phase5b-bot-self-diagnosis from 822bea8ff4
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 26s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 54s
CI / Bot/backend version sync (pull_request) Successful in 22s
CI / Bot tests and audit (pull_request) Successful in 1m38s
CI / Frontend tests, audit, and build (pull_request) Successful in 3m49s
CI / Docker image build (pull_request) Successful in 4m18s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
to 842c864ab8
Some checks failed
CI / Frontend tests, audit, and build (pull_request) Successful in 1m53s
CI / Docker image build (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Has been cancelled
CI / Bot/backend version sync (pull_request) Has been cancelled
CI / Bot tests and audit (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Backend lint (ruff) (pull_request) Has been cancelled
2026-09-06 03:10:13 +00:00
Compare
rbrooks force-pushed feat/phase5b-bot-self-diagnosis from 842c864ab8
Some checks failed
CI / Frontend tests, audit, and build (pull_request) Successful in 1m53s
CI / Docker image build (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Has been cancelled
CI / Bot/backend version sync (pull_request) Has been cancelled
CI / Bot tests and audit (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Backend lint (ruff) (pull_request) Has been cancelled
to f3a0166759
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 26s
CI / Bot/backend version sync (pull_request) Successful in 23s
CI / Bot tests and audit (pull_request) Successful in 1m37s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m12s
CI / Frontend tests, audit, and build (pull_request) Successful in 3m3s
CI / Docker image build (pull_request) Successful in 4m27s
CI / Backend migration, tests, and audit (pull_request) Successful in 7m9s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 20m33s
2026-09-06 03:12:36 +00:00
Compare
claude-bot deleted branch feat/phase5b-bot-self-diagnosis 2026-09-06 03:39:44 +00:00
Sign in to join this conversation.
No description provided.