feat(enterprise): operator-declared cost model, working budgets, virtual keys, guardrails, teams, OIDC #1
Loading…
Reference in a new issue
No description provided.
Delete branch "develop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Enterprise feature suite for LLMAPI: operator-declared cost model, working budget
enforcement, virtual keys, guardrails, teams/RBAC, and OIDC login.
Squashes three concerns into one PR:
feat(enterprise)- the feature suite + 6 bug fixesfix(security)- close the admin-API auth boundarychore(deploy)- declare OIDC env in composeThe cost model changed shape
Everything is free unless the operator explicitly marks it paid, via a
provider -> model picker (
Budgets->Free / paid models, deep-link/budgets#costs). The previous hardcoded free-platform allowlist classified143 of 257 real models as paid - ~99% false positives - which silently
blocked free models on a $0 budget. Default state is now 257 free / 0 paid,
verified against the live catalog.
Bugs fixed (7)
recordSpend()'s only caller was never mounted and nothing setres.locals.usage.current_spend_usdnever left0.000000, so every budget cap was decorative.max_budget_usd: 0->null|| nullmade a $0 key unlimited - the exact inverse of the spec.rpm_limit: 0->nullBearer ***instead of the parsed token - could never authenticate.*route/api/*/api/keysand change paid-model classification.Token estimation unified
Five output-token fallbacks still used
chars/4, which undercounts ~1.6x - andoutput is the expensive side of a paid model. All now use the context engine's
estimator (
chars/3.5x 1.4 safety margin).Security boundary
requireAuthaccepted any valid inference credential for the whole/api/*admin surface. Those keys go to apps and third parties, so this turned "can call
the model" into "owns the gateway". Restored to 401; the debug convenience is now
opt-in via
ADMIN_API_ACCEPTS_INFERENCE_KEYS=true(off by default).The tests asserting that boundary had been failing since
36154a4and were leftunreconciled - so the regression sat in the tree looking like expected noise.
Test suite: 24 failures -> 0
2614 passed / 0 failed / 5 skipped (232 files).
22 of the 24 were stale assertions left behind by deliberate changes (softened
cooldown ladder
30s/2m/5m/10m/1h/24h, retuned balanced weights0.40/0.15/0.45, thechars/3.5estimator). Two were real bugs, fixed in coderather than by relaxing the test.
Three brittle tests were rewritten to assert the property that matters:
ignores column order (SQLite cannot
DROP COLUMN) while still comparingthe column set
wall-clock milliseconds
Deploy safety
Migrations verified against a copy of the production DB (223 models):
Zero data loss, nothing wrongly marked paid. Every
DROP TABLEis insidedown()only;up()is purely additive (ALTER TABLE ADD COLUMN).Not included, deliberately
orderCandidates()exists and is tested but is not wired into the dispatchloop - the routing-strategies page is a read-only view over the existing
bandit scorer. Left as-is on request: the retuned scorer stays authoritative and
routing behaviour is unchanged by this PR.
Tag-based routing, Redis/S3 cache backends, and alert webhooks are not
implemented (not needed at current scale).
Verification
tsc --noEmitclean, server + clientdeploy/never committedpackage.jsonuntouchedrequireAuth accepted ANY valid inference credential -- the unified API key or a client-profile key -- as a fallback for the entire /api/* admin surface. Those keys are handed out to apps, bots, and third parties, so this turned "can call the model" into "owns the gateway": a profile key could read /api/keys and /api/settings/api-key, and now that budgets and paid-model classification live behind the same surface, it could also change what costs money. The boundary was documented and covered by tests in client-profiles.test.ts ("a profile key does NOT authenticate the admin/dashboard API"), but the fallback landed in36154a4as a curl/Postman convenience and those two tests were left failing rather than reconciled -- so the regression sat in the tree looking like expected noise. Restores 401 for inference credentials on /api/*. Dashboard sessions and OIDC sessions are unaffected. The debug convenience survives as an explicit opt-in, ADMIN_API_ACCEPTS_INFERENCE_KEYS=true, off by default and documented in .env.example. Fixed in code rather than by relaxing the tests: the tests were right. No VERSION bump. Not deployed.