Files
ehoandClaude Opus 5 12f17d9d73 docs: archive the finished backlogs (RD-30)
Two backlog trees are complete: `docs/project/backlog/` (75 files, every
WP done) and `docs/project/refactor-backlog-setup/` (the arc before it).
Move both under `docs/project/archive/` with `git mv`, so history stays
intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them,
because it points at the now-archived backlog README.

Add `docs/project/archive/README.md`. It states that these trees are
historical and names the two directories that are still live.

Repoint every inbound reference named in RD-30's Files table: CLAUDE.md,
the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the
`document-feature` and `new-ssp` skills, and the readable-codebase PLAN,
README, and RD-19 ticket. Fix two upward-relative links inside the moved
WP files (WP-68, WP-69) that gained a directory level and would otherwise
break. Repoint `.prettierignore`'s two agent-prompt exclusions to their
new path, so prettier keeps leaving those files' exact wording alone.

Mark RD-30 done and check off its acceptance criteria; flip its README
row to done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:00:38 +02:00

4.3 KiB

WP-74 — E2E isolation without a new backend endpoint

Status: done (42f7bd6) Phase: 12 — DDD hardening

Why

The three Playwright specs shared one mutable backend and admitted it in their own comments ("Restart the backend between CI runs — a second run would see a leftover Concept"). A crashed mid-wizard run poisoned every subsequent run via CreateConcept's 409, and both mutating specs acted as the same identity (DocumentStore.DemoOwner), so any new state-touching spec would collide immediately.

Decisions (pre-made)

WP-70 recorded the fix as a dev-only seed endpoint. That premise was wrong, and exploration established why:

  • The DB path already routes through IConfiguration (Program.cs, Db.ConnectionString = GetConnectionString("AppDb") ?? …), so ConnectionStrings__AppDb as an env var gives a throwaway DB with zero backend change — the same trick TestWebApplicationFactory already uses per-test.
  • StubIdentityProvider already honours an X-Subject header; the only gap was that no FE interceptor sent one.
  • The backend has no IsDevelopment() gate anywhere (grep: zero hits), so a seed endpoint would have had to invent the codebase's first environment gate — a new security posture for no gain.

So: throwaway DB + a dev-only X-Subject interceptor. No new endpoint, no environment gate.

Acceptance criteria

  • npm run e2e passes twice back-to-back with no backend restart — the actual acceptance test, and the thing that failed before this WP.
  • X-Subject observed on a real request reaching the backend (X-Subject: 111222333 on GET /api/v1/uploads/categories), not merely wired.
  • Each new BSN elfproef-verified by script against the weights [9,8,7,6,5,4,3,2,-1].
  • No backend change, no new endpoint, no IsDevelopment() gate.
  • Committed port config still defaults to 4200 (verification used an override).

Notes on the two caveats

  • reuseExistingServer stays on. Flipping it to false would hard-fail npm run e2e for anyone already running the docker stack on 4200/5000 — a real local-workflow regression. The consequence (the throwaway DB only applies when Playwright itself spawns the backend; always true in CI) is documented in a comment on the webServer entry.
  • Unique DB filename per invocation, with global-setup.ts sweeping only prior runs' leftovers. A fixed name unlinked mid-run is only safe if SQLite's pool never reopens by path afterwards; under fullyParallel that risks silently recreating an empty, unmigrated DB.

Deviation: interceptors alone were not enough

Two hand-written call sites bypass Angular's interceptor chain (as CLAUDE.md documents) and needed X-Subject stamped explicitly:

  • libs/shared/src/upload/upload.adapter.ts's raw XHR upload — without this every uploaded document landed under DemoOwner, breaking submit for any other identity.
  • apps/ssp/.../letter-preview.adapter.ts's preview fetch (plus cache: 'no-store', correct regardless since the endpoint sends no Cache-Control).

Known gap (a real backend bug, not caused by this WP)

Under any BSN other than DemoOwner, GET /brief/preview returns a sent letter still carrying the draft watermark — while curl against the same backend at the same instant returns the correct frozen archive. Client caching was ruled out (no-store, then cache-busting query strings), the dev proxy was ruled out, and it reproduced across two BSNs and never for DemoOwner. This points at a staleness/race in BriefStore's SQLite read path.

brief-v2.spec.ts therefore keeps the shared zorgverlener identity — it still gains throwaway-DB repeatability, just not per-spec identity isolation. actors.ts reserves a briefOpsteller actor for whoever fixes the backend. Tracked as a follow-up below.

Verification

npm run e2e   # twice consecutively, no backend restart
npm run lint && npm run typecheck && npm test && npm run build

Note: port 4200 was held by an unrelated container on the dev machine, so verification ran with E2E_BASE_URL pointed at an alternate port. The committed default is unchanged.

Follow-ups

  • /brief/preview staleness for non-DemoOwner identities (above) — the blocker for giving brief-v2.spec.ts its own identity.