Discord: main DRB bot optional end to end + owner-flow integration tests (#225 S8) #271
No Reviewers
Labels
Clear labels
area/ai-pipeline
area/auth
area/billing
area/correlation
area/discord
area/frontend
area/governance
area/growth
area/infra-deploy
area/legal
area/map
area/mqtt
area/node-ota
area/osint
area/recording-audio
area/sdr-radio
area/transcription
board
board:agenda
minutes:draft
minutes:final
role:ceo
role:cfo
role:cio
role:ciso
role:cmo
role:coo
role:cto
Scene/entity extraction, summarizer, AI providers, embeddings
AuthN/Z, roles, tenancy/org scoping, API keys, credentials
Plans, entitlements, AI spend accounting and caps
Incident correlation, linking, lifecycle, severity, corr_debug
Discord bot, radio bots, voice streams, bot tokens, webhooks
Web UI: Next.js app and the edge-node local dashboard
Board process, minutes, unattended runner, agent tooling
Go-to-market, outreach, cold email, discovery conversations
CI/CD, VM, registry, Firestore rules/indexes deploy, Terraform/Caddy
ToS, retention, licences/DAAs, CAN-SPAM, E&O, name suppression
Live map: pins, paths, tiles, legend, geocoding
Node-C2 MQTT, checkins, commands, node sweeper, dynsec
Node install, enrollment, OTA updater, image skew
External feeds: cameras, 511, ADS-B, AIS, outages, and their licences
Call capture, boundaries, PulseAudio, ring buffer, trimming, attribution
OP25 decode, SDR hardware/tuning, conventional, second SDR, mesh radio
STT, transcript correction, place verification, name accuracy
Board process: minutes and board-filed items
Needs board scoping at the next sitting; CEO removes the label once scoped
One exec pre-read, closed by CEO
CEO ruling; open until enacted
Filed by the CEO
Filed by the CFO
Filed by the CIO
Filed by the CISO
Filed by the CMO
Filed by the COO
Filed by the CTO
kind
behavior-bug
Wrong product behavior; the instrument reads it correctly
kind
measurement-bug
Corrupts the recording/instrument (debug data, evals, health checks), not behavior
priority
p0
Drop everything: prod broken, live exposure, or owner-ruled P0
priority
p1
Next up: blocks a gate, a customer, or core incident quality
priority
p2
Normal: real defect or planned work, schedule in turn
priority
p3
Low: minor, cosmetic, or someday
status
blocked
Waiting on an external party, spend, or another issue
status
in-progress
Branch or PR open against it
status
needs-decision
Waiting on an owner or board ruling before work
status
needs-info
Root cause or repro unknown; needs a reading first
type
bug
Something built is wrong: code, data, or a measurement
type
chore
Maintenance, refactor, tests, pinning, cleanup: no user-visible change
type
decision
A ruling to make or record: board minutes, owner calls, legal reviews
type
docs
Docs, runbooks, CLAUDE.md, skills, SOPs
type
feature
New capability or an extension of an existing one
type
security
Security, privacy, credential or tenant-isolation exposure
type
tracking
Tracker, checklist, initiative or punch-list spanning several changes
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: logan/server-26#271
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Slice S8 of #225 (plan: #225 comment 3108). Built on main
f49dc86(S1–S7 and #226/#227 merged). Do not merge until reviewed.What changed
NodeDiscordRadio.tsx): empty state points to Settings → Organization → Connect a Discord server; "The DRB bot has not reported this server's channels" now says a radio bot lists them (or the server bot, if there)./help:/drb linkfield names the no-bot path (Connect a Discord server); Radio bots field names the permissions and that radio bots work without this bot.ERROR_TEXT["limit"](reaches the web picker too) no longer says only "/leave".GET /discord/admin/overviewguilds gainmain_bot_present(true/false/null = never heard),radio_bots(names),linked_via,directory_source; Servers tab gets a Bots column.discord_web.pydocstring (plan finding 5): states the reversed "C2 never calls Discord on the browser's behalf" rule, its guards, and that the server bot is optional end to end.storage.download_audio()removed. No caller on main:routers/media.pymoved todownload_audio_rangein #233. Only stale branches with no open PR still import it (there are no open PRs)./healthcheck hits c2-core only, and nothing in compose depends ondiscord-bot. A dead server bot can't fail a deploy.Integration tests:
tests/test_discord_owner_flows.py(4 tests)Real route handlers and real internals (streams, leases, discord_rest, OAuth, guild link, perms). Firestore is
fake_firestore, MQTT is recorded, and Discord is a modelledDiscordWorldbehindhttpx.MockTransport. The model covers servers, roles, channel overrides, bot apps, REST and the OAuth2 token/revoke endpoints. Authorize-in-Discord makes the bot a member.One-bot org, no server bot:
inspect_tokenruns against the model.oauth/optionsandoauth/startfrom the node pick that bot (preferred).oauth/completelinks viaoauthand checks permissions in the same request (1 ready).join-optionslists the server withmain_bot_present: false, and channels come from the radio bot.voice-checkreports ready.start_node_streamleases the preferred bot and publishes a token id to n1. The node fetches the string with its key.Plus: the reconciler re-places the persistent stream on n2 when n1 dies, still with no server bot.
Full setup: the server bot pushes its directory and links with a
/drb linkcode. Four radio bots: BYO muted on #dispatch, two pool bots, and one pool bot not in the server.missing_permissionsnames all 3 member bots and the@everyoneoverride, and offers an invite for the 4th./joinCounty P25 on #dispatch: the BYO bot is denied Speak via@Muted, so DRB Radio 1 takes it./joinCity Fire on the same channel: its preferred bot, DRB Radio 2, plays beside DRB Radio 1.needs_fix, with pool botsbusy_hereand BYO missing Speak. The 409 text names the bot, the channel, the permission and the fix.preferred_busyrefusal that names its stream.main_bot_present: true,radio_botslists 3 bots.Plus: the server bot is kicked (
present:false). The picker still lists channels and starts work through the radio bots.I mutation-checked these: turning
DISCORD_PERM_CHECK_ENABLEDoff fails both main flows.Results
927 passed, 4 warnings in 22.61stsc --noEmitexit 0 (scratch copy, portable Node 20.20.2)py_compileonly. There are no tests for that service, so the/helpand cog changes are unverified at runtime.What still depends on the main bot (by design or out of scope)
/join,/leave,/status,/drb link,/help, and the trips commands are the server bot's. Without it, streams are started and stopped from the web: the node page picker, or the admin Settings → Discord page./drb linkcode path needs the server bot. Connect a Discord server (per-bot OAuth) doesn't.DISCORD_UNLINKED_GUILD_ORG_ID) still appear only while the server bot is there. A radio bot's membership never discovers a server (plan S5 guard, kept on purpose).discord_directory_refresh_seconds(300 s). A channel created a moment ago is found on Start by one inline re-read.POST /tokens) and setting its OAuth secret (PUT /tokens/{id}/oauth) are platform-admin only. A one-bot org therefore needs a DRB admin once, to register the bot. I did not change that: it is out of S8's scope and touches token-secret handling. Filed as a follow-up (see the #225 comment).Not done here
🤖 Generated with Claude Code
S8 (main bot optional + cleanup): PR #271 — #271. Adds integration tests for both owner flows (one-bot org via BYO + OAuth; full setup with server bot + 4 radio bots), admin Bots column, copy + docstring updates, removes dead storage.download_audio. Remaining main-bot dependencies are listed in the PR body. Follow-up filed: #273 (BYO bot registration + OAuth secret are platform-admin only). Leave #225 open until #271 is reviewed and merged.