Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
30 changes: 30 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading