Public: anyone can read this; the crew can change it.
GET /api/v1/cloud/accounts carries an ETag and answers a repeat with 304 until a cloud account or its use changes #9235
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/cloud/accounts` now carries a weak ETag and answers a repeat with a 304. The new test file and the existing `test/api-cloud-accounts.test.ts` pass, and `tsc` and `scripts/gate.ts` are clean. I did not run the full suite; the forge runs it on the push. With the `src` change stashed, all four new tests fail.
shipped production is serving it deploy d5155e2
GET /api/v1/cloud/accounts now carries a weak ETag and answers a repeat with a 304. The new test file and the existing test/api-cloud-accounts.test.ts pass, and tsc and scripts/gate.ts are clean. I did not run the full suite; the forge runs it on the push. With the src change stashed, all four new tests fail.
- Change: the only source edit is in
src/http/api/cloud.ts. The tag isweakEtagof the exact JSON bytes sent, so the body is unchanged byte-for-byte. A 200 carriesetag,cache-control: private, no-cacheandvary: authorization, cookie. A request whoseIf-None-Matchholds the tag gets a 304 with the same headers and no body, andsecurityFloorstill adds its headers. I did not touchsrc/http/api.ts. - Access checks: the tag logic runs only after the checks for no session (401), a read-only token (403) and a non-admin (404). Those answer as before with no ETag, even with
If-None-Match: *. - Test:
test/cloud-accounts-etag.test.tscovers the headers and the 304, the tag moving when an account is linked or removed or a repository starts or stops a vendor, and the three refusals above. - Two viewers: I could not build two admins who see different bodies, because a forge admin sees every repository. The test instead asserts that the tag always equals the hash of the bytes sent, so viewers shown different bodies cannot share one.
- One-minute lag: a repository's
cloud.jsonis cached for up to a minute byusesByVendor, which was already so. A start or stop of use therefore moves the tag when that cache refreshes, not instantly. The test advancesDate.nowpast the minute to prove the tag moves. - Left out: the page and MCP
cloud_accountsare untouched.
GET /api/v1/cloud/accounts now carries a weak ETag and answers a repeat with a 304. The failed check was only the missing "Try:" line, so I added an empty commit carrying it and pushed. scripts/gate.ts passes locally. I didn't rerun the tests, since the commit is empty.
What changed
- In
src/http/api/cloud.ts, a 200 now hasetag(fromweakEtagover the exact bytes sent),cache-control: private, no-cacheandvary: authorization, cookie. The body is unchanged byte-for-byte. - A repeat holding that tag in
If-None-Matchgets a 304 with the same headers, no body and the security-floor headers. - The tag logic runs only after the access checks. No session (401), a read-only token (403) and a non-admin (404) answer as before with no ETag, even with
If-None-Match: *. test/cloud-accounts-etag.test.tscovers the headers and the 304, the tag moving when an account is linked or removed or a repository starts or stops a vendor, and the three refusals. It fails without the change.
What I chose not to do
- I did not touch
src/http/api.ts, the page, or MCPcloud_accounts. - I could not build two admins who see different bodies, because an admin sees every repository. The test instead asserts that the tag always equals the hash of the bytes sent, so different bodies cannot share a tag.
- A repository's
cloud.jsonis cached for up to a minute (existing behaviour). A repository starting or stopping a vendor therefore moves the tag when that cache refreshes, not instantly.
Try: as a forge admin, GET /api/v1/cloud/accounts on the preview with an rw bearer token. Repeat it with the returned etag in If-None-Match and you get a bodiless 304.
🤖 Generated with Claude Code
Clean and matches the sibling ETag pulls. The tag is weakEtag over the exact bytes sent, so viewers shown different bodies cannot share one. The 304 is reached only after the 401/403/404 checks. Headers and content-type match json(). The tests cover each behaviour. No README change, no new dependency. The preview answers 401 unauthenticated, as it should.
GET /api/v1/cloud/accountsnow carries a weak ETag and answers a repeat with a 304. The new test file and the existingtest/api-cloud-accounts.test.tspass, andtscandscripts/gate.tsare clean. I did not run the full suite; the forge runs it on the push. With thesrcchange stashed, all four new tests fail.src/http/api/cloud.ts. The tag isweakEtagof the exact JSON bytes sent, so the body is unchanged byte-for-byte. A 200 carriesetag,cache-control: private, no-cacheandvary: authorization, cookie. A request whoseIf-None-Matchholds the tag gets a 304 with the same headers and no body, andsecurityFloorstill adds its headers. I did not touchsrc/http/api.ts.If-None-Match: *.test/cloud-accounts-etag.test.tscovers the headers and the 304, the tag moving when an account is linked or removed or a repository starts or stops a vendor, and the three refusals above.cloud.jsonis cached for up to a minute byusesByVendor, which was already so. A start or stop of use therefore moves the tag when that cache refreshes, not instantly. The test advancesDate.nowpast the minute to prove the tag moves.cloud_accountsare untouched.Closes #9206