From a4d59ab2612c824e12959a89fb0055c9602a892c Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Wed, 19 Aug 2026 14:01:40 +0100 Subject: [PATCH 1/2] Add staleness-aware CDN caching and a Postgres-backed rate limiter to the public API --- api/src/index.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/api/src/index.ts b/api/src/index.ts index f80e907..5c09067 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -22,6 +22,36 @@ // those are separate concerns, not built speculatively. The leaderboard ranks // purely on safety_score (payment-blind, a non-negotiable rule). // +// --------------------------------------------------------------------------- +// NOT AT PARITY WITH THE DEPLOYED ROUTES. READ THIS BEFORE SPINNING IT BACK UP. +// +// The dashboard's route handlers gained CDN caching and a rate limiter; this +// server has NEITHER, and that gap is deliberate rather than an oversight to +// port across. Both of those are deployment concerns, not API concerns: +// +// - The cache is a `Cache-Control` header interpreted by Vercel's CDN. A +// standalone service has no CDN in front of it by default, so the same +// header would be a no-op — the shared cache tier has to come from wherever +// this is actually deployed (a reverse proxy, a CDN, or an in-process cache, +// which only becomes viable once there is a single long-lived process). +// - The rate limiter's counter lives in Postgres SPECIFICALLY because +// serverless has no shared memory. A single long-lived Node process does, +// so paying a database round trip per request here would be the wrong +// trade — this is exactly the case where an in-memory bucket is correct. +// +// So: whoever revives this owns both decisions afresh. What must NOT change on +// the way is the JSON contract or the versioned paths, which are shared. +// +// One rule does carry over verbatim, because it is a correctness property of the +// data rather than of the transport: WHATEVER CACHES THIS, IT MUST NOT MASK +// `lastRunAt` / `lastRunStatus`. Those two fields are how a consumer knows our +// data is stale, so a cache that serves a stale `lastRunStatus` is lying about +// freshness in the one place we promise not to. The deployed routes solve it by +// deriving each response's TTL from the `lastRunAt` values in its own body +// (dashboard/app/api/_cache.ts) rather than using a fixed TTL — the reasoning, +// and the bound it buys, are in ARCHITECTURE.md "Caching and rate limits". +// --------------------------------------------------------------------------- +// // Connection: reuses @stenion/db's getPool(), which reads the same repo-root // DATABASE_URL the indexer uses — Neon's *pooled* (-pooler / PgBouncer) // endpoint. That's the right one under Vercel serverless: many short-lived From e818f6c1206e891c4a763e99350b5f6ba1aefd6a Mon Sep 17 00:00:00 2001 From: dubemoyibe-star Date: Wed, 19 Aug 2026 14:02:24 +0100 Subject: [PATCH 2/2] Add staleness-aware CDN caching and a Postgres-backed rate limiter to the public API --- ARCHITECTURE.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9a998b1..310f32b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -592,3 +592,14 @@ dashboard (same `Store` methods, identical JSON contract). The package is kept in the tree — as the reference for the original bare-Node implementation and in case a standalone API service is ever wanted again — but it is **legacy and not deployed**. The live API is the dashboard's routes. + +**It is not at parity, deliberately.** It has no caching and no rate limiting, and those were not +back-ported when the dashboard routes gained them. Both are deployment concerns rather than API +concerns: the cache is a `Cache-Control` header that only means something with a CDN in front, and +the rate limiter's counter lives in Postgres _specifically because_ serverless has no shared memory +— a single long-lived Node process has memory, so paying a database round trip per request there +would be the wrong trade. Whoever revives it owns both decisions afresh; the JSON contract and the +versioned paths are what must not change. The one rule that does carry over is a property of the +data rather than the transport: **whatever caches it must not mask `lastRunAt`/`lastRunStatus`.** +The header comment in `api/src/index.ts` says all of this at the point someone would actually read +it.