feat(bot,webapp): drop the instance Discord fallbacks (D67) and add a real bot identity check (lane 9 B1) #849

Merged
claude-bot merged 2 commits from build/lane9-b1-d67-identity into main 2026-09-26 17:57:24 +00:00
Collaborator

Part of lane 9 (#735), server slice B1. Nothing here waits on A1–A11: the Admin token field and the setup wizard's Discord step are left alone until A2 is answered.

D67: instance-wide Discord never posts

  • Fallbacks removed. session_service._effective_webhook and vote_service.notify_vote_cast no longer fall back to the stored instance webhook or the DEFAULT_DISCORD_WEBHOOK_URL env var. A campaign without its own webhook gets nothing.
  • Call sites fixed. reminder_tasks had two further callers of _effective_webhook; both are updated.
  • Removed:
    • config.default_discord_webhook_url
    • settings_service.get_discord_webhook_fallback
    • KEY_DISCORD_WEBHOOK
    • discord_webhook_url on the notification settings request and response. A PUT that still sends it is ignored, with a test.
    • POST /admin/settings/bot/test-notify
    • sendTestNotify
    • the legacy Admin field and button
  • Kept: the campaign's own test (/test-channel, the bot's "test" event).
  • Stored row. Migration c4e6a8b0d2f1 (data only) deletes the stored default_discord_webhook row. Its downgrade is a no-op.
  • Self-hosters: anyone relying on DEFAULT_DISCORD_WEBHOOK_URL loses those posts. The CHANGELOG says so.

The bot identity check (IS-394, lane 9 headline 3)

"Connected" used to call the bot's unauthenticated /health, so a bot with a rotated-out key still showed as connected.

  • Bot: new internal route GET /identity, authenticated like /notify (X-Bot-Key + compare_digest, 401 otherwise). It returns one of three shapes:
    • ready with the bot's name, id and guild count;
    • disabled with the reason, no token or token refused;
    • starting.
  • Backend: GET /api/admin/settings/bot/identity (admin, 5 s timeout) returns {reachable, key_ok, discord, user, guild_count, detail}:
    • unreachable, 401 (wrong key) and 404 (older bot) each get a plain detail;
    • "No bot URL configured" is its own case.
  • Frontend: getBotIdentity is added. Lane 9 page 4c draws it.
  • Fake bot: the e2e fake bot gains /identity.
  • Contract: /api/bot/* is untouched, so BOT_CONTRACT_VERSION stays 2. check_version_sync.py passes.

Tests

  • Backend targeted: 517 passed.
  • Bot full suite: 468 passed, 1 skipped.
  • Frontend: 2,471 passed; eslint has 0 errors.
  • Migration: up, down and up.
  • ruff: clean. bot/ was checked with ruff check, not formatted.

Merge note: #848 also adds a migration on e5f7a9b1c3d4. Whichever merges second gets re-chained, and its one-head pin moved.

🤖 Generated with Claude Code

Part of lane 9 (#735), server slice B1. Nothing here waits on A1–A11: the Admin token field and the setup wizard's Discord step are left alone until A2 is answered. ## D67: instance-wide Discord never posts - **Fallbacks removed.** `session_service._effective_webhook` and `vote_service.notify_vote_cast` no longer fall back to the stored instance webhook or the `DEFAULT_DISCORD_WEBHOOK_URL` env var. A campaign without its own webhook gets nothing. - **Call sites fixed.** `reminder_tasks` had two further callers of `_effective_webhook`; both are updated. - **Removed:** - `config.default_discord_webhook_url` - `settings_service.get_discord_webhook_fallback` - `KEY_DISCORD_WEBHOOK` - `discord_webhook_url` on the notification settings request and response. A PUT that still sends it is ignored, with a test. - `POST /admin/settings/bot/test-notify` - `sendTestNotify` - the legacy Admin field and button - **Kept:** the campaign's own test (`/test-channel`, the bot's `"test"` event). - **Stored row.** Migration `c4e6a8b0d2f1` (data only) deletes the stored `default_discord_webhook` row. Its downgrade is a no-op. - **Self-hosters:** anyone relying on `DEFAULT_DISCORD_WEBHOOK_URL` loses those posts. The CHANGELOG says so. ## The bot identity check (IS-394, lane 9 headline 3) "Connected" used to call the bot's unauthenticated `/health`, so a bot with a rotated-out key still showed as connected. - **Bot:** new internal route `GET /identity`, authenticated like `/notify` (`X-Bot-Key` + `compare_digest`, 401 otherwise). It returns one of three shapes: - `ready` with the bot's name, id and guild count; - `disabled` with the reason, `no token` or `token refused`; - `starting`. - **Backend:** `GET /api/admin/settings/bot/identity` (admin, 5 s timeout) returns `{reachable, key_ok, discord, user, guild_count, detail}`: - unreachable, 401 (wrong key) and 404 (older bot) each get a plain `detail`; - "No bot URL configured" is its own case. - **Frontend:** `getBotIdentity` is added. Lane 9 page 4c draws it. - **Fake bot:** the e2e fake bot gains `/identity`. - **Contract:** `/api/bot/*` is untouched, so `BOT_CONTRACT_VERSION` stays 2. `check_version_sync.py` passes. ## Tests - Backend targeted: 517 passed. - Bot full suite: 468 passed, 1 skipped. - Frontend: 2,471 passed; eslint has 0 errors. - Migration: up, down and up. - ruff: clean. `bot/` was checked with `ruff check`, not formatted. **Merge note:** #848 also adds a migration on `e5f7a9b1c3d4`. Whichever merges second gets re-chained, and its one-head pin moved. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(webapp,bot): remove D67 Discord fallbacks, add bot identity check
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 59s
CI / Backend lint (ruff) (pull_request) Successful in 30s
CI / Bot tests and audit (pull_request) Successful in 2m16s
CI / Feedback site tests (pull_request) Successful in 53s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Does this change touch the recording path? (pull_request) Successful in 21s
CI / Docker image build (pull_request) Successful in 36s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 14m17s
CI / Frontend tests, audit, and build (pull_request) Successful in 20m41s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 32m36s
CI / Backend migration, tests, and audit (pull_request) Successful in 37m39s
e9aebde5cd
D67: instance-wide Discord never posts. Removes the stored admin fallback
webhook and the DEFAULT_DISCORD_WEBHOOK_URL env var from session_service and
vote_service, drops discord_webhook_url from the admin notification settings
API, and removes the admin "Send test message" button and its endpoint
(POST /admin/settings/bot/test-notify). Migration c4e6a8b0d2f1 deletes the
stored fallback row on upgrade.

IS-394: the bot gains an internal, authenticated GET /identity (like
/notify) reporting whether it is actually signed in to Discord, distinct
from the unauthenticated /health that made "Connected" a false positive
after a key rotation. The backend's new GET /admin/settings/bot/identity
calls through to it. /api/bot/* and BOT_CONTRACT_VERSION are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge origin/main into lane 9 B1; re-chain c4e6a8b0d2f1 onto a2c4e6f8b0d1 (#795)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m25s
CI / Bot tests and audit (pull_request) Successful in 2m41s
CI / Backend lint (ruff) (pull_request) Successful in 31s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Bot/backend version sync (pull_request) Successful in 26s
CI / Does this change touch the recording path? (pull_request) Successful in 26s
CI / Docker image build (pull_request) Successful in 27s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m20s
CI / Frontend tests, audit, and build (pull_request) Successful in 28m0s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 11m43s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 13m55s
15fc9f62ad
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.