docs(website): record the landed route manifest and resequence CSP after PR 0
Companion to website PR 0 (chore(server): freeze the URL surface with a generated route manifest). BACKEND_DESIGN.md gains § 4.0, naming the two generated artifacts that are actually authoritative about the API and what each is authoritative *for*: the manifest records which URLs exist (introspection-derived, reality), the Swagger spec records what they mean (annotation-derived, intent). The prose tables in § 4 are orientation and can drift; those two files cannot. Also documents routes.guards.json as a review aid that is explicitly not a contract. API_V2_PLAN.md marks PR 0 shipped and records its two deviations. The optional unauthenticated-status snapshot was tried and dropped exactly as that section allowed — against the dead-port mariadb pool the tests use it sits on the acquire timeout rather than failing fast — replaced by a deterministic assertion that every /admin/** and /player/** route still carries requireAuth. routes.guards.json is committed and staleness-checked even though a diff in it is not a contract change, because an ungenerated review aid rots into a misleading one. The sequencing section is corrected: PR 0 now runs before the CSP pair. The CSP report-only PR must stand up a POST /api/csp-report collector for `report-to` to target, which is a new URL under /api/**; landing it first would have left PR 0 generating 200 routes against a 199-route baseline, destroying its own acceptance test. With PR 0 first, the collector appears as a reviewed, deliberate +1 in the manifest — the mechanism working as intended. api-route-inventory.json is unchanged, which is the point: the generator reproduced it byte-for-byte on first run. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -261,7 +261,30 @@ Because the auth model is untouched and the URLs are frozen, each PR is a **pure
|
||||
with a green test suite and a zero-diff route manifest as its acceptance criteria** — which is what
|
||||
makes grouped PRs actually reviewable.
|
||||
|
||||
### PR 0 — the route manifest (prerequisite of the first split PR)
|
||||
### PR 0 — the route manifest (prerequisite of the first split PR) — **landed**
|
||||
|
||||
> **Status: shipped.** `server/scripts/routeManifest.js` + `npm run routes:manifest`,
|
||||
> `server/routes.manifest.json` (199 public + 2 internal), `server/routes.guards.json`,
|
||||
> `server/test/routeManifest.test.js`, and a `routes:manifest -- --check` step in
|
||||
> `.gitea/workflows/pr-checks.yml`. No router file moved. **The generator reproduced
|
||||
> `api-route-inventory.json` byte-for-byte on first run**, so the freeze is in effect and the
|
||||
> committed baseline is confirmed accurate rather than merely asserted.
|
||||
>
|
||||
> Two deviations from the design below, both deliberate:
|
||||
>
|
||||
> - **The unauthenticated-status snapshot was tried and dropped**, exactly as this section allowed.
|
||||
> Firing unauthenticated GETs at every manifest path against the dead-port mariadb pool the tests
|
||||
> use does not fail fast — the pool sits on its acquire timeout, and a partial sweep had not
|
||||
> finished after two minutes. A flaky two-minute gate is worse than none. What replaced it is
|
||||
> cheap and deterministic: the test suite asserts from the introspected stack that every
|
||||
> `/api/v1/admin/**` and `/api/v1/player/**` route still carries `requireAuth`.
|
||||
> - **`routes.guards.json` is committed and staleness-checked**, though a diff in it is explicitly
|
||||
> *not* a contract change. Left ungenerated it would rot into a misleading review aid within a
|
||||
> release. The gate is on freshness; the meaning of a guards diff is still "read this", not
|
||||
> "justify this". The generator drops app-level plumbing (helmet, morgan, the JSON parser, the bot
|
||||
> guard) since it applies uniformly to all 199 routes and would bury the per-route gates.
|
||||
|
||||
|
||||
|
||||
"Every URL is unchanged" must be *proved by a diff*, not asserted in review. PR 0 lands the tool that
|
||||
proves it, with no router file moved.
|
||||
@@ -309,9 +332,17 @@ proves it, with no router file moved.
|
||||
|
||||
CSP and the split are independent; the only hard ordering is PR 0 before the first split PR.
|
||||
|
||||
1. **PR — CSP report-only.** Tightened policy behind `Content-Security-Policy-Report-Only` + `report-to`.
|
||||
2. **PR — CSP enforce.** One release later, assuming a clean violation report.
|
||||
3. **PR 0 — route manifest.** Generator + CI check + committed baseline of today's surface. No routers moved.
|
||||
**Resequenced during implementation: PR 0 ships first, before the CSP pair.** The CSP report-only PR
|
||||
has to stand up a violation collector (`POST /api/csp-report`) for `report-to` to point at — which is
|
||||
a new URL under `/api/**`. Landing it first would mean PR 0's generator emitting 200 routes against a
|
||||
199-route committed baseline, so PR 0 could no longer prove itself by reproducing
|
||||
`api-route-inventory.json` byte-for-byte. With PR 0 first, the collector shows up as a reviewed,
|
||||
deliberate `+1` in the manifest — which is exactly the mechanism working as designed.
|
||||
|
||||
1. **PR 0 — route manifest.** Generator + CI check + committed baseline of today's surface. No routers moved. ✅ landed
|
||||
2. **PR — CSP report-only.** Tightened policy behind `Content-Security-Policy-Report-Only` + `report-to`,
|
||||
plus the report collector (manifest `+1`).
|
||||
3. **PR — CSP enforce.** One release later, assuming a clean violation report.
|
||||
4. **PR 1 — admin:** `users`, `account`, `invites`, `auth` (providers).
|
||||
5. **PR 2 — admin:** `moderation`, `bot-activity`, `activity`.
|
||||
6. **PR 3 — admin (content):** `posts`, `pages`, `wiki`, `uploads`.
|
||||
|
||||
Reference in New Issue
Block a user