# ADR-C-006 — extract the actor-agnostic route guards to `libs/shared` Status: **implemented** · 2026-08-26 · Source finding: `06-adr-conformance.md` §ADR-C-006 ## What changed | File | Change | | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `libs/shared/src/application/auth.guard.ts` | **new** — `authGuard` + `capabilityGuard`, injecting `SESSION_PORT` instead of an app-local `SessionStore` | | `libs/shared/src/application/auth.guard.spec.ts` | **new** — the single spec, provides `SESSION_PORT`; one case added asserting the guard reads the port | | `libs/shared/src/application/session.port.ts` | widened by one member: `readonly isAuthenticated: Signal` | | `apps/ssp/src/app/auth/auth.guard.ts` | now a re-export | | `apps/behandelportal/src/app/auth/auth.guard.ts` | now a re-export | | `apps/{ssp,behandelportal}/src/app/auth/auth.guard.spec.ts` | **deleted** — both were byte-identical to the new shared spec | Neither `app.routes.ts` was touched: both still `import { authGuard, capabilityGuard } from '@auth/auth.guard'`. Routing asks the auth context for its guards, which is the direction the boundary should read. ## Why the port widening was free Both `SessionStore`s already exposed `readonly isAuthenticated = computed(() => this._session() !== null)` (`session.store.ts:40` in each app), and both apps already registered `{ provide: SESSION_PORT, useExisting: SessionStore }` (`app.config.ts:64` / `:65`). `SessionPort` is satisfied structurally, so adding the member required no change in either app — the seam existed, it was just narrower than what it already carried. ## Scope discipline Only the guards moved. Per the finding, **no** ticket to merge `session.store.ts`, `session.ts`, `digid.adapter.ts`, `login-form.component.ts` or `login.page.ts`, and no relaxation of ADR-0002 §3. Those five are identical because ADR-C-004 (`Session → Principal`) was never executed; merging them would cement a citizen DigiD/BSN login as the backoffice's shared login, which is the outcome §3 exists to prevent. ## Measured effect Re-ran `tools/baseline-scan.mjs --dup` after the change: | Metric | Before | After | | --------------------------- | -----: | --------: | | `ssp/auth` duplicated lines | 211 | **151** | | `bhp/auth` duplicated % | 86.8% | **82.5%** | | Repo-wide duplication | 7.1% | **6.6%** | The `auth.guard.spec.ts` (36 windows) and `auth.guard.ts` (21 windows) clone pairs have dropped out of the top-clones list entirely. The remaining `ssp/auth` ↔ `bhp/auth` duplication is `session.store.ts` (39), `login-form.component.ts` (35) and `login.page.ts` (23) — exactly the three ADR-C-004 is expected to differentiate. Re-measure BL-002 after that lands; the finding's expectation is a drop to under 40 lines. ## Verification `npm run lint` · `npm run typecheck` · `npm run dep:check` (0 violations, 224 modules) · `npx prettier --check apps libs` — all clean. Tests: shared 122, ssp 235, behandelportal 27, beheer 23 — **407 passed, 0 failed**. `ng build --localize` for both apps.