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

GET /api/v1/repos/:owner/:name/map carries an ETag and answers a repeat with 304 while the map snapshot has not changed #9159

Merged opened this

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

Merged

`GET /api/v1/repos/:owner/:name/map` now carries a weak `etag`, `cache-control: private, no-cache` and `vary: authorization, cookie`. A repeat with a matching `If-None-Match` gets an empty 304 while the map is unchanged.

shipped production is serving it deploy 9f16946

opened this

GET /api/v1/repos/:owner/:name/map now carries a weak etag, cache-control: private, no-cache and vary: authorization, cookie. A repeat with a matching If-None-Match gets an empty 304 while the map is unchanged.

The new test/map-etag.test.ts passes, with 5 of its 5 tests failing when the src changes are removed. I also ran test/map-api-timing.test.ts, test/map-cache.test.ts and the OpenAPI and contract tests, which pass. bun x tsc --noEmit and scripts/gate.ts are clean. scripts/design-lint.ts doesn't apply because nothing under src/web/ changed.

  • Tag: it hashes the sealed scope plus the exact JSON served, including whether ?done=1 was asked. Nothing in the map JSON is an age that ticks, so the tag only moves when the facts move.
  • Scopes: two readers whose sealed scopes differ never share a tag. A stranger replaying the owner's tag gets a 200 without the sealed issue.
  • Changes: opening, blocking and closing an issue, a run starting on one, and a new pillar umbrella each produce a new tag on the next read.
  • Server-Timing: a 304 adds a not-modified phase. The existing graph, marks, pillars and render phases are still listed on a 304, as zero-cost snapshot hits, the way the route already did.
  • Docs: the route's OpenAPI module names the ETag, If-None-Match and the 304.
  • Left out: the tag is built from the snapshot's rendered bytes, not a separate version counter. A 304 still costs one JSON.stringify of the snapshot (no graph rebuild) and saves the body, not the serialisation. The pillar test only adds a pillar umbrella issue. It does not exercise the plan endpoint or the weekly merged counts.

The code is in src/http/api.ts (the map route), and the branch is pushed.

Closes #9156

commented

GET /api/v1/repos/:owner/:name/map now carries a weak etag, cache-control: private, no-cache and vary: authorization, cookie. A repeat with a matching If-None-Match gets an empty 304 while the map is unchanged.

The new test/map-etag.test.ts passes, with 5 of its 5 tests failing when the src changes are removed. I also ran test/map-api-timing.test.ts, test/map-cache.test.ts and the OpenAPI and contract tests, which pass. bun x tsc --noEmit and scripts/gate.ts are clean. scripts/design-lint.ts doesn't apply because nothing under src/web/ changed.

  • Tag: it hashes the sealed scope plus the exact JSON served, including whether ?done=1 was asked. Nothing in the map JSON is an age that ticks, so the tag only moves when the facts move.
  • Scopes: two readers whose sealed scopes differ never share a tag. A stranger replaying the owner's tag gets a 200 without the sealed issue.
  • Changes: opening, blocking and closing an issue, a run starting on one, and a new pillar umbrella each produce a new tag on the next read.
  • Server-Timing: a 304 adds a not-modified phase. The existing graph, marks, pillars and render phases are still listed on a 304, as zero-cost snapshot hits, the way the route already did.
  • Docs: the route's OpenAPI module names the ETag, If-None-Match and the 304.
  • Left out: the tag is built from the snapshot's rendered bytes, not a separate version counter. A 304 still costs one JSON.stringify of the snapshot (no graph rebuild) and saves the body, not the serialisation. The pillar test only adds a pillar umbrella issue. It does not exercise the plan endpoint or the weekly merged counts.

The code is in src/http/api.ts (the map route), and the branch is pushed.

commented

GET /api/v1/repos/:owner/:name/map now carries a weak etag, cache-control: private, no-cache and vary: authorization, cookie. A repeat with a matching If-None-Match gets an empty 304 while the map is unchanged.

Check fix: the gate failed because no commit message had a "Try:" line. I added an empty commit with one and pushed it. The local gate passes now, but I haven't seen the forge's check re-run on it yet.

What changed

  • Tag: it hashes the reader's sealed scope plus the exact JSON served, including whether ?done=1 was asked. Nothing in the map JSON is an age that ticks, so the tag only moves when the facts move. Two readers with different sealed scopes never share a tag.
  • 304 path: a 304 adds a not-modified Server-Timing phase. The existing graph, marks and render phases are still listed on a 304, as zero-cost snapshot hits, as the route already did.
  • Changes that give a new tag: opening, blocking and closing an issue, a run starting on one, and a new pillar umbrella each give a new tag on the next read.
  • Docs and tests: the route's OpenAPI module names the ETag, If-None-Match and the 304. test/map-etag.test.ts has 5 tests, which fail with the src changes removed.

Left out

  • The tag is built from the snapshot's rendered bytes, not a separate version counter. A 304 still costs one JSON.stringify of the snapshot and saves the body, not the serialisation.
  • The pillar test only adds a pillar umbrella issue. It does not exercise the plan endpoint or the weekly merged counts.

Try:

curl -si -H 'If-None-Match: <etag from a first call>' <preview>/api/v1/repos/trevato/forge/map

It answers 304 with no body.

Approved

Clean. Weak etag over scopeKey(scope) plus the exact JSON served, private no-cache, vary, 304 via the existing holds/weakEtag helpers, access checked by loadRepo first. Sealed scopes cannot share a tag and a replayed owner tag gets a 200 without the sealed issue. OpenAPI names the 304. Tests cover each behaviour. Noted, not blocking: a 304 still pays one JSON.stringify of the snapshot.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs