Five remaining items from the May 2026 audit:
#15: Tightened integration test catalog count from >=14 to >=50 — current
catalog post-V9 is 60+ rows. Loose threshold would silently miss a future
migration that drops a chunk.
#17: Added migration-idempotency test
(server/src/__tests__/db/idempotency.test.ts):
- initDb -> close -> initDb on the same file produces identical row counts.
- Every catalog row has exactly one fallback_config entry.
- UNIQUE(platform, model_id) holds — no duplicate rows.
- Every enabled platform has a registered provider.
#19: crypto.ts now validates ENCRYPTION_KEY length and hex-format at
init time. A typo'd key like 'abc' previously fell through and only failed
on the first encrypt() with a cryptic node:crypto error. Now throws fast
with an actionable message including the command to generate a valid key.
Same validation applies to the DB-persisted key path. New test file
crypto-init.test.ts covers all paths (valid, too-short, too-long, non-hex
env key; cor…
fix(catalog): 400 on explicit unknown/disabled model + sync docs (#24)
Two cleanups from the May 2026 audit:
1. /v1/chat/completions — when the request includes an explicit `model`
field that doesn't resolve to an enabled catalog row, return 400
instead of silently auto-routing to a totally different model. The
silent fallthrough surprised playground users who 'forced' a disabled
model and got something unrelated back without a signal.
Behavior:
- model omitted → auto-route via fallback (unchanged).
- model matches enabled row → pin to it (unchanged).
- model matches disabled row → 400 with reason 'is disabled'.
- model not in catalog at all → 400 with reason 'is not in the catalog'.
Both 400s carry error.code = 'model_not_found'.
2. docs/index.html stale references — removed the Hugging Face provider
card (HF was dropped from the catalog in V4), updated provider count
from 12 → 11 across meta description, OG description, Twitter card,
hero badge, and tokens-stat sub. Removed the orphan logos/huggingface.svg.
Ve…