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

GET /api/v1/orgs/:org/teams and …/teams/:team/members carry an ETag and answer a repeat with 304 until a team or its members change #9171

Merged opened this

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

Merged

`GET /api/v1/orgs/:org/teams` and `…/teams/:team/members` now carry a weak ETag. A repeat that sends it back gets a 304 instead of the whole list.

shipped production is serving it deploy d5155e2

opened this

GET /api/v1/orgs/:org/teams and …/teams/:team/members now carry a weak ETag. A repeat that sends it back gets a 304 instead of the whole list.

  • Headers: Both routes in src/http/api/orgs.ts build the tag from the exact JSON body with weakEtag. The 200 carries that etag, cache-control: private, no-cache and vary: authorization, cookie. A repeat holding the tag gets a 304 with the same headers and no body, and the securityFloor headers are still there. This uses a small tagged helper local to orgs.ts, the same shape as the one in issues.ts. Response bodies are unchanged and I didn't touch src/http/api.ts.
  • Access: The 304 is only reached after the org and team checks. A 404 (unknown org, unknown team, or a team the viewer cannot see) and a refused write carry no ETag. A stranger holding another viewer's tag gets a 404 for a team they can't see, or a 200 with a different tag for the teams list.
  • Contract: I added alsoStatus: [304] and a sentence on the ETag to the two route summaries in src/contract/openapi/, as the earlier ETag commits did. That is beyond the two files the issue listed.
  • Tests: test/org-teams-etag.test.ts covers the 200 and 304 headers, creating and removing a team, adding and removing a member, and the 404, 403 and stranger cases. Without the change it fails: there is no ETag to hold.
  • Checks: The new test, the existing org, MCP org, contract and OpenAPI coverage tests, bun x tsc --noEmit and bun scripts/gate.ts all pass. I did not run scripts/fast.ts or the full suite.

To try it: curl -i -H "authorization: Bearer $T" $URL/api/v1/orgs/acme/teams, then repeat with -H 'if-none-match: <etag>'.

I chose not to tag GET /api/v1/orgs/:org, which also lists teams, because the issue names only these two routes.

Closes #9165

commented

GET /api/v1/orgs/:org/teams and …/teams/:team/members now carry a weak ETag. A repeat that sends it back gets a 304 instead of the whole list.

  • Headers: Both routes in src/http/api/orgs.ts build the tag from the exact JSON body with weakEtag. The 200 carries that etag, cache-control: private, no-cache and vary: authorization, cookie. A repeat holding the tag gets a 304 with the same headers and no body, and the securityFloor headers are still there. This uses a small tagged helper local to orgs.ts, the same shape as the one in issues.ts. Response bodies are unchanged and I didn't touch src/http/api.ts.
  • Access: The 304 is only reached after the org and team checks. A 404 (unknown org, unknown team, or a team the viewer cannot see) and a refused write carry no ETag. A stranger holding another viewer's tag gets a 404 for a team they can't see, or a 200 with a different tag for the teams list.
  • Contract: I added alsoStatus: [304] and a sentence on the ETag to the two route summaries in src/contract/openapi/, as the earlier ETag commits did. That is beyond the two files the issue listed.
  • Tests: test/org-teams-etag.test.ts covers the 200 and 304 headers, creating and removing a team, adding and removing a member, and the 404, 403 and stranger cases. Without the change it fails: there is no ETag to hold.
  • Checks: The new test, the existing org, MCP org, contract and OpenAPI coverage tests, bun x tsc --noEmit and bun scripts/gate.ts all pass. I did not run scripts/fast.ts or the full suite.

To try it: curl -i -H "authorization: Bearer $T" $URL/api/v1/orgs/acme/teams, then repeat with -H 'if-none-match: <etag>'.

I chose not to tag GET /api/v1/orgs/:org, which also lists teams, because the issue names only these two routes.

commented

The check failed on a gate rule, not on code: the pull changes src/http/api/orgs.ts, but no commit message had a Try: line. I added one in a new empty commit (d340b62e) and pushed it, so history isn't rewritten. bun scripts/gate.ts is clean locally. I haven't seen the forge's check run on the new push yet.

The Try: line says to call GET /api/v1/orgs/<org>/teams on the preview, then repeat it with the returned etag in If-None-Match and expect a 304.

The code and tests from the first push are unchanged.

Approved

Clean. The tag is computed after the org and team access checks, so 404s carry no ETag and a stranger cannot get a 304 for a team they cannot see. A 304 carries the same headers, and the securityFloor headers are asserted in the test. The test fails without the change. Note: a third local tagged helper now exists beside issues.ts and releases.ts; worth consolidating later, not blocking.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs