Files
atomic-design-poc/docs/project/archive/backlog/WP-40-pii-kernel.md
T
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.0 KiB

WP-40 — PII kernel: branded Bsn VO + masked-value atom

Status: done Phase: 8 — platform/DX/showcase Priority: P2

Outcome

  • shared/kernel/bsn.tsBsn branded VO + parseBsn with the elfproef (11-test) checksum (+spec). Wired into the DigiD login boundary (digid.adapter): login now does real BSN validation instead of "any 9 digits" — login hint + e2e BSNs updated to a valid one (123456782).
  • shared/kernel/pii.ts — pure maskBsn/maskTail/REDACTED (+spec), the functional core. Moved out of debug-state/mask.ts, which keeps only redactProfile (it depends on the registratie BigProfile, so it can't live in shared/kernel — boundary; debug-state is the sanctioned cross-context devtool). mask.spec ported to pii.spec.
  • <app-masked-value> atom (shared/ui/masked-value) + story — masked-by-default value with an optional reveal affordance; centralises the .includes('*') masked-detection that behandel-scherm used to sniff inline (now refactored to the atom). The atom only emits reveal; the caller keeps the step-up confirm + audited fetch.
  • Deviation (flagged): Session.bsn stays typed string — the brand guards the login ENTRY, but Session crosses a persistence boundary where the restore path deliberately drops the bsn (privacy) and JSON can't carry a brand, so re-typing it would add a ''/nullable ripple for no real gain. Backend MaskTail twin unchanged (still in sync).

Why

Masking is real but ad-hoc: pure helpers live in debug-state/mask.ts, the BSN is a bare string in session.ts (no branded VO / checksum, unlike BigNummer/Postcode), and masked state is detected by .includes('*') sniffing — no reusable atomic-design component. For a register this is AVG art. 9 (BSN = special category) territory; the pieces should be first-class and reusable.

Decisions

  • Bsn branded value object + parseBsn with the elfproef (9 digits, weighted mod-11) checksum — parity with the other value objects. Replace the bare string in session.ts.
  • Consolidate the pure maskers into shared/kernel/pii.ts (functional core): maskBsn, maskTail, redactProfile, … Keep the backend MaskTail twin in sync.
  • <app-masked-value> atom (shared/ui) — masked by default, optional reveal affordance + a11y; replaces .includes('*') sniffing and composes into the behandel-scherm reveal.

Files

  • New src/app/shared/kernel/bsn.ts (+spec, elfproef cases) and shared/kernel/pii.ts (+spec).
  • src/app/auth/domain/session.tsbsn: Bsn.
  • New src/app/shared/ui/masked-value/ atom (+ story).
  • src/app/shared/ui/debug-state/mask.ts — re-export/move to the kernel module.

Acceptance criteria

  • parseBsn accepts valid elfproef numbers, rejects bad checksum/length/all-zeros (spec).
  • <app-masked-value> renders masked by default with an optional reveal; behandel-scherm uses it (no more inline sniff).
  • Pure maskers have specs (no TestBed); backend MaskTail parity noted; npm run ci green.