docs(adr): record Fastify 5 HTTP runtime decision as ADR-004
CI / Stage 6 — PostgreSQL integration tests (E00-S05-T01) (pull_request) Successful in 1m13s
CI / Stage 4 — Unit tests (E00-S05-T01) (pull_request) Successful in 1m40s
CI / Stage 5 — Architecture tests (E00-S05-T01) (pull_request) Successful in 3m11s
CI / Stage 7 — Build the admin and server applications (E00-S05-T01) (pull_request) Successful in 1m8s
CI / Stage 2 — Typecheck (E00-S05-T01) (pull_request) Successful in 1m12s
CI / Stage 1 — Frozen lockfile install (E00-S05-T01) (pull_request) Successful in 44s
CI / Stage 3 — Formatting/lint policy (E00-S05-T01) (pull_request) Successful in 44s
CI / Stage 6 — PostgreSQL integration tests (E00-S05-T01) (pull_request) Successful in 1m13s
CI / Stage 4 — Unit tests (E00-S05-T01) (pull_request) Successful in 1m40s
CI / Stage 5 — Architecture tests (E00-S05-T01) (pull_request) Successful in 3m11s
CI / Stage 7 — Build the admin and server applications (E00-S05-T01) (pull_request) Successful in 1m8s
CI / Stage 2 — Typecheck (E00-S05-T01) (pull_request) Successful in 1m12s
CI / Stage 1 — Frozen lockfile install (E00-S05-T01) (pull_request) Successful in 44s
CI / Stage 3 — Formatting/lint policy (E00-S05-T01) (pull_request) Successful in 44s
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
# ADR-004: Fastify 5 HTTP runtime
|
||||||
|
|
||||||
|
- Status: Accepted
|
||||||
|
- Date: 2026-08-31
|
||||||
|
- Deciders: platform stream
|
||||||
|
- References: ADR index (section 70), Technology stack wiki (sections 5.2, 6,
|
||||||
|
7, 8), Architecture wiki (sections 1, 19, 42), ADR-001, ADR-002
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
EPPP is a modular monolith (ADR-001) whose single application image contains
|
||||||
|
the public server, admin API, rendering pipeline, extension runtime and core
|
||||||
|
services. Every inbound request — public pages, the admin API and the
|
||||||
|
health/readiness endpoints — enters the platform through one HTTP runtime, so
|
||||||
|
the runtime choice is a platform-wide, hard-to-reverse decision with security,
|
||||||
|
tooling and operational reach.
|
||||||
|
|
||||||
|
The public rendering pipeline is already documented as starting at the HTTP
|
||||||
|
layer — `Fastify route → SiteResolver → VisitorPreferenceResolver →
|
||||||
|
ContentService → PageComposition + BlockRegistry → ThemeResolver → React DOM
|
||||||
|
server renderer → HTML` (Architecture wiki §19) — and the v0.1 public surface
|
||||||
|
(§42) exposes `GET /`, `GET /posts/:slug`, `GET /assets/*`, `GET /media/*`,
|
||||||
|
`GET /health/live`, `GET /health/ready` and `GET /admin/*`, all served by the
|
||||||
|
HTTP runtime.
|
||||||
|
|
||||||
|
The choice is already recorded higher up: ADR-001 fixes the runtime as
|
||||||
|
"Node.js 24 LTS with Fastify 5" and ADR-002 records the Node line. The
|
||||||
|
technology stack wiki pins **Fastify 5.12.1** in the golden compatibility
|
||||||
|
tuple (§7) as support class B — a documented support policy with no fixed
|
||||||
|
multi-year EOL date (§5.2) — and lists the approved Fastify lifecycle
|
||||||
|
plugins: `@fastify/cookie` 11.1.2, `helmet` 13.1.1, `@fastify/rate-limit`
|
||||||
|
11.2.0, `@fastify/static` 10.1.3, `@fastify/csrf-protection` 8.0.1,
|
||||||
|
`@fastify/swagger` 9.8.1, with `@fastify/multipart` 10.1.1 behind a CI gate
|
||||||
|
(§5.2).
|
||||||
|
|
||||||
|
The workspace currently boots a minimal `node:http` server for the health
|
||||||
|
endpoint (E00-S02-T03) — explicitly documented as a bootstrap until "the
|
||||||
|
Fastify 5 application shell (and the real HTTP API) lands in a later story" —
|
||||||
|
so this ADR formalises the committed runtime decision ahead of that shell.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
EPPP serves all inbound HTTP on the **Fastify 5 HTTP runtime**, exact-pinned
|
||||||
|
to Fastify 5.12.1 in the golden compatibility tuple (Technology stack §7).
|
||||||
|
One Fastify 5 application hosts the public routes, the admin API under
|
||||||
|
`/api/admin/v1/*` and the health/readiness endpoints; the temporary
|
||||||
|
`node:http` bootstrap is replaced when the Fastify 5 application shell lands
|
||||||
|
in a later story.
|
||||||
|
|
||||||
|
Fastify's plugin encapsulation matches the platform's module-boundary
|
||||||
|
discipline (ADR-001): the approved plugin set is the Fastify lifecycle list
|
||||||
|
in the technology stack (§5.2) — cookie, helmet, rate-limit, static,
|
||||||
|
csrf-protection, swagger, with multipart gated in CI before enabling — and
|
||||||
|
Fastify's native Pino 10.3.1 logger is the platform logger (the existing
|
||||||
|
secret-redaction requirement, E00-S04-T03, carries over).
|
||||||
|
|
||||||
|
Fastify is support class B: it has no fixed upstream EOL, and an upgrade to
|
||||||
|
Fastify 6 is a deliberate, ADR-recorded major change gated on a stable v6,
|
||||||
|
all required plugins compatible, the full suite green, and extension
|
||||||
|
contracts stable/migrated (§6). The decision text — **Fastify 5 HTTP
|
||||||
|
runtime** — matches the ADR index entry (ADR-004, section 70).
|
||||||
|
|
||||||
|
## Alternatives
|
||||||
|
|
||||||
|
- **Express (4.x/5.x)** — rejected: the middleware-chain model has no
|
||||||
|
first-class request/response schema validation or serialization, which
|
||||||
|
conflicts with the JSON Schema + TypeBox + Ajv validation decision
|
||||||
|
(ADR-011); plugin encapsulation and lifecycle hooks are weaker than
|
||||||
|
Fastify's, and the community middleware surface is less uniform to pin.
|
||||||
|
- **Hono** — rejected: it is oriented toward edge/serverless runtimes, which
|
||||||
|
conflicts with the long-lived process model, background jobs and
|
||||||
|
connection-backed PostgreSQL access chosen in ADR-001; the golden tuple and
|
||||||
|
extension contracts were bootstrapped on Fastify.
|
||||||
|
- **Koa** — rejected: the minimal core requires assembling routing, body
|
||||||
|
parsing, validation, security headers and logging by hand, adding glue code
|
||||||
|
with no schema-based validation story.
|
||||||
|
- **Bare `node:http`** — rejected for the real API: it is only the temporary
|
||||||
|
health bootstrap (E00-S02-T03); it provides no routing, plugin lifecycle,
|
||||||
|
validation or the ecosystem the v0.1 public surface (§42) needs.
|
||||||
|
- **Fastify 5** — chosen: schema-based validation and serialization aligns
|
||||||
|
with ADR-011, plugin encapsulation matches the module boundaries (ADR-001),
|
||||||
|
Pino logging is native, TypeScript support is first-class, and the
|
||||||
|
lifecycle plugin set is already pinned in the stack (§5.2).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Positive: schema-based request/response validation and serialization aligns
|
||||||
|
with the ADR-011 decision (JSON Schema + TypeBox + Ajv); plugin
|
||||||
|
encapsulation gives each plugin an isolated scope, matching the
|
||||||
|
module-boundary discipline of ADR-001; the runtime is already the documented
|
||||||
|
choice in ADR-001 and the golden tuple, so no rework is needed when the
|
||||||
|
application shell lands; native Pino 10.3.1 logging integrates with the
|
||||||
|
existing redaction requirement (E00-S04-T03); the pinned lifecycle plugin
|
||||||
|
set covers the v0.1 needs — cookies/opaque DB-backed sessions (ADR-019),
|
||||||
|
security headers, rate limiting, static assets/media and swagger docs.
|
||||||
|
- Negative: Fastify 5 is class B — no fixed upstream EOL date, so the support
|
||||||
|
horizon is policy-defined rather than date-defined; the exact pin (5.12.1)
|
||||||
|
must move deliberately through the dependency lanes; the application shell
|
||||||
|
does not exist yet, so this ADR commits a decision whose implementation
|
||||||
|
lands in a later story (the `node:http` bootstrap remains until then).
|
||||||
|
- Neutral: Fastify is an in-process library, so a future module extraction
|
||||||
|
(ADR-001) does not change the shared HTTP runtime unless that module needs
|
||||||
|
its own runtime; plugin majors move through the update lanes like any class
|
||||||
|
B dependency.
|
||||||
|
|
||||||
|
## Operational impact
|
||||||
|
|
||||||
|
- One Fastify 5 process serves the public routes, admin API and
|
||||||
|
health/readiness endpoints; it binds the validated `HOST`/`PORT` from the
|
||||||
|
config adapter (E00-S04-T04).
|
||||||
|
- Health/readiness remain part of the v0.1 surface (`GET /health/live`,
|
||||||
|
`GET /health/ready`, Architecture wiki §42) with readiness gated on
|
||||||
|
migration completion (E00-S03-T06).
|
||||||
|
- All log output continues through the redacting-logger pattern (E00-S04-T03);
|
||||||
|
Fastify's native Pino logger is configured with the same redaction.
|
||||||
|
- The security posture comes from the approved plugin set: helmet (headers),
|
||||||
|
csrf-protection (state-changing requests), rate-limit (abuse), cookie
|
||||||
|
(opaque DB-backed admin sessions, ADR-019), static (assets/media) and
|
||||||
|
swagger (API docs).
|
||||||
|
- Deploy/rollback is unchanged: the HTTP runtime lives inside the single
|
||||||
|
application image (ADR-001), so rollback is redeploying the previous image.
|
||||||
|
- Fastify patch/minor upgrades go through the weekly dependency sweep with
|
||||||
|
full CI; a Fastify 6 major is a programme item (ADR + compatibility + full
|
||||||
|
suite + plugin migration, §6/§8).
|
||||||
|
|
||||||
|
## Revisit trigger
|
||||||
|
|
||||||
|
- Revisit when Fastify 6 is stable, all required lifecycle plugins are
|
||||||
|
compatible, the full suite is green and the extension contracts are
|
||||||
|
stable/migrated — per the LTS strategy (§6) the move to v6 is a new ADR, not
|
||||||
|
a patch.
|
||||||
|
- Revisit if the Fastify 5 support policy changes (class B has no fixed EOL
|
||||||
|
date), or if a required plugin forces a Fastify major earlier than planned.
|
||||||
|
- Revisit if the Fastify 5 application shell, when it lands, cannot satisfy
|
||||||
|
the v0.1 public surface (§42) or the v1.1 boundary contracts (ADR-027 to
|
||||||
|
ADR-032) — for example, a hard requirement Fastify 5 cannot meet.
|
||||||
Reference in New Issue
Block a user