Discord picker: per-bot permission status + owner's preferred bot (#225 S4) — merge after S3 #250
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#250
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.
Must merge after S3 (#249). This branch is stacked on
feat/225-s3-perm-check(commit2c2cf9d), so until #249 merges the diff below also shows S3. S4's own change is commit1ee1560.Slice S4 of #225 (picker UI, MVP frontend), built against S3's actual
routers/discord_checks.pycontract and its widenedPUT /tokens/{id}/prefer/{system}.What changed
Node page picker (
NodeDiscordRadio.tsx) and admin Add form (discord/StreamsPanel.tsx)state; the bot rows are only a fallback.refresh=1. When the server rate-limits it (refreshed:false), the footer says so. Invite links appear when no bot fits.needs_fix,bad_channel). Unknown fails open. A 404 from the check route (not deployed yet) shows no badges.missing_permissions,preferred_busy,preferred_other_node,bad_channel) are shown verbatim and multi-line (S3's text uses\n). Amissing_permissionsrefusal reloads the badges.components/discord/VoiceCheckBits.tsx. Types and helpers are inlib/discordPicker.ts.lib/c2api.tsgetsgetNodeVoiceCheck,getGuildVoiceCheckandgetOrgPreferredBots.Org owner: preferred bot per system (owner decision Q2)
PUT /tokens/{id}/prefer/{system}(_noneclears)./systems: it needs a platform role of admin or operator, and a self-serve owner is a viewer. That is why the card lives on Settings.GET /tokens/org/preferred(owner or admin; admin may pass?org_id=). It returns only the caller's own org's systems and its BYO bots: no pool bots, no token strings. A pool bot that an admin chose for one of the org's systems is flagged but not named.Fix found while wiring Q2:
start_streamreadssystemsthroughdoc_get_cached, which has a 5-minute TTL. A new preference therefore did not apply to starts for up to 5 minutes. The prefer route now drops the cached entry for every system it rewrites.Tests
~/venvs/drb-5c/bin/python -m pytest -q→806 passed, 4 warningstests/test_tokens_org_preferred.py(6): scoping, member refused, admin without an org,?org_idignored for non-admins, pool pick flagged and not named, and cache drop on set / move / clear.tsc --noEmit→ exit 0. The voice-check helpers were also run in Node on a sample S3 payload: verdicts,blocksStart, sentences andparseC2Errorbehaved as expected.drb-qa-review. This subagent has no agent tool, so the main session should run it before merging.Smoke test after deploy (plan rollout)
🤖 Generated with Claude Code
start_stream now asks Discord (cached REST snapshots, S2) whether each bot it could pick may View, Connect and Speak in the voice channel, before the lease. Covers all four entry points: bot POST /discord/streams, web picker, nodes discord_join, and the reconciler's re-place. - pick_token: denied bots skipped; confirmed-ok sorts before unknown; unknown is allowed (fail open); all denied -> missing_permissions naming each bot, missing permission, trace reason and fix. The reuse branch falls through when its token is denied. - Bounded: discord_perm_check_concurrency at once, within discord_perm_check_budget_seconds; unanswered = unknown. - Non-voice channel -> 400 bad_channel (not missing_permissions). - systems/{id}.preferred_token_id is now read (plan finding 1), as a hint. - Pool bots at discord_pool_bot_guild_cap are never offered as invites; pool_full when nothing else is left (owner Q3). - handle_actual drops cached access on missing_permissions, channel_full, not_in_guild, bad_channel. - New GET /discord/nodes/{node_id}/guilds/{guild_id}/voice-check (org member, names only) and GET /discord/guilds/{guild_id}/voice-check (service key or admin). - PUT /tokens/{id}/prefer/{system_id}: org owners for their own systems and own BYO bots; shared systems and pool bots stay admin-only (owner Q2). Keeps both pointers single-valued; audited. - Kill switch: DISCORD_PERM_CHECK_ENABLED=false. - tests/conftest.py: autouse fixture so no test ever reaches discord.com. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Node-page picker and the admin Add form now show, per voice channel, whether a radio bot can stream there, from S3's voice-check routes: - Badge per channel: Ready / Needs fix (names the missing permission) / Couldn't check / Bots busy / No radio bot / Not a voice channel / Limit N. - The chosen channel shows "<bot> can't Speak in #dispatch: <what removed it>" plus c2-core's fix sentence, and an expandable per-bot list. - Footer: "Permissions checked 40s ago · Re-check" (refresh=1, rate limited server-side; a limited re-check says so). Invite links when no bot fits. - Start is disabled only on a definite refusal (needs_fix, bad_channel); unknown fails open. A channel this system already plays in is judged by the bot that carries it, so Update/move is never blocked by the other bots. - Admin Add form disables bots that are definitely denied in the bot list. - 409 refusals (missing_permissions, preferred_busy, preferred_other_node, bad_channel) are shown verbatim, multi-line. Org owners (owner decision Q2) get "Radio bot per system" on Settings > Organization: their own systems, their own BYO bots, set/clear through S3's PUT /tokens/{id}/prefer/{system}. New GET /tokens/org/preferred lists only the caller org's systems and bots (no pool bots, no token strings; a pool bot an admin chose is flagged, not named). The prefer route now drops the 5-minute doc_get_cached entry for every system it rewrites, so a new preference applies to the next start. Stacked on feat/225-s3-perm-check: must merge after S3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>S4 review (not merged). Pushed
ec90a3f: voice-checkchecked_atnow carries the oldest cached snapshot'sfetched_atinstead of the response time, so the footer "Permissions checked N ago" can't call a 10-min-cached denied result fresh. Newtests/test_voice_check_age.py(2 tests).Verified: c2-core pytest
808 passed, 4 warnings; frontendtsc --noEmitexit 0. Merges cleanly over S3's8bdcfca. Still owed before merge:drb-qa-review(the reviewer subagent has no agent tool). Must merge after #249.