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
Check · Review · Land · Live done — Landed and live in production.
`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
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 isweakEtagover the exact JSON bytes, sent withcache-control: private, no-cacheandvary: authorization, cookie. The 304 carries the same headers and no body. It is built the same way as the/forksread, so thesecurityFloorheaders come from the usual response path. Response bodies are unchanged byte for byte, and I did not touchapi.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.tscovers 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/checkandPUT …/upstreamresponses have no ETag, which the issue didn't ask for.
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
weakEtagover the exact JSON bytes, sent withcache-control: private, no-cacheandvary: 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, andapi.tsis untouched. - Tests:
test/upstream-etag.test.tschecks 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.
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.
GET /api/v1/repos/:owner/:name/upstreamnow sends a weak ETag, and a repeat that holds it gets a 304. The new test file and the existing upstream timing test pass, andtsc --noEmitis clean. I did not runscripts/fast.tsor the full suite; the forge runs the full check on the push.scripts/gate.tsprinted nothing in the same command, so I can't say whether it ran or found anything.src/http/api/upstream.ts. The tag isweakEtagover the exact JSON bytes, sent withcache-control: private, no-cacheandvary: authorization, cookie. The 304 carries the same headers and no body. It is built the same way as the/forksread, so thesecurityFloorheaders come from the usual response path. Response bodies are unchanged byte for byte, and I did not touchapi.ts.If-None-Match: *. A tag taken from the admin's response doesn't turn a stranger's refusal into a 304.test/upstream-etag.test.tscovers 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.
POST …/upstream/checkandPUT …/upstreamresponses have no ETag, which the issue didn't ask for.Closes #9245