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
Check · Review · Land · Live done — Landed and live in production.
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
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/quotasanswers{ quotas, changes }.quotashasrepositories,apps,storage,previews,runsandchecks, 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 throughchangeQuotas.nullor""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.tsandputApiV1AdminQuotasKey.ts, and the routes index is regenerated. - Timing: the GET records the session phase, as the limits route does, because
route-timing-coveragerequires it. - Test:
test/api-admin-quotas.test.tsreads with admin androtokens, sets, clears withnulland"", and hits each refusal. Aroadmin token gets a 403 on PUT and the value stays unchanged. - Member case: a member with no
administerright 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.mdandsrc/http/server.tsare 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.
Two changes, both small.
- (high-ish, fix first) PUT with a body that has no
valuekey ({}, a typo like{"valeu":3}, or an empty body) silently CLEARS the quota:input.valueis undefined andparseQuotamaps undefined to null. A destructive write from a malformed request. Require"value" in inputand answer 422invalid_quotaotherwise, and add a test that{}leaves an existing quota unchanged.
- 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/quotasandPUT /api/v1/admin/quotas/:key. Add them, as the limits paragraph namesPUT /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.
Both of Sentinel's changes are made and pushed.
- Destructive PUT:
PUT /api/v1/admin/quotas/:keynow answers 422invalid_quotawhen the body has novaluekey. That covers{}, a typo like{"valeu":3}and an empty body. The check is insrc/http/api/admin.ts, after the admin gate and beforechangeQuotas.{"value": null}still clears a quota, and""still does too. - New test:
test/api-admin-quotas.test.tssetspreviewsto 4, sends those three bodies, expects 422 each time, and checks the quota is still 4. - Docs: the Account quotas paragraph in
docs/OPERATING.mdnow namesGET /api/v1/admin/quotasandPUT /api/v1/admin/quotas/:key. It also says that a missingvalueis a 422 andnullis 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.
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.
A forge admin can now read and set the per-account quotas over REST, the same values MCP
quotasgives.bun x tsc --noEmit,bun scripts/gate.tsand the new test pass, and so domcp-size, the openapi tests androute-timing-coverage. I did not run the whole suite;scripts/fast.tsnamed 943 files, too broad to run here.GET /api/v1/admin/quotasanswers{ quotas, changes }.quotashasrepositories,apps,storage,previews,runsandchecks, 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 throughchangeQuotas.nullor""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 422invalid_quota.getApiV1AdminQuotas.tsandputApiV1AdminQuotasKey.ts, and the routes index is regenerated.route-timing-coveragerequires it.test/api-admin-quotas.test.tsreads with admin androtokens, sets, clears withnulland"", and hits each refusal. Aroadmin token gets a 403 on PUT and the value stays unchanged.administerright gets a 403 from the shared admin gate before the handler's 404 can answer. Same as limits, so I left it.docs/MCP.md,README.mdandsrc/http/server.tsare 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