Evidence (verified on production 2026-06-12)
curl -sI against /rss.xml, /statements/2026-05-31-weekly and ...weekly.md on prod: no Cache-Control, no ETag, no cf-cache-status — Cloudflare doesn't cache Worker responses without explicit headers. The intended audience is feed readers + LLM agents polling rss.xml/llms.txt/.md on schedules; content changes at most weekly. Every poll burns a Worker invocation and renders the full content collection.
TDD plan (failing tests first)
src/lib/http/cached-response.ts (pure): feedResponse(body, contentType, { maxAge, sMaxage, swr }) returning a Response with correct headers; or a cacheHeaders() builder.
Tests FIRST: header string exactly public, max-age=300, s-maxage=3600, stale-while-revalidate=86400 (tune values — content changes weekly; 1h edge cache + SWR is conservative), charset preserved, status set.
- Adopt in
rss.xml.ts, llms.txt.ts, sitemap.xml.ts, statements.md.ts, and markdownResponse (200s only — 404s get no-store or short TTL; test that too).
- HTML pages: set via
Astro.response.headers in Layout or per-page; decide whether statement HTML gets the same TTL (probably yes).
- Purge story: deploys replace the Worker but the CF edge cache persists — either keep
s-maxage ≤ the acceptable post-publish delay (new statements appear within 1h) or add a deploy-time purge step later. Document the choice in the module.
Acceptance criteria
| AC |
Test |
| Response factory header contract (200 vs 404) |
unit |
| All feed + .md endpoints emit Cache-Control |
unit on the factory + curl after deploy shows cf-cache-status: HIT on second request |
| A newly published statement is visible at the configured staleness bound |
documented, value asserted in test |
Evidence (verified on production 2026-06-12)
curl -sIagainst/rss.xml,/statements/2026-05-31-weeklyand...weekly.mdon prod: noCache-Control, noETag, nocf-cache-status— Cloudflare doesn't cache Worker responses without explicit headers. The intended audience is feed readers + LLM agents pollingrss.xml/llms.txt/.mdon schedules; content changes at most weekly. Every poll burns a Worker invocation and renders the full content collection.TDD plan (failing tests first)
src/lib/http/cached-response.ts(pure):feedResponse(body, contentType, { maxAge, sMaxage, swr })returning aResponsewith correct headers; or acacheHeaders()builder.Tests FIRST: header string exactly
public, max-age=300, s-maxage=3600, stale-while-revalidate=86400(tune values — content changes weekly; 1h edge cache + SWR is conservative), charset preserved, status set.rss.xml.ts,llms.txt.ts,sitemap.xml.ts,statements.md.ts, andmarkdownResponse(200s only — 404s getno-storeor short TTL; test that too).Astro.response.headersinLayoutor per-page; decide whether statement HTML gets the same TTL (probably yes).s-maxage≤ the acceptable post-publish delay (new statements appear within 1h) or add a deploy-time purge step later. Document the choice in the module.Acceptance criteria
cf-cache-status: HITon second request