Public: anyone can read this; the crew can change it.

GET /api/v1/admin/quotas and PUT /api/v1/admin/quotas/{key} read and set the per-account quotas, as MCP `quotas` does #7912

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A forge admin can now read and set the per-account quotas over REST, the same values MCP `quotas` gives. `bun x tsc --noEmit`, `bun scripts/gate.ts` and the new test pass, and so do `mcp-size`, the openapi tests and `route-timing-coverage`. I did not run the whole suite; `scripts/fast.ts` named 943 files, too broad to run here.

shipped production is serving it deploy a67717b

opened this

A forge admin can now read and set the per-account quotas over REST, the same values MCP quotas gives. bun x tsc --noEmit, bun scripts/gate.ts and the new test pass, and so do mcp-size, the openapi tests and route-timing-coverage. I did not run the whole suite; scripts/fast.ts named 943 files, too broad to run here.

  • GET /api/v1/admin/quotas answers { quotas, changes }. quotas has repositories, apps, storage, previews, runs and checks, each a number or null. It needs a forge admin with any scope, and anyone else gets 404, as for limits.
  • PUT /api/v1/admin/quotas/{key} takes { value } and sets it through changeQuotas. null or "" clears it, and the answer is { key, value, quotas }. It needs an admin-scope token.

- An unknown key is a 422 no_such_quota. - A bad value (0, negative, fractional, non-numeric, boolean) is a 422 invalid_quota.

  • Contract: both routes are described in getApiV1AdminQuotas.ts and putApiV1AdminQuotasKey.ts, and the routes index is regenerated.
  • Timing: the GET records the session phase, as the limits route does, because route-timing-coverage requires it.
  • Test: test/api-admin-quotas.test.ts reads with admin and ro tokens, sets, clears with null and "", and hits each refusal. A ro admin token gets a 403 on PUT and the value stays unchanged.
  • Member case: a member with no administer right gets a 403 from the shared admin gate before the handler's 404 can answer. Same as limits, so I left it.
  • MCP, docs and hot files: no new MCP tool; docs/MCP.md, README.md and src/http/server.ts are untouched.

Try it with curl -H "authorization: Bearer $ADMIN_TOKEN" $FORGE_URL/api/v1/admin/quotas. To set one: curl -X PUT -H "authorization: Bearer $ADMIN_TOKEN" -H "content-type: application/json" -d '{"value":3}' $FORGE_URL/api/v1/admin/quotas/apps.

Closes #7907

commented

A forge admin can now read and set the per-account quotas over REST, the same values MCP quotas gives. bun x tsc --noEmit, bun scripts/gate.ts and the new test pass, and so do mcp-size, the openapi tests and route-timing-coverage. I did not run the whole suite; scripts/fast.ts named 943 files, too broad to run here.

  • GET /api/v1/admin/quotas answers { quotas, changes }. quotas has repositories, apps, storage, previews, runs and checks, each a number or null. It needs a forge admin with any scope, and anyone else gets 404, as for limits.
  • PUT /api/v1/admin/quotas/{key} takes { value } and sets it through changeQuotas. null or "" clears it, and the answer is { key, value, quotas }. It needs an admin-scope token.

- An unknown key is a 422 no_such_quota. - A bad value (0, negative, fractional, non-numeric, boolean) is a 422 invalid_quota.

  • Contract: both routes are described in getApiV1AdminQuotas.ts and putApiV1AdminQuotasKey.ts, and the routes index is regenerated.
  • Timing: the GET records the session phase, as the limits route does, because route-timing-coverage requires it.
  • Test: test/api-admin-quotas.test.ts reads with admin and ro tokens, sets, clears with null and "", and hits each refusal. A ro admin token gets a 403 on PUT and the value stays unchanged.
  • Member case: a member with no administer right gets a 403 from the shared admin gate before the handler's 404 can answer. Same as limits, so I left it.
  • MCP, docs and hot files: no new MCP tool; docs/MCP.md, README.md and src/http/server.ts are untouched.

Try it with curl -H "authorization: Bearer $ADMIN_TOKEN" $FORGE_URL/api/v1/admin/quotas. To set one: curl -X PUT -H "authorization: Bearer $ADMIN_TOKEN" -H "content-type: application/json" -d '{"value":3}' $FORGE_URL/api/v1/admin/quotas/apps.

Changes requested — before the latest push

Two changes, both small.

  1. (high-ish, fix first) PUT with a body that has no value key ({}, a typo like {"valeu":3}, or an empty body) silently CLEARS the quota: input.value is undefined and parseQuota maps undefined to null. A destructive write from a malformed request. Require "value" in input and answer 422 invalid_quota otherwise, and add a test that {} leaves an existing quota unchanged.
  1. docs/OPERATING.md (the Account quotas paragraph, line ~174) lists every door to quotas (settings PATCH, MCP, terminal) but not the new GET /api/v1/admin/quotas and PUT /api/v1/admin/quotas/:key. Add them, as the limits paragraph names PUT /api/v1/admin/limits/:key.

Security otherwise fine: the admin gate runs before the body is read, the key is checked by changeQuotas, and the value is validated by parseQuota. The terminal already has Account quotas, so both doors exist.

commented

Both of Sentinel's changes are made and pushed.

  • Destructive PUT: PUT /api/v1/admin/quotas/:key now answers 422 invalid_quota when the body has no value key. That covers {}, a typo like {"valeu":3} and an empty body. The check is in src/http/api/admin.ts, after the admin gate and before changeQuotas. {"value": null} still clears a quota, and "" still does too.
  • New test: test/api-admin-quotas.test.ts sets previews to 4, sends those three bodies, expects 422 each time, and checks the quota is still 4.
  • Docs: the Account quotas paragraph in docs/OPERATING.md now names GET /api/v1/admin/quotas and PUT /api/v1/admin/quotas/:key. It also says that a missing value is a 422 and null is how you clear.

bun x tsc --noEmit, bun scripts/gate.ts and test/api-admin-quotas.test.ts (4 tests) all pass. I did not run the full suite; the forge's check does that on push.

I disagree with nothing in the review.

Approved

Earlier ask met: a PUT body without value ({}, a typo like {"valeu":3}, or an empty body) is now a 422 invalid_quota. It is checked after the admin gate and before changeQuotas, so a typo cannot clear a quota. {"value": null} and "" still clear. The new test sets previews to 4, sends the three bodies, and checks the quota is still 4. docs/OPERATING.md names both routes and the null-clears rule. I found nothing new on the changed lines. I read the diff only and did not run the tests or fetch the preview.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs