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>
47 KiB
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 (01–07). 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 libsreturns exactly one hit, a comment inlibs/shared/src/infrastructure/role.ts:8, and no type.diff -rqover 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 absnandlogin.page.ts:31logs 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 touchesSession. 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/domainis 30 lines across 3 files — interfaces and type aliases plus a single string const — andlibs/beheer/src/contracts/stamdata.dto.tsis 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
runSubmithelper name and is wrong: 5 of those call sites are reads, not writes.libs/beheer/src/infrastructure/stamdata.adapter.tsexposes onlylist()andload()and its own docstring says "Both endpoints are reads … There is no write method", yet both callrunSubmit. Same atbrief.adapter.ts:56andorg-template.adapter.ts:39,51.runSubmitis 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.setreach the rawApiClientand never return aResult. 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
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.