# WP-40 — PII kernel: branded `Bsn` VO + masked-value atom Status: todo Phase: 8 — platform/DX/showcase Priority: P2 ## 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. - `` 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.ts` — `bsn: 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 (spec). - [ ] `` renders masked by default with an accessible reveal; behandel-scherm uses it. - [ ] Pure maskers have specs (no TestBed); backend `MaskTail` parity noted; `npm run ci` green.