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

3.4 KiB

WP-75 — Close the remaining FE/BE seams

Status: done (6fa27d1) Phase: 12 — DDD hardening

Why

WP-71 added scripts/check-seam.sh guarding one literal pair (the scholing threshold) and documented three further FE/BE duplications that nothing tested across the seam. This closes them — two by deletion, one by a guard, one by an actual fix.

Decisions (pre-made)

  1. Dead reference impls get deleted, and CLAUDE.md is amended. This overturns the documented policy that server-owned rules "stay in domain/*.policy.ts as reference impl + unit test". That policy is precisely what kept dead code alive. Blast radius is small: registration.policy.ts is the only *.policy.ts in the repo.
  2. Guard the Besluit tag list by extending check-seam.sh, not adding a second script.
  3. The phone seam gets a contract test, not a grep check — see below.

Acceptance criteria

  • isHerregistratieEligible deleted (uncalled; dead by its own doc-comment) along with isStatusConsistent (also uncalled — WP-71 had added a spec for it the session before). The three live exports (statusLabel, statusColor, herregistratieDeadline) stay, and herregistratieDeadline gained direct coverage it previously only had transitively.
  • CLAUDE.md amended: server-owned rules live only on the server; the FE may mirror a server-supplied value (a threshold, a bound) for instant feedback, but never reimplements the algorithm. ADR-0001's matching claim aligned.
  • check-seam.sh guards the Besluit tag list, proven to fail when a fourth member is added to the C# enum only, naming both files and both lists. Anchored on the full declaration so it avoids the "greps all matches" trap WP-69 documented.
  • Phone contract test added and green; backend stripping fixed.

The phone divergence was real, not latent

WP-71 recorded this as latent because the Angular app normalises before sending — true of that path. The contract test proved the two sides genuinely disagreed: the backend returned 422 for +31612345678 and (06) 12345678, both of which the FE's own parseTelefoonnummer accepts. Any non-Angular client, crafted POST, or future FE change would have hit it.

SubmissionRules.RejectPhoneChange now strips exactly what the FE strips ([\s\-()], then a leading +310) before applying the shared ^0\d{9}$. The FE value object was not touched — it is the more permissive and correct side.

Why a contract test rather than a grep check: both sides carry the identical ^0\d{9}$ literal, so a drift check would have compared them, found them equal, and reported all clear. The divergence was in the normalisation before the regex — invisible to text comparison. Worth remembering when choosing between the two guard styles: grep checks catch drifting constants, contract tests catch drifting behaviour.

Verification

npm run check:seam    # both checks OK
npm run ci
cd backend && dotnet test BigRegister.slnx --filter "Category!=Integration"

Follow-ups

  • Making Besluit flow through the generated client as an enum rather than a string would remove that seam entirely rather than guarding it — a wire change, so not done here.
  • The herregistratie-eligibility seam is closed by deletion; if a FE mirror is ever reintroduced, the disjoint-fixture problem returns and would need a contract test, not a grep check.