refactor(auth): land Session -> Principal, add MedewerkerAdapter (RB-13)
ADR-0002 SS3 models Zorgverlener/Medewerker as different Principal variants with different login flows. Actor #2 (apps/behandelportal) landed in WP-61/67 and the union never followed: grep -rn "Principal" returned one hit, a comment. Both apps' auth/domain/session.ts stayed byte-identical (`{ bsn, naam }`), so the backoffice's Behandelaar carried a BSN and logged into the backoffice as a citizen, by DigiD, under a fabricated citizen's name (login.page.ts). The divergence ADR-0002 predicted took an orthogonal side door instead (medewerker.interceptor.ts's X-Medewerker/X-Rollen stamp, which never touches SessionStore) -- which is why ssp/auth and bhp/auth still measured as 100%/84% duplicated after ADR-C-006 shared the route guards. RB-09 (landed the day before) made the backend's IIdentityProvider able to say "no identity" and fail closed; this ticket is its named FE half. Each app's auth/domain/session.ts becomes principal.ts, holding the one Principal variant that app actually has an actor for: ssp keeps `{ kind: 'zorgverlener', bsn, naam }` (G1 still strips the BSN before persisting); behandelportal gets `{ kind: 'medewerker', medewerkerId, naam, rollen }` (no BSN to strip -- G2 shape validation only). A new MedewerkerAdapter replaces DigidAdapter in behandelportal, resolving the existing MEDEWERKER_ID/currentRollen() dev stand-in into a Principal; because there is no credential to check, it returns Principal directly rather than a Result whose error variant could never occur. login.page.ts stops being a BSN/wachtwoord form -- one explainer line and an "Inloggen met SSO" button -- and its dead error-handling branch goes with the Result wrapper that justified it. Measured with tools/baseline-scan.mjs --dup: auth duplication drops from 168/168 (ssp) and 168/200 (bhp) to 32/179 and 32/259 -- under the backlog's <40 target. What remains is the ADR-C-006 route-guard re-export (deliberately identical), generic test/story-file boilerplate, and one shared fragment of the root-singleton-store idiom -- not re-converged identity or login-flow logic. SS3's prediction that the two actors would authenticate differently enough to justify not sharing auth has now actually been tested, not just asserted, and held. Also: renamed Session.bsn to Principal.bsn in two doc comments (libs/shared/src/infrastructure/subject.ts, subject.interceptor.ts) that cited the old type name; regenerated libs/shared/docs/behaviour-spec.mdx (generated file, per its own banner); recorded the resolution in ADR-0002 as a new amendment, replacing its "Known debt" section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# ADR 0002 — User groups as actors, not bounded contexts
|
||||
|
||||
Status: Accepted · Date: 2026-07-01 · Amended 2026-08-01 (WP-67)
|
||||
Status: Accepted · Date: 2026-07-01 · Amended 2026-08-01 (WP-67), 2026-08-27 (RB-13)
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -167,28 +167,34 @@ status lifecycle + authorization endpoints/DTOs — **shipped** (WP-61…WP-67):
|
||||
`AanvraagStatusTag` (`Domain/Applications/AanvraagStatus.cs`), `GET /me` (`Program.cs:578`),
|
||||
`Domain/Authorization/Authz.cs`.
|
||||
|
||||
## Known debt: `Session → Principal` was never built
|
||||
A third bullet stood here too — `Session → Principal` — from 2026-08-26 until it was paid
|
||||
off by RB-13 the next day. See the amendment below for the historical record and what
|
||||
landed.
|
||||
|
||||
§3's `Principal` union is the one decision here that has **not** been executed, and it is now
|
||||
debt rather than a deferral. Actor #2 arrived — `apps/behandelportal` shipped — and the union
|
||||
did not follow. `grep -rn "Principal" apps libs` returns a single hit: a comment in
|
||||
`libs/shared/src/infrastructure/role.ts`. There is no such type.
|
||||
## Amendment (RB-13, 2026-08-27): `Session → Principal` landed
|
||||
|
||||
What that omission actually costs, measured 2026-08-26:
|
||||
§3's `Principal` union was accepted on 2026-07-01 and not executed until now — see the
|
||||
"Known debt" record this replaces, added 2026-08-26 by the refactor-backlog audit
|
||||
(`ADR-C-004`) that found it. `apps/ssp/src/app/auth/domain/principal.ts` now exports the
|
||||
`zorgverlener` variant (`{ kind: 'zorgverlener'; bsn; naam }`);
|
||||
`apps/behandelportal/src/app/auth/domain/principal.ts` exports the `medewerker` variant
|
||||
(`{ kind: 'medewerker'; medewerkerId; naam; rollen }`) — each app holds only the one
|
||||
member of the union it actually has an actor for, per this ADR's own proposed resolution.
|
||||
`apps/behandelportal`'s `DigidAdapter` is gone; a `MedewerkerAdapter` resolves the
|
||||
dev-stand-in medewerker identity (`medewerker.ts`'s `MEDEWERKER_ID`/`currentRollen()` —
|
||||
unchanged, still the mechanism `medewerkerInterceptor` uses for the backend headers) into
|
||||
a `Principal` instead, and `login.page.ts` is an SSO-stand-in entry (one button, no BSN
|
||||
field) rather than the citizen DigiD form it used to share with the SSP verbatim.
|
||||
|
||||
- `apps/ssp/src/app/auth` and `apps/behandelportal/src/app/auth` are byte-identical —
|
||||
`diff -rq` reports **zero** content differences across 9 of 11 files, the only delta being
|
||||
two extra files in behandelportal.
|
||||
- `behandelportal`'s Behandelaar still carries a `bsn` and logs in through `DigidAdapter`.
|
||||
A backoffice user authenticates as a citizen, which is precisely what §3 was written to prevent.
|
||||
- The divergence that _did_ occur took an orthogonal side door — `medewerker.interceptor.ts`,
|
||||
a dev-only `X-Medewerker` header stamp that never touches `Session`.
|
||||
|
||||
The WP-67 amendment above justifies keeping `auth` duplicated on the grounds that it is
|
||||
"expected to diverge". That reasoning still holds — but it has never been **tested**, because
|
||||
the change that would test it is this one. Read the two identical copies as evidence that
|
||||
§3 is unexecuted, not as evidence that §3 was wrong.
|
||||
|
||||
ponytail: this ADR draws the boundaries so nothing has to be undone later. The original
|
||||
"YAGNI until the backoffice work starts" call was right when written and has now expired —
|
||||
the backoffice started. `Principal` is owed.
|
||||
The two `auth` contexts, measured 2026-08-27 after the change
|
||||
(`tools/baseline-scan.mjs --dup`): **32 duplicated lines each** (from 168 at the
|
||||
2026-08-26 measurement above; from 211 before ADR-C-006 shared the route guards). What
|
||||
remains is not re-converged identity/login-flow code — it is `auth.guard.ts`'s intentional
|
||||
verbatim re-export (ADR-C-006: a route guard is actor-agnostic, not in this ADR's scope)
|
||||
plus ordinary test/story-file boilerplate (`describe`/`it` shape, a `Meta`/`StoryObj`
|
||||
scaffold) that any two spec or story files share regardless of subject. The prediction in
|
||||
§3 — that Zorgverlener and Medewerker, modelled as distinct `Principal` variants, would
|
||||
turn out to authenticate differently enough that sharing `auth` would have been the wrong
|
||||
call — has now actually been tested, not just asserted, and held: the two contexts diverge
|
||||
in domain type, adapter, and login UI as soon as the union exists to make that
|
||||
divergence possible.
|
||||
|
||||
Reference in New Issue
Block a user