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

87 lines
4.3 KiB
Markdown

# 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
- [x] **`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] **`X-Subject` observed on a real request** reaching the backend (`X-Subject: 111222333` on
`GET /api/v1/uploads/categories`), not merely wired.
- [x] Each new BSN elfproef-verified by script against the weights `[9,8,7,6,5,4,3,2,-1]`.
- [x] No backend change, no new endpoint, no `IsDevelopment()` gate.
- [x] 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
```bash
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.