LLM policy: admin-driven provider switches, curated local models, IPv6 rate-limit fix
Three commits moving LLM configuration from hardcoded defaults into `org_settings`, fixing an IPv6 rate-limiter bug that logged an error on every backend boot, and bumping rate-limit defaults from debug-obstacle levels to something suited for normal firm use.
Admin-driven model picker (76030d6f, migration 0019, +1013/-1036 across 23 files). Adds three columns to org_settings: providers_enabled (JSONB master switch per provider, defaults all-on), local_llm_base_url (runtime override; NULL falls back to LOCAL_LLM_BASE_URL env), and local_llm_models (curated [{id,label}] array). A new /admin/llm page lets an admin choose which providers are enabled, optionally override the local endpoint URL at runtime, and control which Ollama models appear in the user picker. Per-user API keys are removed - only env-supplied keys from install.sh / .env.compose are honoured. The hardcoded assertProviderAllowed check in the dispatcher is replaced by assertModelAllowed reading from LlmPolicy loaded off org_settings. On backend boot, if policy.local_llm_base_url is set, the backend writes it into process.env.LOCAL_LLM_BASE_URL so the local adapter picks it up without a restart.
Follow-ups (61689c39). The rate limiter keyGenerator was calling req.ip ?? "anon" when falling back to IP for pre-auth requests. express-rate-limit v8 with IPv6 addresses was logging ERR_ERL_KEY_GEN_IPV6 on every boot and on every pre-auth request from an IPv6 client. The fix: call ipKeyGenerator(req.ip ?? "") from the library, which canonicalises IPv6 addresses to a /64 prefix. This also closes a bypass path: rotating the low 64 bits of an IPv6 address was a real way to exceed the general IP bucket. Migration 0020 drops user_api_keys - the table held per-user AES-256-GCM ciphertext that never escaped the encrypted-at-rest layer, so the changelog records the drop as acceptable.
Rate-limit defaults (af2a0d89). General: 300 → 1500 per 15 min per IP. Chat: 30 → 200 per 15 min per user. Chat-create: 60 → 300 per 15 min per user. Upload: 50 → 300 per hour per user. All four are now documented as commented-out env vars in .env.compose.example so operators can tune without a code change.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?