refactor(server): drop seed-router-config / seed-streaming-tts scripts
The two seed scripts are fully superseded by the new admin endpoint `POST /api/admin/config/router` — same encryption, same configKV writes, same `configkv:invalidate` publish, plus auth + audit + body limits. Keeping both code paths created a drift risk on the AAD label and the merge semantics. Doc + test fallout: - `e2e-llm-router.ts` now points readers to the admin endpoint for the prerequisite seed step. - `docs/ai-context/verifications/llm-router.md` and `streaming-tts.md` get curl-based seed instructions; the 2026-05-15 llm-router evidence stays intact with a note that the script it used has since been removed. - The U9 follow-up entry in `llm-router.md` flips from "not shipped" to "partially shipped" — ETag + HMAC publish are still deferred, so the `config_write` / `config_invalid_hmac` Grafana panels stay parked. - Self-edit on the admin route + `app.ts` docstrings to drop the earlier "scripts stay as break-glass" wording.
This commit is contained in:
@@ -28,14 +28,28 @@ stops asserting completion ahead of measurement.
|
||||
upstream, then invokes the router directly to call OpenRouter for a chat
|
||||
completion. Validates envelope decrypt → configKV load → key rotation →
|
||||
upstream fetch → response parse on the real wire path.
|
||||
- **Command**:
|
||||
- **Command** (admin endpoint replaced the seed script on 2026-05-18; the
|
||||
2026-05-15 evidence below was captured with the now-removed
|
||||
`scripts/seed-router-config.ts`):
|
||||
```bash
|
||||
# 1. seed via the admin endpoint — requires an account whose email is in
|
||||
# ADMIN_EMAILS and is verified.
|
||||
curl -sS -X POST http://localhost:3000/api/admin/config/router \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"mode": "merge",
|
||||
"slices": [{
|
||||
"kind": "openrouter",
|
||||
"modelName": "chat-default",
|
||||
"overrideModel": "openai/gpt-4o-mini",
|
||||
"plaintextKey": "<OPENROUTER_KEY>"
|
||||
}],
|
||||
"defaults": { "chatModel": "chat-default" }
|
||||
}' | jq
|
||||
|
||||
# 2. exercise the router via the in-process e2e harness.
|
||||
cd apps/server
|
||||
pnpm exec dotenvx run --env-file=.env.local -- \
|
||||
tsx scripts/seed-router-config.ts \
|
||||
--openrouter-key '<OPENROUTER_KEY>' \
|
||||
--openrouter-model openai/gpt-4o-mini \
|
||||
--default-chat-model chat-default
|
||||
pnpm exec dotenvx run --env-file=.env.local -- \
|
||||
tsx scripts/e2e-llm-router.ts
|
||||
```
|
||||
@@ -106,9 +120,14 @@ stops asserting completion ahead of measurement.
|
||||
|
||||
## Known limitations / follow-up
|
||||
|
||||
- **U9 admin HTTP endpoint**: bootstrap currently goes through the
|
||||
`scripts/seed-router-config.ts` CLI. The plan's full HTTP admin endpoint
|
||||
with ETag + audit log + HMAC publish is deferred; tracked in plan U9.
|
||||
- **U9 admin HTTP endpoint**: partially shipped 2026-05-18 as
|
||||
`POST /api/admin/config/router` (see `routes/admin/config/router/index.ts`).
|
||||
Covers the write path with audit-log fields on the structured logger,
|
||||
envelope encryption in-process, and cross-instance invalidation publish.
|
||||
The plan's ETag-based optimistic concurrency control and HMAC-signed
|
||||
invalidate payload are still deferred; the `config_write` and
|
||||
`config_invalid_hmac` counters described below remain producerless until
|
||||
those land.
|
||||
- ~~**GATEWAY_BASE_URL**: still required in env schema~~. Resolved
|
||||
2026-05-15: env entry removed, all routes go through `llmRouter.route` /
|
||||
`routeTts` / `listTtsVoices`. The `LLM_ROUTER_MASTER_KEY` env var is
|
||||
@@ -123,8 +142,9 @@ stops asserting completion ahead of measurement.
|
||||
PR — `app.ts` now emits `connected` / `error` / `reconnecting` from the
|
||||
`configkv:invalidate` subscriber). The remaining two counters
|
||||
(`config_write`, `config_invalid_hmac`) intentionally have no panels
|
||||
because their producer is the Plan U9 admin HTTP endpoint that has
|
||||
not shipped; they will rejoin Rows 6.5 / 6.7 alongside the U9 PR.
|
||||
because their producer is the ETag + HMAC slice of the U9 admin
|
||||
endpoint that has not shipped (see the U9 entry above); they will
|
||||
rejoin Rows 6.5 / 6.7 when that slice lands.
|
||||
Alert rules (key.exhausted > 0, fallback ratio > 30%, single-key
|
||||
error ratio > 80%) are still configured through Grafana UI, not
|
||||
build.ts — IaC-ifying them is a separate follow-up.
|
||||
|
||||
@@ -82,23 +82,30 @@ once it lands.
|
||||
### Prerequisite: seed `STREAMING_TTS_UPSTREAM`
|
||||
|
||||
```bash
|
||||
cd apps/server
|
||||
STREAMING_TTS_UPSTREAM_URL="ws://airi-unspeech.railway.internal:5933/v1/audio/speech/stream" \
|
||||
VOLCENGINE_TTS_API_KEY="$VOLCENGINE_TTS_API_KEY" \
|
||||
pnpm exec dotenvx run --env-file=.env.local -- \
|
||||
tsx scripts/seed-streaming-tts.ts
|
||||
curl -sS -X POST http://localhost:3000/api/admin/config/router \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"slices": [{
|
||||
"kind": "streaming-tts",
|
||||
"upstreamURL": "ws://airi-unspeech.railway.internal:5933/v1/audio/speech/stream",
|
||||
"plaintextKey": "<VOLCENGINE_TTS_API_KEY>"
|
||||
}]
|
||||
}' | jq
|
||||
```
|
||||
|
||||
The script reads `LLM_ROUTER_MASTER_KEY` and `REDIS_URL` from
|
||||
`.env.local`, envelope-encrypts the Volcengine key under AAD
|
||||
`{ modelName: 'streaming-tts', keyEntryId: 'volcengine-prod-1' }`, and
|
||||
writes the `STREAMING_TTS_UPSTREAM` configKV entry. Use `--dry-run` to
|
||||
preview the ciphertext length without committing.
|
||||
The server envelope-encrypts the plaintext key under AAD
|
||||
`{ modelName: 'streaming-tts', keyEntryId: 'volcengine-prod-1' }` and
|
||||
writes `STREAMING_TTS_UPSTREAM`. Add `"dryRun": true` to preview the
|
||||
ciphertext length without committing.
|
||||
|
||||
To point at a different unspeech instance later, just re-run the script
|
||||
with a different `STREAMING_TTS_UPSTREAM_URL`. To rotate the upstream
|
||||
key, re-run with `--key-id volcengine-prod-N` (the audio-speech-ws
|
||||
route always reads `keys[0]`, so a write replaces the active key).
|
||||
To point at a different unspeech instance later, repeat the call with a
|
||||
different `upstreamURL`. To rotate the upstream key, pass
|
||||
`"keyEntryId": "volcengine-prod-N"` (the audio-speech-ws route always
|
||||
reads `keys[0]`, so a write replaces the active key).
|
||||
|
||||
`$ADMIN_TOKEN` is a Bearer token for an account whose email is in
|
||||
`ADMIN_EMAILS` and is verified.
|
||||
|
||||
### Scenario L1: streaming session happy path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user