Four close-outs and their README rows. The behaviour spec is regenerated once here rather than per-track — it derives from every test name in the repo, so any track running it would have conflicted with the other three. Records two findings the arc surfaced but did not cause: the /brief/preview staleness for non-DemoOwner identities (blocking per-spec identity isolation in brief-v2.spec.ts), and that WP-72/73 had to share a commit because both edit Program.cs — separate execution waves prevented build collisions but did not produce separable diffs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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)
- Dead reference impls get deleted, and
CLAUDE.mdis amended. This overturns the documented policy that server-owned rules "stay indomain/*.policy.tsas reference impl + unit test". That policy is precisely what kept dead code alive. Blast radius is small:registration.policy.tsis the only*.policy.tsin the repo. - Guard the
Besluittag list by extendingcheck-seam.sh, not adding a second script. - The phone seam gets a contract test, not a grep check — see below.
Acceptance criteria
isHerregistratieEligibledeleted (uncalled; dead by its own doc-comment) along withisStatusConsistent(also uncalled — WP-71 had added a spec for it the session before). The three live exports (statusLabel,statusColor,herregistratieDeadline) stay, andherregistratieDeadlinegained direct coverage it previously only had transitively.CLAUDE.mdamended: 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.shguards theBesluittag 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 +31 → 0) 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
Besluitflow through the generated client as an enum rather than astringwould 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.