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

GET /api/v1/admin/capacity, /admin/limits and /admin/quotas carry an ETag and answer a repeat with 304 until a limit or the capacity reading changes #9266

Merged opened this

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

Merged

`GET /api/v1/admin/capacity`, `/admin/limits` and `/admin/quotas` now return a weak ETag and answer a repeat request with 304 until a limit, a quota or a capacity reading changes.

shipped production is serving it deploy 0b2906d

opened this

GET /api/v1/admin/capacity, /admin/limits and /admin/quotas now return a weak ETag and answer a repeat request with 304 until a limit, a quota or a capacity reading changes.

  • What changed: in src/http/api/admin.ts, the three GET routes now build their response with taggedJson instead of json. taggedJson already sat beside them (it serves /admin/people and the other lists). It computes the weak tag from the exact bytes sent and sets cache-control: private, no-cache and vary: authorization, cookie. It answers 304 with no body when If-None-Match matches.
  • Order of checks: taggedJson runs only after accountAdmin and the "not an agent" check. A refused request (no session, a member, an agent admin) answers with the same status and body as before and no ETag, even when it sends If-None-Match: *.
  • Bodies: unchanged byte for byte, because both helpers use the same JSON.stringify.
  • Untouched: src/http/api.ts and every other admin route.

Tests: test/admin-limits-etag.test.ts checks four cases on each of the three routes:

  • The 200 response carries the tag and headers, and the 304 repeat keeps the headers, has no body and keeps the securityFloor headers.
  • A PUT to checks.concurrency, share.daily_max or previews changes the tag, and resetting it changes it again.
  • A second admin who sees the same bytes gets a 304 with the same tag, so the tag depends on the body, not the viewer.
  • Refused callers answer as before with no ETag.

Without the change, the first three cases fail on every route (9 of 12 tests). The refused-caller case passes either way: it describes how these routes already behave, and only fails if the 304 is ever answered before the access check.

Not covered: the issue asks for "a token short of each route's scope", but no such token exists for these reads. They need ro, the lowest scope, so the refused cases test the callers that actually get turned away. Separately, the existing capacity tests show a member being refused with 403, while accountAdmin would return 404. The refused-caller test compares against each route's current answer rather than hard-coding a status.

Checks run: bun scripts/gate.ts, bun x tsc --noEmit and the new suite pass, as do the quotas, capacity, limits, admin-gate, admin-people-etag and admin timing suites. To try it, curl -i one of the routes with an admin token, then repeat with If-None-Match: <etag> to get the 304.

🤖 Generated with Claude Code

Closes #9248

commented

GET /api/v1/admin/capacity, /admin/limits and /admin/quotas now return a weak ETag and answer a repeat request with 304 until a limit, a quota or a capacity reading changes.

  • What changed: in src/http/api/admin.ts, the three GET routes now build their response with taggedJson instead of json. taggedJson already sat beside them (it serves /admin/people and the other lists). It computes the weak tag from the exact bytes sent and sets cache-control: private, no-cache and vary: authorization, cookie. It answers 304 with no body when If-None-Match matches.
  • Order of checks: taggedJson runs only after accountAdmin and the "not an agent" check. A refused request (no session, a member, an agent admin) answers with the same status and body as before and no ETag, even when it sends If-None-Match: *.
  • Bodies: unchanged byte for byte, because both helpers use the same JSON.stringify.
  • Untouched: src/http/api.ts and every other admin route.

Tests: test/admin-limits-etag.test.ts checks four cases on each of the three routes:

  • The 200 response carries the tag and headers, and the 304 repeat keeps the headers, has no body and keeps the securityFloor headers.
  • A PUT to checks.concurrency, share.daily_max or previews changes the tag, and resetting it changes it again.
  • A second admin who sees the same bytes gets a 304 with the same tag, so the tag depends on the body, not the viewer.
  • Refused callers answer as before with no ETag.

Without the change, the first three cases fail on every route (9 of 12 tests). The refused-caller case passes either way: it describes how these routes already behave, and only fails if the 304 is ever answered before the access check.

Not covered: the issue asks for "a token short of each route's scope", but no such token exists for these reads. They need ro, the lowest scope, so the refused cases test the callers that actually get turned away. Separately, the existing capacity tests show a member being refused with 403, while accountAdmin would return 404. The refused-caller test compares against each route's current answer rather than hard-coding a status.

Checks run: bun scripts/gate.ts, bun x tsc --noEmit and the new suite pass, as do the quotas, capacity, limits, admin-gate, admin-people-etag and admin timing suites. To try it, curl -i one of the routes with an admin token, then repeat with If-None-Match: <etag> to get the 304.

🤖 Generated with Claude Code

commented

GET /api/v1/admin/capacity, /admin/limits and /admin/quotas now send a weak ETag and answer a repeat request with a 304 until a limit, a quota or a capacity reading changes.

  • The change: in src/http/api/admin.ts, all three GET routes build their response with the existing taggedJson helper instead of json. That helper already serves /admin/people and the other admin lists.
  • Headers: the tag is computed from the exact body sent, with cache-control: private, no-cache and vary: authorization, cookie.
  • Order of checks: the 304 is answered only after the access check. A request that is refused (no session, a member, an agent admin) gets the same status and body as before and no ETag, even with If-None-Match: *.
  • Bodies: unchanged byte for byte, because both helpers use the same JSON.stringify.
  • Untouched: src/http/api.ts and every other admin route.

Tests: test/admin-limits-etag.test.ts checks four things on each of the three routes:

  • The first answer is a 200 with the tag and headers, and a repeat is a 304 with no body that keeps the headers and the securityFloor headers.
  • A PUT to checks.concurrency, share.daily_max or previews changes the tag, and resetting it changes it again.
  • A second admin shown the same body gets a 304 with the same tag, so two viewers share a tag only when they see the same body.
  • Refused callers answer as before, with no ETag.

Without the change, the first three cases fail on every route (9 of 12 tests). The refused-caller case passes either way: it guards that the 304 never comes before the access check.

What I didn't cover: the issue asks for "a token short of each route's scope". These reads need ro, which is already the lowest scope, so no token falls short; the refused cases use the callers that are actually turned away. That test also compares against each route's current answer instead of fixing a status, because the existing capacity tests expect 403 for a member where accountAdmin alone would give 404.

Checks: the gate, bun x tsc --noEmit, the new suite and the neighbouring quotas, capacity, limits, admin-gate, admin-people-etag and admin timing suites passed on the change. The second commit (no code change) adds the Try: line the preview gate needs; I didn't re-run the tests after adding it.

Try: on the preview, as an admin, call GET /api/v1/admin/limits (or /admin/capacity, /admin/quotas) with a token. Repeat with If-None-Match set to the returned etag and you get a 304. After PUT /api/v1/admin/limits/share.daily_max, the same tag gets a 200 with a new etag.

🤖 Generated with Claude Code

Approved

Clean, minimal: the three GETs use the existing taggedJson after accountAdmin, so refused callers get no ETag and the 304 only follows the access check. Bodies unchanged. Tests cover 200/304 with the floor headers, tag moving on a PUT and on reset, shared tag across admins, and refused callers. No terminal or README change needed (cache headers on existing reads). Box 4 gap: no token can fall short of ro, so that part is vacuous; accepted.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs