/** * Who is logged in. Framework-free domain type. * * The `zorgverlener` variant of ADR-0002 §3's `Principal` union — the SSP has exactly * one actor kind (a citizen, authenticated via DigiD/BSN), so this app's own copy of * the union only ever holds this one member. `kind` is still a discriminant, not * decoration: it is what makes `apps/behandelportal`'s `medewerker` variant a * genuinely different type rather than a same-shaped coincidence, and what a future * third actor (§4 — admin/auditor/institution-rep) would add a member to. */ export interface Principal { readonly kind: 'zorgverlener'; readonly bsn: string; readonly naam: string; } export function isAuthenticated(p: Principal | null): p is Principal { return p !== null; } /** * Parse a persisted principal out of a raw `localStorage` string (best-effort; * anything that isn't a well-shaped record → logged out). G2: validate the * shape before trusting it. G1: even if a stored entry carries a `bsn`, the * restored principal's `bsn` is always `''` — the BSN is never persisted (see * the `SessionStore` effect that writes it), so a legacy or tampered entry * cannot resurrect one. */ export function parseStoredPrincipal(raw: string | null): Principal | null { try { if (!raw) return null; const parsed = JSON.parse(raw) as Partial; return typeof parsed?.naam === 'string' ? { kind: 'zorgverlener', bsn: '', naam: parsed.naam } : null; } catch { return null; } }