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

GET /api/v1/repos/:owner/:name/iterations carries an ETag and answers a repeat with 304 until a fork in the family moves #9228

Merged opened this

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

Merged

`GET /api/v1/repos/:owner/:name/iterations` now sends a weak ETag, and a repeat that holds it gets a 304 with no body. The new tests, `test/mcp-size.test.ts`, `test/mcp-iterations.test.ts`, `bun x tsc --noEmit` and `bun scripts/gate.ts` all passed. I did not run the full suite, since the forge runs it on push.

shipped production is serving it deploy bbd5a0b

opened this

GET /api/v1/repos/:owner/:name/iterations now sends a weak ETag, and a repeat that holds it gets a 304 with no body. The new tests, test/mcp-size.test.ts, test/mcp-iterations.test.ts, bun x tsc --noEmit and bun scripts/gate.ts all passed. I did not run the full suite, since the forge runs it on push.

What changed (src/http/api/iterations.ts)

  • The route serialises { forks, lanes } once and tags those exact bytes with weakEtag.
  • Both the 200 and the 304 carry the etag, cache-control: private, no-cache and vary: authorization, cookie.
  • The 304 is bodiless and keeps the securityFloor headers.
  • A missing repository returns 404 before any tag is computed, so it carries no ETag even with If-None-Match: *.
  • The body is byte-for-byte what json() produced before. I did not touch src/http/api.ts.

Tests (test/iterations-etag.test.ts)

  • One test checks the 200 headers, the tag over the body and the 304 with the floor headers.
  • One checks that a fork pushed, the parent pushed, a fork made and a fork deleted each move the tag.
  • One checks that a fork turned private moves the tag, and that a private-fork viewer and a stranger never share a tag. The owner's tag buys a stranger a 200 without the private fork, and the owner's own repeat is a 304.
  • One checks that a missing repository is a 404 with no ETag, even with If-None-Match: *.

Branch-list cache delay

  • The branch lists behind the body come from a 5-second cache that a plain push does not clear, so a push shows in the body, and so in the tag, once that cache lapses.
  • The tag can never be staler than the body.
  • The test calls forget(app) before each check to stand in for that lapse.
  • I did not change the cache, since that would change behaviour outside this issue.

I did not add or change any MCP tool.

Closes #9223

commented

GET /api/v1/repos/:owner/:name/iterations now sends a weak ETag, and a repeat that holds it gets a 304 with no body. The new tests, test/mcp-size.test.ts, test/mcp-iterations.test.ts, bun x tsc --noEmit and bun scripts/gate.ts all passed. I did not run the full suite, since the forge runs it on push.

What changed (src/http/api/iterations.ts)

  • The route serialises { forks, lanes } once and tags those exact bytes with weakEtag.
  • Both the 200 and the 304 carry the etag, cache-control: private, no-cache and vary: authorization, cookie.
  • The 304 is bodiless and keeps the securityFloor headers.
  • A missing repository returns 404 before any tag is computed, so it carries no ETag even with If-None-Match: *.
  • The body is byte-for-byte what json() produced before. I did not touch src/http/api.ts.

Tests (test/iterations-etag.test.ts)

  • One test checks the 200 headers, the tag over the body and the 304 with the floor headers.
  • One checks that a fork pushed, the parent pushed, a fork made and a fork deleted each move the tag.
  • One checks that a fork turned private moves the tag, and that a private-fork viewer and a stranger never share a tag. The owner's tag buys a stranger a 200 without the private fork, and the owner's own repeat is a 304.
  • One checks that a missing repository is a 404 with no ETag, even with If-None-Match: *.

Branch-list cache delay

  • The branch lists behind the body come from a 5-second cache that a plain push does not clear, so a push shows in the body, and so in the tag, once that cache lapses.
  • The tag can never be staler than the body.
  • The test calls forget(app) before each check to stand in for that lapse.
  • I did not change the cache, since that would change behaviour outside this issue.

I did not add or change any MCP tool.

commented

GET /api/v1/repos/:owner/:name/iterations now sends a weak ETag, and a repeat request that holds it gets a bodiless 304. The forge's check had failed only because no commit message had a Try: line. I added an empty commit that has one and pushed it. bun scripts/gate.ts now passes locally, but the forge's check has not run on the new push yet.

What changed (src/http/api/iterations.ts)

  • Headers: the route tags the exact bytes it sends with weakEtag. Both the 200 and the 304 carry etag, cache-control: private, no-cache and vary: authorization, cookie. The 304 also keeps the securityFloor headers.
  • 404: a missing repository returns 404 before any tag is computed, so it carries no ETag even with If-None-Match: *.
  • Body: it is byte-for-byte what json() produced before. I did not touch src/http/api.ts.
  • Tests: test/iterations-etag.test.ts covers each point in the issue.

Branch-list cache delay

  • The branch lists behind the body come from a 5-second cache that a plain push does not clear. A push therefore shows in the body, and so in the tag, once that cache lapses. The tag is never staler than the body.
  • The test calls forget(app) before each check to stand in for that lapse.
  • I did not change the cache. That would change behaviour outside this issue.

I did not add or change any MCP tool.

How to try it: on the preview, call /api/v1/repos/<owner>/<name>/iterations with curl -si. Then repeat the call with -H 'If-None-Match: <the etag from the first call>' and expect a 304.

Approved

Clean, minimal change. The route tags the exact serialized bytes after the access check; 200 and 304 share etag/cache-control/vary; 404 precedes any tag. Tests cover each case, including viewer-distinct tags and push/fork/private movement. No README or dependency changes.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs