Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/builds/:label/shots/looks carries an ETag and answers a repeat with 304 until a new look of the pin's commit is taken #9289
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/builds/:label/shots/looks` now carries a weak ETag, and a repeat that holds it gets a 304 with no body. `bun scripts/gate.ts` printed nothing, `bun x tsc --noEmit` is clean, and `test/build-shots.test.ts` passes (13 tests). I did not run the full suite.
shipped production is serving it deploy 9e69a5f
GET /api/v1/repos/:owner/:name/builds/:label/shots/looks now carries a weak ETag, and a repeat that holds it gets a 304 with no body. bun scripts/gate.ts printed nothing, bun x tsc --noEmit is clean, and test/build-shots.test.ts passes (13 tests). I did not run the full suite.
- Change: the handler in
src/http/api/build_shots.tsfollows the same shape asGET …/builds. It hashes the JSON it would send withweakEtag, and answers 304 whenIf-None-Matchholds the tag. - Per-viewer tag: the hash is taken after the
mayFetchLookfilter. A viewer who may fetch fewer looks therefore gets a different tag. - Headers: the response carries
cache-control: private, no-cacheandvary: authorization, cookie. The server appendsAccept-Encodingtovary, so the header actually readsauthorization, cookie, Accept-Encoding. The test checks that it containsauthorization, cookie. - Test: the new test in
test/build-shots.test.tschecks that the first GET has the tag and headers, that a repeat returns an empty 304, and that recording a new look for the pin's commit returns 200 with a different tag. The ETag block is the only code the new test depends on, so it fails without the change. - Test placement: I put the new test after the "picks screenshots" test, because recording an extra look earlier changes the ids the later pick test expects.
I did not add a test for the viewer-filter case (a viewer who may fetch fewer looks gets a different tag). It holds because the filter runs before the hash, but nothing tests it.
#9276 edits this file for the sibling route, so whichever of the two lands second will need a rebase.
GET /api/v1/repos/:owner/:name/builds/:label/shots/looks now carries a weak ETag. A repeat request that sends it in If-None-Match gets an empty 304 with the same headers. The code is unchanged since the first push.
The gate rejected the pull because no commit message had a Try: line. I added an empty commit whose message carries one and pushed it as a normal push. I did not re-run bun scripts/gate.ts or the tests after this push, so the new check run is the first confirmation. The earlier local run of test/build-shots.test.ts passed, and bun x tsc --noEmit was clean.
- What changed:
src/http/api/build_shots.tshashes the JSON it would send, after themayFetchLookfilter, withweakEtag. A viewer who may fetch fewer looks gets a different tag. It sendscache-control: private, no-cacheandvary: authorization, cookie. The server also appendsAccept-Encodingtovary. - Test:
test/build-shots.test.tschecks the tag and headers, then a repeat that gets a 304. After a new look is recorded for the pin's commit, the same tag gets a 200 with a new tag. I did not add a test for the viewer-filter case. - Try it:
curl -si -H 'authorization: Bearer <token>' https://lab--9289--forge-trevato-apps.open-platform.sh/api/v1/repos/<owner>/<name>/builds/<label>/shots/looks. Then repeat it withIf-None-Matchset to the returnedetagand expect 304. - Conflict risk: #9276 edits the same file for the sibling route, so whichever lands second needs a rebase.
GET …/builds/:label/shots/looks now carries a weak ETag and answers a repeat that holds it with a 304. The last check failed on a duplicate import, and I've fixed that and pushed again.
I rebased by merging main into the branch. #9276 had already landed and added the same etag.ts import to src/http/api/build_shots.ts, so the file imported holds and weakEtag twice. I deleted the extra line. bun x tsc --noEmit is clean and test/build-shots.test.ts passes (14 tests). I did not run the gate script or the rest of the suite here.
- Change: the route sets
etag,cache-control: private, no-cacheandvary: authorization, cookie. The tag is taken over the JSON after themayFetchLookfilter, so a viewer who may fetch fewer looks gets a different tag. The server also appendsAccept-Encodingtovary. - Tests: the new test in
test/build-shots.test.tschecks that a repeat gets a 304. It also checks that recording a new look for the pin's commit gives a 200 with a new tag. There is no test of the viewer-filter case. - Try it: GET
/api/v1/repos/<owner>/<name>/builds/<label>/shots/lookson the preview with a bearer token, then repeat it withIf-None-Matchset to the returnedetagand expect 304.
The route change is correct and the preview serves ETags. One thing to fix: box 2 (a viewer who may fetch fewer looks gets a different tag) is behaviour this pull adds, and nothing tests it. The filter by mayFetchLook sits inside the hash, so a regression that hashed before filtering would pass the suite. Add a test: record a look that alice may fetch and bob may not (or the reverse), then assert the two tags differ and that bob's tag does not match alice's If-None-Match (200, not 304). Also drop the needless reorder of the Handler import in build_shots.ts, which is unrelated churn. Box 1 and box 3 are met.
Both items are already in commit 16713a8e on the branch, and the pull's head is that commit. I did not re-run the tests or the type check this turn.
Changes since the last description:
- Viewer-filter test: a new test in
test/build-shots.test.tscovers two viewers with differentmayFetchLookresults. The looks were taken as the owner, so alice (a writer) may fetch them and bob (a reader) may not.
- Bob gets an empty list and a different ETag from alice.
- Bob sending alice's tag in If-None-Match gets a 200, not a 304.
- Import reorder: reverted. The diff against
mainno longer touches theHandler/etagimport lines insrc/http/api/build_shots.ts. - Code change:
GET …/builds/:label/shots/lookshashes the JSON it would send, after themayFetchLookfilter. It sendsetag,cache-control: private, no-cacheandvary: authorization, cookie. A repeat that holds the tag gets an empty 304. The server also appendsAccept-Encodingtovary.
I made no other changes.
To try it, GET /api/v1/repos/<owner>/<name>/builds/<label>/shots/looks on the preview with a bearer token. Repeat it with If-None-Match set to the returned etag and expect 304. Taking a new look of the pin's commit then gives a 200 with a new tag.
Closes #9282
🤖 Generated with Claude Code
The earlier ask, a test for the per-viewer filter, is met: alice gets items, bob gets [] with a different tag, and bob sending alice's tag gets 200. The route hashes the filtered JSON, sets etag, cache-control private, no-cache and vary authorization, cookie, and answers 304 on a held tag. The preview answers 200 on /. I read the diff only; I did not run the tests.
GET /api/v1/repos/:owner/:name/builds/:label/shots/looksnow carries a weak ETag, and a repeat that holds it gets a 304 with no body.bun scripts/gate.tsprinted nothing,bun x tsc --noEmitis clean, andtest/build-shots.test.tspasses (13 tests). I did not run the full suite.src/http/api/build_shots.tsfollows the same shape asGET …/builds. It hashes the JSON it would send withweakEtag, and answers 304 whenIf-None-Matchholds the tag.mayFetchLookfilter. A viewer who may fetch fewer looks therefore gets a different tag.cache-control: private, no-cacheandvary: authorization, cookie. The server appendsAccept-Encodingtovary, so the header actually readsauthorization, cookie, Accept-Encoding. The test checks that it containsauthorization, cookie.test/build-shots.test.tschecks that the first GET has the tag and headers, that a repeat returns an empty 304, and that recording a new look for the pin's commit returns 200 with a different tag. The ETag block is the only code the new test depends on, so it fails without the change.I did not add a test for the viewer-filter case (a viewer who may fetch fewer looks gets a different tag). It holds because the filter runs before the hash, but nothing tests it.
#9276 edits this file for the sibling route, so whichever of the two lands second will need a rebase.
Closes #9282