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>
This commit is contained in:
eho
2026-09-08 23:00:38 +02:00
co-authored by Claude Opus 5
parent 097e8468e0
commit 12f17d9d73
161 changed files with 154 additions and 24 deletions
@@ -0,0 +1,547 @@
## Scope: apps/ssp (6 contexts), apps/behandelportal (3 contexts), libs/shared (11 layers), libs/beheer (5 layers), backend/src/BigRegister.Api (6 folders), backend/tests
## Status: complete
## Last updated: 2026-08-26
## Depends on: none
## ---
# 00 — Metrics Baseline
Fixed input to every Phase 1 agent (0107). **No agent may propose a change without
citing a `BL-###` observation or a metric row from this file.**
---
## 1. Method, and what to trust
Everything was measured with tooling already in the repo. No dependency was added, no
config file edited.
| Metric | Command | Trust |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| FE coverage | `npm run test:coverage``coverage/{ssp,behandelportal,shared,beheer}/lcov.info` | exact |
| FE spec reach | derived: source files present in any lcov vs. files on disk | exact |
| BE coverage | `cd backend && dotnet test BigRegister.slnx --filter "Category!=Integration" --collect:"XPlat Code Coverage"` | exact |
| TS complexity + fn length | `npx eslint apps libs --rule '{"complexity":["warn",0],"max-lines-per-function":["warn",{"max":0,"skipBlankLines":true,"skipComments":true}]}' -f json` — threshold 0 makes ESLint report _every_ function with its score, giving a full distribution rather than only violations | exact (ESLint core rule, no plugin) |
| C# complexity | `node docs/project/refactor-backlog-setup/refactor-backlog/tools/baseline-scan.mjs --complexity` | **file CC exact-ish; per-method CC approximate** |
| Duplication | `node …/tools/baseline-scan.mjs --dup` | **approximate** |
| Layering violations | `npm run dep:check` | exact |
| Coupling / instability | `npx depcruise apps/<app>/src libs --config .dependency-cruiser.<app>.js --metrics --output-type metrics` | exact |
`tools/baseline-scan.mjs` is new and lives in this workspace so the numbers reproduce.
It is a crude text scan: line-window hashing for duplication (not token-based like
jscpd), regex method detection for C# (does not understand expression-bodied members or
nested lambdas). Deterministic, zero-install, good enough to answer "did this move".
Upgrade path if a ticket ever needs more precision: jscpd, and a Roslyn analyzer.
**Excluded as generated** (counted once, then excluded everywhere):
`libs/shared/src/infrastructure/api-client.ts` (2372 lines, NSwag) and
`backend/.../Data/Migrations/**` (17 files, 2690 lines, EF Core).
**LCOV aggregation rule.** The four FE project runs overlap — `libs/shared` files appear
in `ssp`'s lcov as well as `shared`'s. Each file is attributed to its owning module and
the **best** result across the four runs is taken; a shared file exercised by an app's
specs is genuinely covered.
**Protocol deviation, stated for the record.** The measurement runs are repo-global (one
coverage run, one depcruise run) and cannot be executed module-by-module; only the
write-up is per module. The resume contract still holds — the file is the recovery state.
---
## 2. Size inventory
| Unit | src .ts/.cs | src lines | spec files | spec lines | stories |
| ------------------------------ | ----------: | ---------: | ---------: | ---------: | ------: |
| apps/ssp | 92 | 10 415 | 39 | 3 564 | 21 |
| apps/behandelportal | 29 | 1 309 | 7 | 396 | 4 |
| libs/shared (excl. generated) | 86 | 5 194 | 21 | 1 226 | 40 |
| libs/beheer | 13 | 1 146 | 4 | 269 | 1 |
| **Frontend total** | **220** | **18 064** | **71** | **5 455** | **66** |
| backend/src (excl. Migrations) | 54 | 4 989 | — | — | — |
| backend/tests | 39 | — | 39 | 4 253 | — |
| e2e | 5 | 332 | — | — | — |
Backend source by folder: `Program.cs` 940 · `Data` (excl. Migrations) 1 697 · `Domain`
991 · `Zgw` 710 · `Contracts` 329 · `Stamdata` 322. Zero `.html` template files exist
anywhere in `apps/`/`libs/` — every Angular template is inline.
---
## 3. Coverage
### 3a. Frontend — line/branch coverage of files a spec actually reaches
| Module | Files | Lines | Line % | Branches | Branch % |
| -------------------------- | -----: | --------: | --------: | --------: | --------: |
| ssp/auth | 4 | 35 | 42.9% | 26 | 46.2% |
| bhp/auth | 4 | 35 | 42.9% | 26 | 46.2% |
| libs/shared/upload | 2 | 125 | 52.0% | 118 | 50.0% |
| libs/beheer/application | 1 | 70 | 65.7% | 37 | 40.5% |
| libs/beheer/infrastructure | 1 | 37 | 67.6% | 43 | 60.5% |
| ssp/herregistratie | 5 | 302 | 70.9% | 286 | 67.8% |
| libs/shared/layout | 2 | 80 | 72.5% | 36 | 55.6% |
| libs/shared/ui | 13 | 279 | 72.8% | 230 | 75.2% |
| ssp/brief | 11 | 486 | 75.3% | 461 | 68.8% |
| ssp/registratie | 21 | 430 | 80.0% | 471 | 77.3% |
| libs/shared/application | 8 | 76 | 80.3% | 50 | 70.0% |
| bhp/behandeling | 5 | 107 | 91.6% | 146 | 81.5% |
| libs/shared/infrastructure | 9 | 75 | 94.7% | 84 | 81.0% |
| libs/shared/kernel | 5 | 28 | 96.4% | 20 | 90.0% |
| libs/beheer/domain | 2 | 54 | 98.1% | 64 | 75.0% |
| ssp/showcase | 1 | 11 | 100.0% | 4 | 100.0% |
| libs/shared/testing | 3 | 9 | 100.0% | 2 | 50.0% |
| **TOTAL** | **98** | **2 240** | **75.1%** | **2 104** | **70.6%** |
### 3b. Frontend — spec _reach_ (the number that matters)
75.1% is coverage **of the 98 files a spec imports**. It is not coverage of the codebase.
122 of 220 source files are never loaded by any Vitest run at all.
| Module | Source files | Reached | Never reached | % reached |
| ---------------------------------------- | -----------: | ------: | ------------: | --------: |
| ssp/root, ssp/shell, bhp/root, bhp/shell | 12 | 0 | 12 | 0% |
| libs/shared/domain | 3 | 0 | 3 | 0% |
| libs/beheer/ui | 4 | 0 | 4 | 0% |
| libs/beheer/contracts | 1 | 0 | 1 | 0% |
| libs/shared/layout | 11 | 2 | 9 | 18% |
| bhp/behandeling | 16 | 5 | 11 | 31% |
| ssp/showcase | 3 | 1 | 2 | 33% |
| libs/shared/ui | 34 | 13 | 21 | 38% |
| ssp/brief | 26 | 11 | 15 | 42% |
| bhp/auth | 8 | 4 | 4 | 50% |
| libs/shared/upload | 4 | 2 | 2 | 50% |
| ssp/registratie | 41 | 21 | 20 | 51% |
| ssp/herregistratie | 9 | 5 | 4 | 56% |
| ssp/auth | 6 | 4 | 2 | 67% |
| libs/beheer/domain | 3 | 2 | 1 | 67% |
| libs/shared/application | 11 | 8 | 3 | 73% |
| libs/shared/infrastructure | 11 | 9 | 2 | 82% |
| libs/shared/kernel | 5 | 5 | 0 | 100% |
| **TOTAL** | **220** | **98** | **122** | **45%** |
Most of the 122 are `ui/` components, which CLAUDE.md §5 deliberately exercises through
**Storybook + the a11y addon (66 stories)**, not Vitest. That is a house decision, not a
gap — see BL-004 before filing anything against it.
### 3c. Backend
| Module | Files | Lines | Line % | Branches | Branch % |
| ------------------ | -----: | --------: | --------: | --------: | --------: |
| backend/Domain | 17 | 730 | 94.2% | 356 | 82.0% |
| backend/Stamdata | 10 | 252 | 96.8% | 120 | 71.7% |
| backend/Program.cs | 1 | 1 160 | 97.4% | 316 | 84.8% |
| backend/Contracts | 2 | 334 | 97.6% | 40 | 65.0% |
| backend/Zgw | 8 | 624 | 98.1% | 124 | 85.5% |
| backend/Data | 13 | 1 852 | 99.0% | 400 | 75.5% |
| **TOTAL** | **51** | **4 952** | **97.6%** | **1 356** | **79.6%** |
241 tests, all green. Every backend source file is reached. Branch coverage is the weak
axis, not line coverage — `Contracts` 65.0%, `Stamdata` 71.7%, `Data` 75.5%.
---
## 4. Complexity
### 4a. TypeScript (exact — ESLint core `complexity`)
Distribution over 2 085 source functions (specs/stories excluded):
**p50 1 · p75 2 · p90 3 · p95 5 · p99 12 · max 27.** 25 functions exceed CC 10 (1.2%).
Function length over 1 149 functions: **p50 3 · p75 7 · p90 13 · p95 20 · p99 34 ·
max 143.** Only 2 functions exceed 75 lines.
| Module | Fns | max CC | p90 CC | CC>10 | max fn lines | fn>75 |
| -------------------------------------------------------- | --: | -----: | -----: | ----: | -----------: | ----: |
| libs/shared/upload | 90 | 27 | 4 | 1 | 109 | 1 |
| ssp/registratie | 361 | 23 | 4 | 7 | 143 | 1 |
| ssp/herregistratie | 144 | 19 | 3 | 4 | 31 | 0 |
| libs/shared/infrastructure | 32 | 19 | 6 | 1 | 33 | 0 |
| ssp/brief | 608 | 17 | 3 | 6 | 73 | 0 |
| bhp/behandeling | 85 | 16 | 5 | 3 | 28 | 0 |
| libs/beheer/domain | 22 | 11 | 7 | 1 | 24 | 0 |
| libs/beheer/infrastructure | 13 | 11 | 7 | 1 | 25 | 0 |
| libs/shared/ui | 285 | 11 | 3 | 1 | 37 | 0 |
| libs/beheer/ui | 100 | 10 | 1 | 0 | 8 | 0 |
| libs/shared/application | 61 | 7 | 3 | 0 | 30 | 0 |
| bhp/auth · ssp/auth · ssp/shell | 63 | 5 | 3 | 0 | 13 | 0 |
| libs/beheer/application | 42 | 4 | 3 | 0 | 15 | 0 |
| libs/shared/kernel | 18 | 4 | 4 | 0 | 13 | 0 |
| libs/shared/layout | 75 | 4 | 2 | 0 | 29 | 0 |
| ssp/showcase · ssp/root · bhp/root · libs/shared/testing | 86 | 3 | 1 | 0 | 15 | 0 |
**Read BL-001 before filing any complexity ticket.** 23 of the 25 CC>10 functions are
reducers (9), `parse*` trust boundaries (10), or `validate*` (4) — all three are
mandated house idioms.
The 25, in full:
| CC | Function | Location | Kind |
| --: | ------------------------ | -------------------------------------------------------------------------------- | --------- |
| 27 | `reduceUpload` | libs/shared/src/upload/upload.machine.ts:131 | reducer |
| 23 | `validateStep` | apps/ssp/src/app/registratie/domain/registratie-wizard.machine.ts:117 | validate |
| 20 | `reduce` | apps/ssp/src/app/registratie/domain/registratie-wizard.machine.ts:321 | reducer |
| 19 | `validateStep` | apps/ssp/src/app/herregistratie/domain/intake.machine.ts:97 | validate |
| 19 | `parseAanvraagStatus` | apps/ssp/src/app/registratie/infrastructure/applications.adapter.ts:72 | parse |
| 19 | `fetch` | libs/shared/src/infrastructure/api-client.provider.ts:49 | **other** |
| 17 | `parseOrgTemplate` | apps/ssp/src/app/brief/infrastructure/brief.adapter.ts:331 | parse |
| 17 | `parseDuoLookup` | apps/ssp/src/app/registratie/infrastructure/duo.adapter.ts:46 | parse |
| 16 | `parseBeoordelingStatus` | apps/behandelportal/src/app/behandeling/infrastructure/beoordeling.adapter.ts:31 | parse |
| 16 | `parseStatus` | apps/ssp/src/app/brief/infrastructure/brief.adapter.ts:202 | parse |
| 16 | `validateAll` | apps/ssp/src/app/registratie/domain/registratie-wizard.machine.ts:166 | validate |
| 15 | `parseBeoordelingView` | apps/behandelportal/src/app/behandeling/infrastructure/beoordeling.adapter.ts:68 | parse |
| 15 | `validateAll` | apps/ssp/src/app/herregistratie/domain/intake.machine.ts:135 | validate |
| 14 | `reduce` | apps/behandelportal/src/app/behandeling/domain/besluit.machine.ts:70 | reducer |
| 14 | `reduce` | apps/ssp/src/app/brief/domain/org-template.machine.ts:57 | reducer |
| 14 | `reduce` | apps/ssp/src/app/herregistratie/domain/herregistratie.machine.ts:161 | reducer |
| 14 | `reduce` | apps/ssp/src/app/herregistratie/domain/intake.machine.ts:222 | reducer |
| 14 | `reduce` | apps/ssp/src/app/registratie/domain/change-request.machine.ts:57 | reducer |
| 13 | `reduce` | apps/ssp/src/app/brief/domain/brief.machine.ts:188 | reducer |
| 12 | `parsePassage` | apps/ssp/src/app/brief/infrastructure/brief.adapter.ts:235 | parse |
| 12 | `parseBrief` | apps/ssp/src/app/brief/infrastructure/brief.adapter.ts:277 | parse |
| 12 | `parseDashboardView` | apps/ssp/src/app/registratie/infrastructure/dashboard-view.adapter.ts:50 | parse |
| 11 | `reduce` | libs/beheer/src/domain/stamdata-editor.machine.ts:31 | reducer |
| 11 | `parseAuditEntries` | libs/beheer/src/infrastructure/audit.adapter.ts:21 | parse |
| 11 | `collect` | libs/shared/src/ui/rich-text-editor/rich-text-dom.ts:130 | **other** |
Functions over 75 lines — the entire population:
`createDraftSync` 143 lines (apps/ssp/src/app/registratie/application/draft-sync.ts:50),
`reduceUpload` 109 lines (libs/shared/src/upload/upload.machine.ts:131).
### 4b. C# (approximate)
| Module | Files | Σ file CC | max file CC | Methods | max method CC | p90 | CC>10 |
| ------------------ | ----: | --------: | ----------: | ------: | ------------: | --: | ----: |
| backend/Program.cs | 1 | **78** | **78** | 13 | 12 | 7 | 1 |
| backend/Data | 23 | 101 | 27 | 77 | 16 | 4 | 1 |
| backend/tests | 39 | 121 | 26 | 230 | 20 | 2 | 1 |
| backend/Domain | 18 | 71 | 21 | 36 | 8 | 6 | 0 |
| backend/Stamdata | 10 | 34 | 21 | 11 | 10 | 6 | 0 |
| backend/Zgw | 8 | 39 | 11 | 20 | 8 | 4 | 0 |
| backend/Contracts | 2 | 4 | 3 | 6 | 3 | 3 | 0 |
Method-length distribution (n=393): p50 9 · p90 22 · p99 90 · max 293.
Highest-CC files: `Program.cs` 78 · `Data/ApplicationStore.cs` 27 ·
`tests/OpenZaakZaakSourceTests.cs` 26 · `Domain/Letters/LetterHtml.cs` 21 ·
`Stamdata/StamdataTable.cs` 21 · `Data/BriefStore.cs` 17.
Methods over CC 10: `ToDto` (Data/BriefStore.cs:34, CC 16),
`LogBrief` (Program.cs:889, CC 12), and one 293-line test method
(`CreateZaak_posts_zaak_status_and_rol_and_maps_the_result_back`, CC 20).
---
## 5. Duplication (approximate — 6-line normalized window)
Repo-wide: **1 755 of 24 701 significant lines duplicated = 7.1%.**
| Module | Sig. lines | Duplicated | % |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------: | ---------: | ---------: |
| ssp/auth | 211 | 211 | **100.0%** |
| bhp/auth | 243 | 211 | **86.8%** |
| bhp/root | 90 | 68 | 75.6% |
| ssp/root | 122 | 68 | 55.7% |
| bhp/shell | 21 | 8 | 38.1% |
| ssp/herregistratie | 1 536 | 121 | 7.9% |
| ssp/brief | 5 015 | 393 | 7.8% |
| backend/tests | 2 538 | 197 | 7.8% |
| backend/Data | 1 068 | 82 | 7.7% |
| libs/shared/infrastructure | 510 | 36 | 7.1% |
| libs/beheer/domain | 250 | 17 | 6.8% |
| libs/beheer/ui | 509 | 34 | 6.7% |
| libs/beheer/application | 185 | 12 | 6.5% |
| bhp/behandeling | 954 | 55 | 5.8% |
| ssp/shell | 194 | 8 | 4.1% |
| ssp/registratie | 3 798 | 144 | 3.8% |
| libs/shared/ui | 2 604 | 58 | 2.2% |
| backend/Zgw | 327 | 6 | 1.8% |
| ssp/showcase | 487 | 8 | 1.6% |
| backend/Program.cs | 510 | 6 | 1.2% |
| libs/shared/application | 566 | 6 | 1.1% |
| libs/shared/layout | 869 | 6 | 0.7% |
| backend/Contracts, backend/Domain, backend/Stamdata, libs/shared/{domain,kernel,upload,testing,environments}, libs/beheer/{contracts,infrastructure}, e2e | 1 894 | 0 | 0.0% |
Top clone pairs:
| Windows | Pair |
| ------: | ------------------------------------------------------------------------------------------------ |
| 39 | `bhp/auth/application/session.store.ts``ssp/auth/application/session.store.ts` |
| 36 | `bhp/auth/auth.guard.spec.ts``ssp/auth/auth.guard.spec.ts` |
| 35 | `bhp/auth/ui/login-form/…``ssp/auth/ui/login-form/…` |
| 25 | `bhp/app.config.ts``ssp/app.config.ts` |
| 23 | `bhp/auth/ui/login.page.ts``ssp/auth/ui/login.page.ts` |
| 21 | `bhp/auth/auth.guard.ts``ssp/auth/auth.guard.ts` |
| 13 | `libs/shared/…/role.interceptor.spec.ts``…/subject.interceptor.spec.ts` |
| 11 | `brief/ui/letter-canvas.stories.ts``brief/ui/letter-composer.stories.ts` |
| 9 | `ssp/brief/ui/org-template.page.ts``libs/beheer/src/ui/stamdata.page.ts` |
| 8 | `bhp/behandeling/domain/besluit.machine.ts``ssp/registratie/domain/change-request.machine.ts` |
| 8 | `tests/OpenZaakZaakSourceTests.cs``tests/ZgwDivergenceTests.cs` |
| 7 | `bhp/…/besluit-form.component.ts``ssp/…/change-request-form.component.ts` |
| 7 | `ssp/…/intake-wizard.component.ts``ssp/…/registratie-wizard.component.ts` |
**The auth duplication is a deliberate decision, not an accident** — see BL-002 before
proposing to merge it.
---
## 6. Layering and coupling
`npm run dep:check`: **0 violations** (223 modules, 584 dependencies cruised) across 11
`severity: error` rules — `shared-no-features`, `beheer-no-features`, `shared-no-beheer`,
`<app>-no-other-app`, per-context `<app>-<context>-scope`, `domain-is-pure`,
`contracts-import-nothing`, `ui-not-infrastructure`, `apiclient-infrastructure-only`,
`no-testing-in-production`, `no-circular`.
Instability I = Ce/(Ca+Ce). Low I = stable base, high I = volatile leaf. Measured in the
ssp cruise (behandelportal's figures for `libs/*` differ only in Ca, same shape):
| Folder | N | Ca | Ce | I |
| --------------------------------------- | --: | --: | --: | -----: |
| apps/ssp/src/app/brief | 46 | 2 | 143 | 99% |
| apps/ssp/src/app/herregistratie | 18 | 4 | 80 | 95% |
| apps/ssp/src/app/showcase | 4 | 1 | 17 | 94% |
| apps/ssp/src/app/registratie | 67 | 19 | 181 | 91% |
| apps/behandelportal/src/app/behandeling | 24 | 2 | 62 | 97% |
| apps/behandelportal/src/app/auth | 11 | 4 | 25 | 86% |
| apps/ssp/src/app/auth | 9 | 5 | 24 | 83% |
| libs/beheer/src/ui | 5 | 3 | 27 | 90% |
| libs/beheer/src/application | 3 | 2 | 16 | 89% |
| libs/beheer/src/infrastructure | 3 | 3 | 11 | 79% |
| libs/shared/src/layout | 19 | 22 | 52 | 70% |
| libs/shared/src/ui | 68 | 128 | 111 | 46% |
| libs/shared/src/infrastructure | 19 | 43 | 25 | 37% |
| libs/shared/src/application | 18 | 55 | 30 | 35% |
| libs/shared/src/upload | 5 | 30 | 13 | 30% |
| libs/beheer/src/domain | 5 | 8 | 3 | 27% |
| libs/shared/src/testing | 4 | 17 | 2 | 11% |
| libs/shared/src/kernel | 9 | 71 | 4 | **5%** |
| libs/shared/src/domain | 3 | 11 | 0 | 0% |
| libs/beheer/src/contracts | 1 | 0 | 0 | 0% |
This is textbook: `kernel`/`domain` are the stable base (I ≤ 5%), feature contexts are
volatile leaves (I ≥ 83%), nothing depends on them. **The frontend dependency structure
is not a problem area** — do not spend tickets here.
**The backend has no equivalent enforcement at all.** `Domain/` purity (verified: zero
`Microsoft.EntityFrameworkCore` / `Microsoft.AspNetCore` imports) holds by convention and
code review only. See BL-006.
---
## 7. Pattern inventory — what already exists
Agents 03 (DDD/hexagonal) and 04 (CQRS-light) may only **extend** what is listed here.
They may not introduce either pattern into a module where it is absent.
### Backend
| Pattern | State |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ports with 2+ implementations | `IZaakSource` (`Data/LocalZaakSource.cs``Zgw/OpenZaakZaakSource.cs`), `IDocumentSource` (`Data/LocalDocumentSource.cs``Zgw/OpenZaakDocumentSource.cs`) — config-switched on `Zgw:Enabled` at `Program.cs:57-84`, ADR-0005 |
| Single-impl interface | `IIdentityProvider``StubIdentityProvider` |
| **Not behind any port** | 7 static, non-DI stores: `ApplicationStore`, `DocumentStore`, `BriefStore`, `OrgTemplateStore`, `FeatureFlagStore`, `AuthzAuditStore`, `IdempotencyStore`. No `AddDbContext`; each opens a short-lived context via `Db.Create()` under its own lock. Deliberate, documented in `Data/Db.cs` and `Program.cs:40-45` |
| Domain purity | `Domain/` is EF-free and ASP-free (verified). Rules are static classes of pure functions with co-located tests in `tests/Domain/`: `SubmissionRules`, `DocumentRules`, `IntakePolicy`, `BeoordelingRules`, `DiplomaRules`, `HerregistratieRule`, `LetterHtml`, `OrgTemplateRules`, `Authz`, `FeatureFlags`. `Domain/Applications/Aanvraag.cs` is a C# tagged union (`Concept`/`Submitted`/`Decided`) |
| CQRS-light | **Partial.** `Contracts/Dtos.cs` holds 65 records split by direction (`*Request` in, `*Dto`/`*Response` out). Read/write split exists as _comment banners_ inside a single **940-line `Program.cs`** carrying all 48 endpoint mappings. No handler types, no mediator, no `Features/` folders. Cross-cutting behaviour is factored into local helpers (`Submit`, `StamdataAdmin`, `CasesAdmin`, `Beoordelen`, `OrgAdmin`, `FlagsAdmin`) — authorization/idempotency wrappers, not handlers |
| Mapping | `Contracts/Mappers.cs` (`.ToDto()`, `.ToDetailDto()`), `Data/AanvraagMapper.cs` |
| ZGW anti-corruption layer | Fully built: `Zgw/{OpenZaakZaakSource,OpenZaakDocumentSource,ZgwHttpClient,ZgwTokenProvider,ZgwZaakMapper,ZgwOptions,ZgwDiagnosticHandler}.cs`, 5 test files. ADR-0005 |
### Frontend
| Pattern | Count | Notes |
| ------------------------------------- | ----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Command factories (write side) | 3 | `registratie/application/submit-change-request.ts`, `behandeling/application/submit-besluit.ts`, `registratie/application/draft-sync.ts` |
| Mutations living _inline in adapters_ | ~13 | `brief.adapter.ts` (save/submit/approve/reject/send/reset), `org-template.adapter.ts` (save/publish/rollback), `stamdata.adapter.ts` — they call `runSubmit` directly instead of going through a command factory |
| Infrastructure adapters (read side) | 20 | +1 outside an `infrastructure/` folder: `libs/shared/src/upload/upload.adapter.ts` |
| `parse*` trust boundaries | 30 | 24 in adapters, 6 in value objects / kernel |
| Application stores | 15 | all `providedIn: 'root'` |
| Elm-style machines | 9 | 8 under a `domain/` folder; outlier `libs/shared/src/upload/upload.machine.ts` |
| Explicit port | 1 | `SessionPort` + `SESSION_PORT` token (`libs/shared/src/application/session.port.ts`) |
| Config-seam tokens | 3 | `DEBUG_PANEL`, `HEADER_NAV_ITEMS`, `HEADER_ADMIN_LINKS` |
| `contracts/` DTO files | 4 | most adapters consume NSwag-generated types directly instead |
Shared application kit (`libs/shared/src/application/`): `remote-data.ts`, `store.ts`,
`submit.ts`, `action-state.ts`, `debounced-save.ts`, `history.ts`,
`machine-remote-data.ts`, `pending-saves.ts`.
### ADRs on record
`0001` BFF-lite + decision DTOs (Accepted) · `0002` user groups as actors, not bounded
contexts (was **Proposed** when measured; promoted to **Accepted** 2026-08-26 by ADR-C-005,
with §3's `Principal` omission recorded in the ADR as known debt) · `0003` CIBG Huisstijl
(Accepted) · `0004`
stamdata as code (Accepted) · `0005` OpenZaak behind the BFF — the explicit
ports-and-adapters ADR (Accepted) · `0006` test data through the production door
(Accepted).
---
## 8. Rankings, worst to best
**Spec reach (FE):** ssp/root · ssp/shell · bhp/root · bhp/shell · libs/shared/domain ·
libs/beheer/ui · libs/beheer/contracts (all 0%) → libs/shared/layout 18% →
bhp/behandeling 31% → libs/shared/ui 38% → ssp/brief 42% → … → libs/shared/kernel 100%.
**Line coverage (FE, of reached files):** ssp/auth · bhp/auth 42.9% →
libs/shared/upload 52.0% → libs/beheer/application 65.7% → … → ssp/showcase 100%.
**Branch coverage (BE):** Contracts 65.0% → Stamdata 71.7% → Data 75.5% → Domain 82.0% →
Program.cs 84.8% → Zgw 85.5%.
**Duplication:** ssp/auth 100% → bhp/auth 86.8% → bhp/root 75.6% → ssp/root 55.7% →
bhp/shell 38.1% → ssp/herregistratie 7.9% → … → 12 modules at 0%.
**Complexity (TS, CC>10 count):** ssp/registratie 7 → ssp/brief 6 → ssp/herregistratie 4
→ bhp/behandeling 3 → 5 modules with 1 → the rest 0.
**Complexity (C#, file CC):** Program.cs 78 → Data 101 across 23 files (max 27) →
Domain 71 (max 21) → Zgw 39 → Stamdata 34 → Contracts 4.
**Coupling:** nothing to rank — 0 violations, healthy instability gradient (§6).
---
## 9. Thresholds Phase 1 must use
Derived from the measured distributions above, not invented.
| Threshold | Value | Basis |
| ---------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------- |
| Cyclomatic complexity | **> 10** | fixed by the agent prompt; TS p99 is 12, so this catches the top ~1% |
| **Agent 01's `[N] lines` — TS function** | **> 40** | TS fn-length p99 is 34; only 4 functions exceed 50 |
| Agent 01's `[N] lines` — C# method | **> 60** | C# method p99 is 90, p90 is 22 |
| Agent 01's `[N] lines` — component/file | **> 400** | 6 TS files and 1 C# file exceed it |
| Nesting depth | **> 3** | fixed by the agent prompt |
| Duplication worth a ticket | **> 10% of a module's significant lines** | repo mean is 7.1% |
Largest files (the >400 population): `backend/Program.cs` 940 ·
`registratie/ui/registratie-wizard.component.ts` 645 · `showcase/concepts.page.ts` 496 ·
`brief/ui/letter-canvas.component.ts` 463 · `brief/infrastructure/brief.adapter.ts` 437 ·
`herregistratie/ui/intake-wizard.component.ts` 406.
---
## 10. Baseline observations (cite these)
Stable IDs. A Phase 1 finding must reference one of these or a metric row above.
**BL-001 — 23 of 25 TS functions over CC 10 are mandated house idioms.**
Reducers (9), `parse*` trust boundaries (10) and `validate*` (4) are switch/guard-dense
by construction: CLAUDE.md §3 requires a tagged-union reducer over booleans, and ADR-0001
requires a hand-written `parse*` at every wire boundary. High CC there is the design
working, not decaying. Only two CC>10 functions are outside the idiom:
`api-client.provider.ts:49 fetch` (CC 19) and `rich-text-dom.ts:130 collect` (CC 11).
_Any complexity ticket against a `reduce*`/`parse*`/`validate*` must argue why this case
differs — a bare CC number is not sufficient grounds._
**BL-002 — `ssp/auth` is 100% duplicated against `bhp/auth`, deliberately.**
211 of 211 significant lines; `session.store.ts`, `login-form.component.ts`, `login.page.ts`
and `auth.guard.ts` are near-identical. CLAUDE.md §1 and ADR-0002 state auth is _not_
shared because Zorgverlener and Medewerker are different `Principal` variants expected to
diverge. **The divergence has not happened yet.** This is legitimately in scope for
agent 06 (ADR conformance) as either an `ADR-fix` — the prediction has not held over two
phases of work — or a "still waiting" note. It is _not_ a straightforward
extract-to-shared refactor; that would contradict an accepted ADR.
> **Sharpened 2026-08-26 by agent 06 (ADR-C-004/005) — verified.** "The prediction has
> not held" is the wrong diagnosis. ADR-0002 §3's concrete deliverable, `Session →
Principal`, was **never built**: `grep -rn "Principal" apps libs` returns exactly one
> hit, a comment in `libs/shared/src/infrastructure/role.ts:8`, and no type. `diff -rq`
> over the two auth folders shows **zero** content differences — 9 of 11 files identical,
> the only delta being two extra files in behandelportal. `behandelportal`'s Behandelaar
> still carries a `bsn` and `login.page.ts:31` logs a backoffice user in through DigiD.
> The rule was not falsified, it was untested; the divergence that did occur went through
> an orthogonal side door (`medewerker.interceptor.ts`) that never touches `Session`.
> Amending ADR-0002 would ratify the omission. Use agent 06's sequencing instead.
**BL-003 — `Program.cs` is the single largest complexity concentration in the repo.**
940 lines, 48 endpoint mappings, file CC 78 (next-highest file: 27), read/write separated
only by comment banner. 97.4% line / 84.8% branch covered, so it is well-tested, not
fragile — this is a structure finding, not a correctness one. It is the one place where
agent 04's CQRS-light and agent 03's vertical-slice thinking both have real purchase,
_and_ the one place where "extend the existing pattern, don't introduce one" is hardest to
honour: there is no `Features/` folder to extend.
**BL-004 — 122 of 220 FE source files (55%) are never loaded by any Vitest run.**
Overwhelmingly `ui/` components, which CLAUDE.md §5 covers via 66 Storybook stories with
the a11y addon instead. Agent 02 must distinguish _"untested"_ from _"tested through
Storybook"_ before filing; the genuine gaps are non-`ui/` files with no spec —
`libs/shared/domain` (3 files, 0%), `libs/beheer/contracts`, and the app root/shell files.
> **Corrected 2026-08-26 by agent 02 — verified.** The two named "genuine gaps" are false
> positives. `libs/shared/src/domain` is 30 lines across 3 files — interfaces and type
> aliases plus a single string const — and `libs/beheer/src/contracts/stamdata.dto.ts` is
> 30 lines of DTO shapes. Neither contains an executable statement, so 0% is correct and
> unimprovable. Close both rows rather than ticketing them. Only the app root/shell files
> remain as candidates from this observation.
**BL-005 — backend branch coverage lags line coverage by 18 points** (97.6% vs 79.6%).
Weakest: `Contracts` 65.0%, `Stamdata` 71.7%, `Data` 75.5%. Line coverage is near-total,
so the missing tests are edge-case branches, not whole units.
**BL-006 — the backend has zero automated architecture enforcement.**
The frontend has 11 dependency-cruiser rules at `severity: error`, 0 violations, running
in CI. The backend has none: one assembly, no `Domain.csproj` boundary, no
NetArchTest/ArchUnitNET. `Domain/` purity currently holds by convention. Any agent-03
proposal that depends on the backend's layering staying clean should note that nothing
enforces it.
**BL-007 — the FE write side is inconsistently placed.**
3 command factories vs ~13 call sites invoking `runSubmit` directly inside adapters
(`brief.adapter.ts`, `org-template.adapter.ts`, `stamdata.adapter.ts`). CLAUDE.md §3 makes
the command factory the idiom. This is agent 04's clearest extend-an-existing-pattern
target — the pattern exists, it is just not applied uniformly.
> **Corrected 2026-08-26 by agent 04 (CQ-003/CQ-005) — verified.** The "~13 mutations"
> count was derived from the `runSubmit` helper name and is wrong: 5 of those call sites
> are **reads**, not writes. `libs/beheer/src/infrastructure/stamdata.adapter.ts` exposes
> only `list()` and `load()` and its own docstring says "Both endpoints are reads … There
> is no write method", yet both call `runSubmit`. Same at `brief.adapter.ts:56` and
> `org-template.adapter.ts:39,51`. `runSubmit` is the write fold — it mints the
> Idempotency-Key — so the name, not the code, produced the miscount. Conversely agent 04
> found **3 mutations this baseline missed entirely**: `ApplicationsStore.cancel`,
> `AdminCasesStore.delete`, `FeatureFlagStore.set` reach the raw `ApiClient` and never
> return a `Result`. Use agent 04's inventory, not this count.
**BL-008 — `coverageExclude` does not exclude the generated API client.**
`libs/shared/src/infrastructure/api-client.ts` is listed in `coverageExclude` in all four
`angular.json` test targets, yet appears in all four `lcov.info` files (987 lines at ~7%),
dragging the reported `libs/shared/infrastructure` figure from 94.7% down to 6.9%. This
file excludes it manually. Small, real, and cheap to fix.
**BL-009 — no coverage threshold is enforced anywhere.**
Neither `angular.json` nor CI sets a minimum; `npm run test:coverage` runs in CI and the
output is discarded. There is no ratchet, so no ticket can be verified as "improved
coverage" by CI alone — verify against the numbers in this file.
**BL-010 — `libs/shared/src/upload/` sits outside the layer convention.**
`upload.machine.ts` (CC 27, 109 lines — the highest in the repo) lives in its own
top-level folder rather than under `domain/`, and `upload.adapter.ts` outside
`infrastructure/`. It is carved out by name in the `apiclient-infrastructure-only`
dependency-cruiser rule, i.e. the exception is already encoded rather than resolved.
**BL-011 — the FE test suite is flaky under parallel load.**
`npm run test:coverage` failed on the first run with two `[vitest-pool] Timeout waiting
for worker to respond` errors in `libs/shared`, then passed 21/21 when re-run serially.
Not a code defect; relevant to any ticket whose acceptance is "CI green".
---
## 11. Reproducing this file
```bash
npm run test:coverage # if flaky, re-run the failing project alone
cd backend && dotnet test BigRegister.slnx --filter "Category!=Integration" \
--collect:"XPlat Code Coverage"
npx eslint apps libs --rule '{"complexity":["warn",0],"max-lines-per-function":["warn",{"max":0,"skipBlankLines":true,"skipComments":true}]}' -f json
node docs/project/refactor-backlog-setup/refactor-backlog/tools/baseline-scan.mjs
npm run dep:check
npx depcruise apps/ssp/src libs --config .dependency-cruiser.ssp.js --metrics --output-type metrics
npx depcruise apps/behandelportal/src libs --config .dependency-cruiser.behandelportal.js --metrics --output-type metrics
```
The `baseline-scan.mjs` figures are deterministic and must reproduce exactly. Coverage
figures move with the tests.