Beta servers: testers can use beta features in servers whose owner opted in

- beta.Configure takes the opted-in servers; testers are looked up in the beta server
- beta commands registered per opted-in server and removed when it opts out
- beta.ServerEnabled for behaviour that no person triggers
- docs updated

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
FernandoJVideiraandClaude Sonnet 5.5 committed 2026-10-23 01:25:50 +01:00
1 parent e2fe30b524
commit fcf4f3ccbd
12 files changed
+491 -122

No files matched your search

+46 -14
View File
@@ -1,24 +1,43 @@
# Beta features
A beta feature is one that only a few chosen people can use while it is still being tried out. The same rule applies on the bot and on the dashboard: you can use beta features if you are **a member of the beta server and hold the Beta Tester role**.
A beta feature is one that is still being tried out, so only chosen people can use it, and only where the server agreed. Two things must both be true:
1. **The person is a beta tester**: a member of the beta server who holds the Beta Tester role.
2. **Beta features are on in the server they use it in**: the beta server itself, or any other server whose owner opted in.
The same rule applies on the bot and on the dashboard.
## Who the testers are
- The **beta server** and the **Beta Tester role** are chosen by the bot's owner on the dashboard, under **Admin, Beta access**. Only the owner can see or change this page.
- Giving someone the role in Discord makes them a tester. Taking it away removes their access: within about 30 seconds on the bot, and within about 30 seconds on the dashboard API.
- The **beta server** and the **Beta Tester role** are chosen by the bot's owner on the dashboard, under **Admin, Beta access**. Only the owner can see or change that page.
- Giving someone the role in Discord makes them a tester. Taking it away removes their access: within about 30 seconds on the bot (a few seconds if the bot has them cached), and within about 30 seconds on the dashboard API.
- Until a role is chosen, **nobody** has beta access.
- The settings are stored in the `beta_settings` table (one row). The API writes it, and the bot polls it every 30 seconds. There is nothing to put in `.env`.
- Being a tester is about the person, so it is always checked in the **beta server**, also when they use a feature somewhere else. Having a role with the same ID in another server means nothing.
Access **fails closed**. If the settings cannot be read, if the role or the server is not set, if a person is not in the server, if a command is used in a DM, or if Discord cannot be reached, access is denied.
## Which servers have beta features
- The beta server always has them.
- Any other server gets them only when **its owner** switches on **Beta features** on that server's page of the dashboard. Beta features can act on a whole server (anti-raid behaviour, for example), so the people who answer for the server decide. A server's admins who are not its owner cannot switch it on.
- The bot's owner can take a server out again from **Admin, Beta access**, but cannot put one in.
- The list of opted-in servers is the `beta_guilds` table. The bot polls it every 30 seconds, together with the beta settings.
Beta never replaces a feature's own permission checks. A beta command that needs Manage Server still needs it, a music command still needs the DJ role, and anything for the owner or trusted users still needs that.
## What a tester sees
- **Bot:** beta commands appear only in the beta server and in opted-in servers, and run only for testers. When a server opts in, the commands show up within about 30 seconds. When it opts out, they are removed.
- **Dashboard:** beta pages and links show a **Beta** badge, and only to testers, and only for servers where beta features are on.
## On the bot
Code: `internal/beta` (the gate), `internal/commands/commands.go` (`RegisterBeta`), `internal/bot/bot.go` (`WatchBeta`, `registerBetaCommands`), `internal/commands/betacmd` (`/beta`).
Code: `internal/beta` (the rules), `internal/commands/commands.go` (`RegisterBeta`), `internal/bot/bot.go` (`WatchBeta`, `syncBetaCommands`), `internal/commands/betacmd` (`/beta`).
- Beta commands are registered **only in the beta server**, so nobody else sees them in the command menu.
- Each beta handler is wrapped in `beta.Gate`, which checks that the interaction came from the beta server and that the member has the role. If not, the user gets "This feature is in beta and isn't available to you yet." (Autocomplete requests get an empty list.) Hiding the command is never the only protection.
- If the owner changes the beta server on the dashboard, the bot moves the beta commands to the new server within 30 seconds and removes them from the old one. Commands in the beta server that are neither regular nor beta commands are removed too. If the beta server is also `GUILD_ID`, the regular commands are kept.
- `/beta` shows the owner how access is set up. Only the owner of the Discord application can run it, and it is hidden from everyone but administrators.
- Beta commands are registered in each server where beta features are on, and only there. `/beta` is only in the beta server.
- Each beta handler is wrapped in `beta.Gate`. It checks that beta is on in the server of the interaction and that the person is a tester, and otherwise answers "This feature is in beta and isn't available to you or this server yet." (Autocomplete requests get an empty list.) Hiding the command is never the only protection.
- For a person using a command in another server, the bot looks up their roles in the beta server: first in its gateway cache, otherwise it asks Discord (remembered for 30 seconds). If anything fails, the answer is no.
- If the owner changes the beta server on the dashboard, the beta commands move within 30 seconds. In the beta server, commands this bot left there that no longer exist are removed too. If the beta server is also `GUILD_ID`, the regular commands are kept.
- `/beta` shows the bot's owner how access is set up. Only the owner of the Discord application can run it, and it is hidden from everyone but administrators.
- A server that opted out while the bot was **offline** keeps its beta commands in the menu until someone removes them. Using one still fails, because the gate checks every time.
### Add a beta command
@@ -30,22 +49,35 @@ func init() {
}
```
`RegisterBeta` adds the definition to `BetaCommands` and the handler, wrapped in `beta.Gate`, to the dispatcher. Buttons and forms that belong to a beta command need the same care: check `beta.Guard(s, i)` at the top of their handlers, because a custom ID can be reached from an old message.
`RegisterBeta` adds the definition to `BetaCommands` and the handler, wrapped in `beta.Gate`, to the dispatcher. Buttons and forms that belong to a beta command need the same care: call `beta.Guard(s, i)` at the top of their handlers, because a custom ID can be reached from an old message.
### Add beta behaviour that no one runs
Event handlers and background jobs have no person to check. Gate them on the server only:
```go
if !beta.ServerEnabled(guildID) {
return // beta features are off here
}
```
Do not use this for anything that should only be for testers as people. It means "this server agreed to try beta features", and every member of the server is affected.
### Release it
Move the definition into `AllCommands` and the handler into the `handlers` map, and delete the `RegisterBeta` call. On the next start the command appears everywhere, and it is removed from the beta server's own list because it is now a regular command.
Move the definition into `AllCommands` and the handler into the `handlers` map, and delete the `RegisterBeta` call. On the next start the command appears everywhere, and the beta servers drop it from their own lists because it is now a regular command.
## On the API and dashboard
These live in the other two repositories.
- **API**: put the feature's routes under `/api/v1/beta`. That route group sits behind a `betaOnly` middleware, which asks Discord whether the signed-in user holds the role in the beta server (with a 30-second memory) and answers `403` otherwise. `GET /users/me` also returns `is_beta`, which the dashboard uses to decide what to show.
- **Dashboard**: give a route `meta: { requiresBeta: true }` to keep its page away from non-testers, and use `is_beta` to hide its links. This is only about display. The API checks access on every request, so a page that was shown by mistake still cannot read or change anything.
- **API**: put the feature's routes under `/api/v1/beta` (not tied to a server), or add the `betaGuildOnly()` middleware to a route under `/api/v1/guilds/:guild_id/...` (tied to a server). The first asks Discord whether the signed-in user holds the role in the beta server (30-second memory); the second also checks that the server is the beta server or opted in. Both answer `403` otherwise. `GET /users/me` returns `is_beta`, and `GET /guilds/:guild_id/beta` tells the dashboard whether beta features are on for that server and whether the caller owns it.
- **Dashboard**: give a route `meta: { requiresBeta: true }` to keep its page away from non-testers, and mark navigation entries with `beta: true` so testers see them with a **Beta** badge (and nobody else sees them). This is only about display. The API checks access on every request, so a page that was shown by mistake still cannot read or change anything.
## Why it works this way
- The role in Discord is the single list of testers, so there is no second list to keep in step with it.
- Servers choose to take part, because a beta feature can affect everyone in them.
- Access is decided by the server (bot and API), never by the browser.
- Beta commands do not exist outside the beta server, and the gate checks again when they run.
- The settings are in the database, not in the environment, so the owner can change them without a deploy.