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

GET /api/v1/admin/subscriptions carries an ETag and answers a repeat with 304 until a subscription is added or removed #9234

Merged opened this

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

Merged

`GET /api/v1/admin/subscriptions` now carries a weak ETag and answers a repeat with a 304. I did not call `keep_plan` on this issue; the change was small enough to do in one pass.

shipped production is serving it deploy 652cd9c

opened this

GET /api/v1/admin/subscriptions now carries a weak ETag and answers a repeat with a 304. I did not call keep_plan on this issue; the change was small enough to do in one pass.

Verification: tsc --noEmit is clean. scripts/gate.ts printed nothing, so I take it as passing, but I did not check its exit code. The new test file and the existing test/api-admin-subscriptions.test.ts pass together (7 tests). The three tests in the new file that exercise the ETag and 304 fail with the route change stashed. I did not run the full suite; the forge's check does that on the push.

What changed

  • src/http/api/subscriptions.ts: the 200 carries etag (from weakEtag over the exact JSON sent), cache-control: private, no-cache and vary: authorization, cookie. A request whose If-None-Match holds the tag (holds) gets a bodiless 304 with the same headers and the securityFloor headers.
  • The body is byte-for-byte what json({ items }) produced. src/http/api.ts is untouched.
  • The 401 and the refusal for a token short of access still run before the tag is computed. They carry no ETag, even with If-None-Match: *, and a tag taken from an admin buys a refused viewer nothing.
  • The tag hashes the body, so any change to the rows moves it. That covers adding or removing a subscription, changing where one is granted, and changing its budget. Two viewers shown different bodies cannot share a tag.

Tests: test/subscriptions-etag.test.ts covers each of the above.

Decisions

  • I followed the pattern from #9229 in admin.ts, but kept the tagging inline in this route instead of adding a shared helper, because the issue limits the files to the route and its test.
  • The 403 case is tested with a non-admin token, which is how adminGate refuses a viewer on this route. I found no separate "token short of ro" case to test.

Closes #9213

commented

GET /api/v1/admin/subscriptions now carries a weak ETag and answers a repeat with a 304. I did not call keep_plan on this issue; the change was small enough to do in one pass.

Verification: tsc --noEmit is clean. scripts/gate.ts printed nothing, so I take it as passing, but I did not check its exit code. The new test file and the existing test/api-admin-subscriptions.test.ts pass together (7 tests). The three tests in the new file that exercise the ETag and 304 fail with the route change stashed. I did not run the full suite; the forge's check does that on the push.

What changed

  • src/http/api/subscriptions.ts: the 200 carries etag (from weakEtag over the exact JSON sent), cache-control: private, no-cache and vary: authorization, cookie. A request whose If-None-Match holds the tag (holds) gets a bodiless 304 with the same headers and the securityFloor headers.
  • The body is byte-for-byte what json({ items }) produced. src/http/api.ts is untouched.
  • The 401 and the refusal for a token short of access still run before the tag is computed. They carry no ETag, even with If-None-Match: *, and a tag taken from an admin buys a refused viewer nothing.
  • The tag hashes the body, so any change to the rows moves it. That covers adding or removing a subscription, changing where one is granted, and changing its budget. Two viewers shown different bodies cannot share a tag.

Tests: test/subscriptions-etag.test.ts covers each of the above.

Decisions

  • I followed the pattern from #9229 in admin.ts, but kept the tagging inline in this route instead of adding a shared helper, because the issue limits the files to the route and its test.
  • The 403 case is tested with a non-admin token, which is how adminGate refuses a viewer on this route. I found no separate "token short of ro" case to test.
commented

GET /api/v1/admin/subscriptions now carries a weak ETag and answers a repeat that holds it with a bodiless 304. The check failed only on the missing Try: line, so I pushed an empty commit with that line and changed no code.

I did not re-run the tests or the gate for this push. The code is the same as the commit that passed tsc and the two subscription test files locally.

What changed

  • src/http/api/subscriptions.ts: the 200 carries etag (from weakEtag over the exact JSON sent), cache-control: private, no-cache and vary: authorization, cookie. A matching If-None-Match gets a 304 with the same headers and the securityFloor headers.
  • The body is byte-for-byte what json({ items }) produced. src/http/api.ts is untouched.
  • The 401 and the 403 for a non-admin run before the tag is computed. They carry no ETag, even with If-None-Match: *.
  • The tag hashes the body, so adding or removing a subscription, changing where it is granted, or changing its budget moves it. Two viewers shown different bodies never share one.
  • test/subscriptions-etag.test.ts covers each of these, and its ETag and 304 tests fail without the route change.

What I chose not to do: I kept the tagging inline in this route instead of adding a shared helper, because the issue limits the files to the route and its test. It follows the pattern from #9229.

How to try it: call GET /api/v1/admin/subscriptions on the preview with an admin token. Repeat it with the returned etag in If-None-Match and expect a 304 with no body.

Approved

Clean. The tag is taken over the exact bytes sent, only after the 401 and access checks, with private no-cache and vary. A 304 carries the same headers. Inline tagging matches the existing routes. Tests cover 200, 304 with floor headers, tag movement on add, retarget, budget and delete, and 401/403 with no ETag even with If-None-Match: *.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs