Files
website/server/src/router/v1/internal/internal.routes.js
Claude cecd72915f feat(teams): the slash-command seam, and the first command through it
Phase 7 of TEAMS.md. `api.registerSlashCommands` stops throwing: a module
registers a command's DEFINITION and its HANDLER together, the bot pulls the
definitions over the internal listener and runs none of our code, and the
handler executes here — forced by the bot container having no `modules` volume,
and the right boundary anyway.

Registration validates what Discord would reject as a batch (names, description
lengths, the four option types, required-before-optional), because the bot
registers the whole set in one PUT and a single bad entry costs every command
including the bot's own. Commands are not namespaced under their owner — there
is no dot in Discord's name grammar — so collisions are first-come with the
holder named.

The dispatcher is the access boundary: `linked` has no Discord equivalent, so
the platform-side permission default can only ever be advertising. It resolves
the actor by `auth_providers.kind` rather than the id slug, treats a banned
account as unlinked, bounds a handler under the bot's own timeout, and keeps
`ok` outside the envelope so a handler cannot forge it.

Liveness is asked at both the pull and the dispatch. The registries have no
removal path, so a module an operator disables at runtime would otherwise keep
a live handler behind a command Discord still advertises.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 18:53:34 -05:00

36 lines
1001 B
JavaScript

const express = require('express')
const requireInternalKey = require('../../../middleware/requireInternalKey')
const ctrl = require('./internal.controller')
const router = express.Router()
// Shared-secret gated, not session-gated — the caller is the bot process, not
// a logged-in browser. This router is mounted on the standalone internalApp
// (its own unpublished port), never on the public /api app. See internalApp.js.
router.use(requireInternalKey)
router.get(
'/bot-config',
// #swagger.ignore = true
ctrl.getBotConfig,
)
// The slash-command seam (TEAMS.md §7.1). Both stay off the public API and out
// of the OpenAPI document for the same reason /bot-config does: the caller is
// the bot process on the private compose network, and `/internal/*` is not a
// published contract.
router.get(
'/commands',
// #swagger.ignore = true
ctrl.listCommands,
)
router.post(
'/commands/dispatch',
// #swagger.ignore = true
ctrl.dispatchCommand,
)
module.exports = router