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

GET /api/v1/repos/:owner/:name/upstream carries an ETag and answers a repeat with 304 until the outside upstream or its last check changes #9267

Merged opened this

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

Merged

`GET /api/v1/repos/:owner/:name/upstream` now sends a weak ETag, and a repeat that holds it gets a 304. The new test file and the existing upstream timing test pass, and `tsc --noEmit` is clean. I did not run `scripts/fast.ts` or the full suite; the forge runs the full check on the push. `scripts/gate.ts` printed nothing in the same command, so I can't say whether it ran or found anything.

shipped production is serving it deploy 1f09d72

opened this

GET /api/v1/repos/:owner/:name/upstream now sends a weak ETag, and a repeat that holds it gets a 304. The new test file and the existing upstream timing test pass, and tsc --noEmit is clean. I did not run scripts/fast.ts or the full suite; the forge runs the full check on the push. scripts/gate.ts printed nothing in the same command, so I can't say whether it ran or found anything.

  • Change: the only source edit is in src/http/api/upstream.ts. The tag is weakEtag over the exact JSON bytes, sent with cache-control: private, no-cache and vary: authorization, cookie. The 304 carries the same headers and no body. It is built the same way as the /forks read, so the securityFloor headers come from the usual response path. Response bodies are unchanged byte for byte, and I did not touch api.ts.
  • Access: the tag is computed after the existing scope, repository and admin check. A non-admin, an anonymous caller and a missing repository get the same responses as before, with no ETag, even with If-None-Match: *. A tag taken from the admin's response doesn't turn a stranger's refusal into a 304.
  • Tests: test/upstream-etag.test.ts covers the headers, the 304 and the access cases above. It also checks that the tag moves when:

- the URL or branch changes; - a check records a result; - a check repeats at a new time; - the upstream is disabled; - the upstream is removed.

  • Not done: the POST …/upstream/check and PUT …/upstream responses have no ETag, which the issue didn't ask for.

Closes #9245

commented

GET /api/v1/repos/:owner/:name/upstream now sends a weak ETag, and a repeat that holds it gets a 304. The new test file and the existing upstream timing test pass, and tsc --noEmit is clean. I did not run scripts/fast.ts or the full suite; the forge runs the full check on the push. scripts/gate.ts printed nothing in the same command, so I can't say whether it ran or found anything.

  • Change: the only source edit is in src/http/api/upstream.ts. The tag is weakEtag over the exact JSON bytes, sent with cache-control: private, no-cache and vary: authorization, cookie. The 304 carries the same headers and no body. It is built the same way as the /forks read, so the securityFloor headers come from the usual response path. Response bodies are unchanged byte for byte, and I did not touch api.ts.
  • Access: the tag is computed after the existing scope, repository and admin check. A non-admin, an anonymous caller and a missing repository get the same responses as before, with no ETag, even with If-None-Match: *. A tag taken from the admin's response doesn't turn a stranger's refusal into a 304.
  • Tests: test/upstream-etag.test.ts covers the headers, the 304 and the access cases above. It also checks that the tag moves when:

- the URL or branch changes; - a check records a result; - a check repeats at a new time; - the upstream is disabled; - the upstream is removed.

  • Not done: the POST …/upstream/check and PUT …/upstream responses have no ETag, which the issue didn't ask for.
commented

GET /api/v1/repos/:owner/:name/upstream now sends a weak ETag and answers a repeat that holds it with a 304 and no body. The first check on this pull failed only because no commit message had a "Try:" line, so I pushed an empty commit that has one. I did not rerun the tests for that commit. scripts/gate.ts printed nothing when I ran it just before the push, but I did not look at its exit status, so I can't confirm it passed. The code is unchanged from the first push, where test/upstream-etag.test.ts, the upstream timing test and tsc --noEmit all passed.

What changed

  • Headers: the tag is weakEtag over the exact JSON bytes, sent with cache-control: private, no-cache and vary: authorization, cookie. The 304 carries the same headers and the usual security headers.
  • Access: the tag is built only after the existing scope, repository and admin check. A token short of ro, a non-admin and a missing repository answer as before, with no ETag, even with If-None-Match: *. Bodies are byte-for-byte as before, and api.ts is untouched.
  • Tests: test/upstream-etag.test.ts checks that the tag moves when the upstream URL or branch changes, a check records a result or repeats at a new time, the upstream is disabled, or it is removed. It also checks the 304 headers and the access cases above.

Left out: PUT …/upstream and POST …/upstream/check have no ETag; the issue didn't ask for one.

Try it: call GET /api/v1/repos/<owner>/<name>/upstream on the preview with an admin token. Repeat it with the returned etag in If-None-Match and expect a 304.

Approved

Clean. The tag is computed over the exact bytes sent, only after the scope/repo/admin check, so refusals carry no ETag and * cannot reach a 304. Imports exist, headers and 304 match the issue, and the tests cover each case, including check-time movement and removal. No new dependency, no dead code. Nothing blocking.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs