Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
T
2026-08-28 13:20:14 +02:00

438 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Scope: all findings from 00-baseline, 02-testability, 04-cqrs-light, 06-adr-conformance, 07-bio2-compliance — deduplicated, scored, CD-sequenced
## Status: complete
## Last updated: 2026-08-27
## Depends on: 00-baseline.md, 02-testability.md, 04-cqrs-light.md, 06-adr-conformance.md, 07-bio2-compliance.md
## ---
# 99 — Consolidated refactoring backlog
**47 findings in, 33 open tickets + 5 ADR-fixes + 1 shipped set out.** Everything below
traces to at least one `TE-`/`CQ-`/`ADR-C-`/`BIO-` finding and cites a baseline metric.
**HALT lifted 2026-08-27** — the operator approved the backlog and Phase 3 started.
**CD batch 1 (RB-01..RB-06) is implemented**, one commit per ticket on branch
`refactor/adr-c-006-shared-route-guards`, each with a note in `implementation/rb-0N.md`.
Batches 26 are untouched. The `Status` column below is the source of truth.
Two batch-1 findings had knock-on effects a later ticket must not re-derive:
- **RB-01's residual is RB-09's problem.** Both callers of the document-content endpoint
reach it as a plain browser navigation (`<a href>` / `previewUrl`), carrying no identity
header and passing through no interceptor, so `StubIdentityProvider` answers with the
seeded citizen. The links keep working only because one citizen owns every document in
the POC. That is BIO-002; RB-09 needs this endpoint to receive a real credential.
- **RB-06 also deleted `SubmissionRules.RejectRegistratie`**, which the row did not ask for.
It was reachable only from the deleted endpoint and contradicted by the live submit path.
Recorded as the ticket's one judgement call in `implementation/rb-06.md`.
`Pii.MaskTail` now lives in `Domain/People/Pii.cs` (moved out of `Program.cs` by RB-03) —
**RB-11 and any later redaction work should use it rather than hand-rolling a second copy.**
---
## Coverage of this backlog — read this before treating it as complete
Three of the seven Phase 1 agents were **deliberately skipped** by the operator
(reasons recorded in `_status.md`). This backlog therefore contains **no findings of the
following kinds**, and their absence is not evidence that none exist:
| Agent not run | Category of finding that is absent |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **01 — readability** | Function/file length, naming, nesting depth, comment quality, dead code, test readability. No ticket below is a "this is too long/unclear" ticket. |
| **03 — DDD/hexagonal** | Backend layering, vertical-slice structure, port extraction, module boundaries. The backend's structure is untouched except where CQRS-light reached it. |
| **05 — BDD** | Nothing material — the agent self-reduced to a structural note; `gen:behaviour-spec` already covers the intent. |
Concrete consequences, so nobody assumes these were considered and dismissed:
- **`createDraftSync` (143 lines, the longest function in the repo, §4a) is only partly
addressed.** RB-21 splits its read half out on CQRS grounds. Whether the remainder is
still too long was never assessed.
- **The other named length/complexity candidates have no owner:**
`api-client.provider.ts:49 fetch` (CC 19) and `rich-text-dom.ts:130 collect` (CC 11) —
the only two CC>10 functions outside the mandated idioms per **BL-001**; the 293-line
CC-20 test method in `OpenZaakZaakSourceTests.cs`; and the six files over 400 lines
(§9). RB-19 reorders `Program.cs` but does not shorten it.
- **Backend structure was assessed only through the CQRS-light lens.** **BL-003**'s
invitation (940 lines → `Features/`) is filed as out-of-mandate **OOM-A**, not a ticket.
**BL-010** (`libs/shared/upload/` outside the layer convention) is resolved only
incidentally, by RB-24, which came from the ADR agent rather than the structure agent.
- **Two baseline observations remain unowned by any agent:** **BL-005** (backend branch
coverage 18 points behind line coverage; `Contracts` 65.0%, `Stamdata` 71.7%, `Data`
75.5% — `backend/tests/` has no `Contracts/` folder at all) and **BL-009** (no coverage
ratchet anywhere). Neither is a testability _blocker_, so agent 02 correctly declined
both; they are coverage work with no seam to add, and no ticket below covers them.
---
## Already done — implemented and committed, do not re-file
Branch `refactor/adr-c-006-shared-route-guards`, five commits.
| Finding | Commit subject | Status | Residual |
| ------------- | ----------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **ADR-C-005** | `docs(adr-0002): accept, and record the unbuilt Principal union as debt` | **implemented** | ADR-0002 is now `Accepted`, so **RB-13 (ADR-C-004) now stands on a correct ADR** — that was the whole point of the gate. |
| **ADR-C-006** | `refactor(auth): share the actor-agnostic route guards (ADR-C-006)` | **implemented** | Auth duplication **211 → 151 lines**. §5's `ssp/auth 100% / bhp/auth 86.8%` rows and the `auth.guard*` clone pairs in the baseline are now **stale** — re-measure before citing them. Standing compliance criterion from agent 07: any future change to `authGuard`/`capabilityGuard` is an access-control change and must re-run the guard spec for both apps. |
| **CQ-004** | `fix(flags): surface a failed admin toggle instead of swallowing it` | **implemented** | **Half of its compliance criterion is unmet.** Agent 07 required "fix the FE error **and** the BE audit row together". The FE error shipped; `PUT /admin/flags/{key}` still writes **no** audit row. That half is carried by **RB-07**, and it is why **ADR-C-009** must not be signed off before RB-07 lands. |
| **TE-009** | `fix(stamdata): evaluate the profession validity window per call, not at type-load` | **implemented** | Also closed the latent dead-`ActiveOn`-branch bug. Not compliance-flagged. |
| **BL-008** | `build: make coverageExclude actually exclude the generated API client` | **implemented** | The reported `libs/shared/infrastructure` figure should now read ≈94.7%, not 6.9%. §3a is stale on that row. |
**Correction to the hand-off.** The brief listed "CQ-002/004 (`FeatureFlagStore.set`)" as
fixed. Only **CQ-004** was — `FeatureFlagStore.set` is the CQ-004 subject. **CQ-002**
(`ApplicationsStore.cancel`, `AdminCasesStore.delete`) is **verified still open**: both
still do `try { await this.adapter.x(id) } catch { this.state.set(before) }` with no
`runSubmit`, no `Result`, and no error channel. It is filed below as **RB-20**.
---
# The backlog
**How to read the CD batch column.** A batch is a _suggested ordering wave_, not a release
train. Every ticket in the table ships **alone**, on its own merge, without any other
ticket in its batch. Where a ticket genuinely cannot ship alone it was split into a chain
(RB-22/RB-23) — see "Tickets that were rejected and split". `Depends on` means _must be
deployed first_, not _must ship together_.
**Compliance column.** `SIGN-OFF` = requires compliance sign-off before merge, per rule 4.
Every ticket tracing to a `BIO-` finding, plus every row on agent 07's authoritative
16-row "Compliance review required" list, carries it — regardless of priority.
| ID | Module | Category | Description | Baseline metric improved | Effort | Risk | Priority | CD batch # | Depends on | Compliance | Status |
| --------- | -------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------ | -------- | -------- | ---------- | ---------- | ------------ | -------- |
| **RB-01** | backend/Program.cs + Data | security | Add an owner/capability check to `GET /uploads/{id}/content` and `/uploads/status`; 404 not 403 | §3c Data 75.5% branch vs 99.0% line (BL-005) | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-02** | backend/Program.cs + Data | privacy | Stop concatenating the BSN into `AuthzAudit.Resource`; assert on **values** in the test | §3c Data 75.5% branch (BL-005) | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-03** | backend/Contracts | privacy | `MaskTail(a.Owner, 3)` in `ToAdminSummaryDto` — both cross-owner lists inherit it | §3a bhp/behandeling 91.6%/81.5%; §7 Mapping row | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-04** | backend/Data | privacy | Mask the BSN used as `AuditEntry.Actor` on document audit rows (ownership column untouched) | §3c Data 99.0% line / 75.5% branch | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-05** | backend/Zgw | privacy | Drop the BSN-bearing query + body snippet from the `ZgwHttpClient` exception message | §3c Zgw 98.1%/85.5% (best backend branch) — a design gap, not a test gap | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-06** | backend/Program.cs | security | Delete the dead `POST /registrations` (no FE caller) — or add the `ForeignIds` guard | BL-003 (48 mappings in 940 lines, file CC 78) | S | Low | **P1** | 1 | — | **SIGN-OFF** | **done** |
| **RB-07** | backend/Program.cs | audit | Audit the **allow** path in all five authz gates + the 3 brief transitions and the besluit | §3c Program.cs 84.8% branch; BL-003 | SM | Med | **P1** | 2 | — | **SIGN-OFF** | **done** |
| **RB-08** | backend/Program.cs | security | Route `DELETE /admin/uploads/{id}` through `CasesAdmin`; delete the orphaned `IsAdmin` gate | BL-003; §7 CQRS-light wrappers row | S | Low | **P1** | 2 | RB-07 | **SIGN-OFF** | **done** |
| **RB-09** | backend/Domain + Program.cs | security | `IIdentityProvider` can express "no identity"; stub Development-only; fail fast in Production | §7 "Single-impl interface `IIdentityProvider`"; BL-006 | S | Med | **P1** | 2 | — | **SIGN-OFF** | **done** |
| **RB-10** | ssp/auth + bhp/auth + ssp/shell | testability | Extract `parseStoredSession` (×2 apps) + spec `redactProfile`; assert a stored BSN yields `''` | §3a auth 42.9%/46.2% (worst FE line, §8); file LH 2/LF 20, BRH 3/BRF 13 | S | Low | **P1** | 2 | — | **SIGN-OFF** | **done** |
| **RB-11** | ssp/brief + libs/shared/infra | security | Dev hatches out of prod on the 3 hand-written `fetch` paths; export their parse boundaries; fix the doc | §3b ssp/brief 42% reach (11/26, none `ui/`); §3a 68.8% branch | M | Med | **P1** | 2 | — | **SIGN-OFF** | **done** |
| **RB-12** | backend/tests (CI) | security gate | One test enumerating the route table; every route hits an authz wrapper or an explicit allow-list | BL-006 (zero backend architecture enforcement) | M | Low | **P1** | 3 | — | **SIGN-OFF** | **done** |
| **RB-13** | ssp/auth + bhp/auth | ADR execution | Land `Session → Principal`; `MedewerkerAdapter`; backoffice login stops being a DigiD/BSN form | BL-002 (211→151 dup after ADR-C-006; expected <40 after this) | M | Med | **P1** | 3 | RB-09 | **SIGN-OFF** | **done** |
| **RB-14** | repo (CI) | security gate | `dotnet list package --vulnerable --include-transitive` as a failing step | BL-006; §7 (the .NET tree is entirely unscanned today) | S | Low | P2 | 3 | — | **SIGN-OFF** | **done** |
| **RB-15** | backend/Program.cs | security | Wrap Swagger + the OpenAPI document in `if (app.Environment.IsDevelopment())` | BL-003; §3c Program.cs 97.4%/84.8% | S | Low | P2 | 3 | — | **SIGN-OFF** | **done** |
| **RB-16** | backend/Stamdata | input valid. | `DateOnly.TryParse` on `?peildatum=` → 400 instead of an unhandled 500 | §3c Stamdata 96.8% line / **71.7% branch** (BL-005) | S | Low | P2 | 3 | — | **SIGN-OFF** | **done** |
| **RB-17** | libs/shared/app + brief + beheer | CQRS-light | Split `runResult` (fold) from `runSubmit` (fold + idempotency mint); point the 5 reads at it | BL-007; §7 "read adapters 20 / mutations inline ~13" | S | Low | P2 | 3 | — | **SIGN-OFF** | **done** |
| **RB-18** | backend/Data | security | Key `IdempotencyStore` on `{SubjectId}:{idemKey}` | §7 stores "Not behind any port"; agent 02's Data note (no TTL, no reset) | S | Low | P2 | 3 | RB-17 | **SIGN-OFF** | **done** |
| **RB-19** | backend/Program.cs | structure | Reorder all 48 endpoints under read/write sub-banners; regroup admin-cases + org-template preview | BL-003 (940 lines, file CC 78 vs next-highest 27) | S | **High** | P2 | 4 | RB-12 | **SIGN-OFF** | **done** |
| **RB-20** | ssp/registratie | CQRS-light | `ApplicationsStore.cancel` / `AdminCasesStore.delete` through `runSubmit`; surface the error | BL-007; §7 "Command factories 3" | S | Low | P2 | 4 | — | **SIGN-OFF** | **done** |
| **RB-21** | ssp/registratie | CQRS-light | Extract the read half of `createDraftSync` into `application/find-concept.ts` | §4a `createDraftSync` 143 lines — longest fn in the repo; §9 (>40) | M | Med | P2 | 4 | — | — | **done** |
| **RB-22** | ssp/brief | CQRS-light | _(expand)_ `BriefStore.load()` tolerates a 404 by calling the existing `reset()` once | BL-003; §7 Backend CQRS-light row | S | Low | P2 | 4 | — | **SIGN-OFF** | **done** |
| **RB-23** | backend/Program.cs + Data | CQRS-light | _(contract)_ `GET /brief` 404s when absent; `GetOrCreate``Get` | BL-003; §7 Backend CQRS-light row | S | Med | P2 | 4 | RB-22 | **SIGN-OFF** | **done** |
| **RB-24** | libs/shared/upload | ADR conform. | Move `upload/` into `infrastructure`/`domain`/`application`; **delete** the depcruise carve-out | BL-010; §7 "+1 adapter outside `infrastructure/`", "8 of 9 machines in `domain/`"; §3b shared/domain 0% reach | M | Med | P2 | 5 | — | **SIGN-OFF** | **done** |
| **RB-25** | libs/shared/upload | testability | `UPLOAD_TRANSPORT` injection token (the `SESSION_PORT` shape) instead of `inject(KeepaliveTransport)` | §3a upload 52.0%/50.0%; §3b file unreached, non-`ui/` | S | Low | P2 | 5 | RB-24 | **SIGN-OFF** | **done** |
| **RB-26** | libs/shared/upload | testability | Move the accept/reject decision to `planFileSelection` in `upload.machine.ts` | §3a upload 52.0%/50.0%; §4a module max CC 27 | S | Low | P2 | 5 | RB-24 | **SIGN-OFF** | **done** |
| **RB-27** | libs/shared/upload | testability | Extract `uploadOutcome(status, responseText)` out of the XHR closure | file LH 5/64 (**7.8% line**), BRH 3/57 (**5.3% branch**) | SM | Low | P2 | 5 | RB-25 | **SIGN-OFF** | **done** |
| **RB-28** | libs/beheer + ssp/brief | testability | `BLOB_PRESENTER` token; the 3 commands' success paths become assertable | §3a beheer/application **40.5% branch — worst FE**; brief.store BRH 32/64 | SM | Low | P2 | 5 | — | **SIGN-OFF** | **done** |
| **RB-29** | backend/Domain | testability | Thread the existing `at` through `LetterHtml.ResolveAuto` instead of reading `UtcNow` | §3c Domain 82.0% branch; §4b `LetterHtml.cs` CC 21 | S | Low | P2 | 5 | — | — | **done** |
| **RB-30** | backend/Data + Domain | testability | Extract 5 brief guards into `Domain/Letters/BriefRules.cs`; add `tests/Domain/BriefRuleTests.cs` | §3c Data **75.5% branch** (BL-005); §4b `BriefStore.cs` CC 17, `ToDto` CC 16 | M | Med | P2 | 5 | — | **SIGN-OFF** | **done** |
| **RB-31** | 4 app contexts (specs only) | ADR conform. | Replace hand-rolled state literals with `given(reduce, initial)` replays in 4 machine specs | §7 Elm machines 9 (1 has a `*.testing.ts`); §3a herreg 67.8% / brief 68.8% branch | M | Low | P2 | 6 | — | — | open |
| **RB-32** | libs/shared/docs | ADR conform. | Add the missing `language-switcher` row to the CIBG gap register (9 markers vs 8 rows) | §2 libs/shared 86 files / 5 194 lines; §6 layout Ca 22 | S | Low | P3 | 6 | — | — | open |
| **RB-33** | libs/shared/testing | ADR conform. | Adopt `unwrapOk` at its one call site — **or delete it**; both satisfy ADR-0006 §3 | BL-004; §3a libs/shared/testing 3 files, 100% line | S | Low | P3 | 6 | — | — | **done** |
---
## Notes on the table
**Why P1 is not simply "everything".** Rule 2's P1 definition ("violates a correct ADR,
blocks testability, or is a BIO2 compliance risk") would catch nearly every finding, which
would make the score useless. It is applied as: **P1 = a control is broken, an accepted
ADR's decision is unexecuted, or a security-relevant guard has no test today.** A ticket
that is merely _flagged because it touches a control_ (TE-003/4/5/6/8, CQ-006, ADR-C-002)
is **P2 with mandatory sign-off** — the compliance risk is one the ticket could introduce,
not one that exists. That distinction is the whole reason rule 4 is orthogonal to rule 2.
**RB-01 and RB-02 sort above every structural ticket** regardless of effort. Both are live
production-shaped defects, independently verified: a BSN concatenated into the persisted
authz audit `Resource` (`Program.cs:674`) and an unauthorized document-content endpoint
(`GET /uploads/{documentId}/content`). Four documents claim the audit trail holds no PII
and the test cited as enforcing it (`AuthzAuditTests.cs:51-53`) asserts on **column
names**, so the BSN travels in a column called `Resource` that the regex cannot see — the
value-asserting test is part of RB-02's definition of done, not a follow-up.
**RB-11 ships the doc correction in the same diff as the code.** `?role=` and `?subject=`
are _not_ stripped from production builds on three hand-written `fetch` adapters, while
`docs/reference/roles-and-access.md:23` says "they do not exist in a production build".
Correcting the doc without the code, or the code without the doc, both leave the repo
lying about itself. `?subject=` additionally writes a **BSN into `sessionStorage`** in any
build, which is the specific thing `SessionStore`'s G1 comment promises never happens.
**RB-12 before RB-19, deliberately.** Agent 07 flags CQ-006 as needing the authz suites as
its safety net; agent 04 flags it as the prerequisite for OOM-A. RB-12's route-table test
is the check that "each moved endpoint kept its gate" is verified by CI rather than by a
reviewer's eye across a 900-line diff. RB-19 carries the only **High** risk in the table
for exactly that reason and must land alone, never mixed with a behaviour change.
**RB-07 gates ADR-C-009, not the other way round.** Agent 06's proposed four-part test for
runtime-editable config includes "writes are admin-capability-gated **and audited**".
Today they are gated and not audited. Signing the ADR amendment first would ratify a
control the code does not implement.
**RB-13's dependency on RB-09 is real, not stylistic.** Landing `Principal` on the
frontend alone closes ADR-C-004 and leaves BIO-002 wide open: a production behandelportal
build still resolves to the seeded **zorgverlener** — failing closed on backoffice
capabilities (correctly) but **open on every citizen-scoped endpoint** and holding
`CanRevealBigNummer`, because `drafter` is the no-header default. RB-09 makes "no
identity" representable at the interface; RB-13 is the FE half.
---
## Merges — what was deduplicated, and how confident each merge is
| Merged ticket | Findings folded in | Confidence | Reasoning |
| --------------- | ------------------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **RB-10** | TE-001 + BIO-017 | **Certain** | Agent 07 says outright: "this is TE-001 plus one assertion; it does not need its own ticket if TE-001 is scheduled". BIO-017's second half (`redactProfile` spec) is a five-line spec in the same PII-guard category, so it rides along. |
| **RB-11** | BIO-012 + TE-002 + BIO-006(a) + BIO-006(b) | **Certain** | Agent 07 instructs: "Fix all three in one touch of the file, or the next reviewer will re-open it." All four land in the same three `fetch` adapters plus `role.ts`/`subject.ts` plus one doc line. BIO-006(b) is the same doc edit as BIO-012's. |
| **RB-09** | BIO-001(a) + BIO-001(b) + BIO-002 | **Certain** | BIO-001's own remediation _is_ (a) fail-fast + (b) "give `Resolve` a way to say no identity (see BIO-002)". BIO-002's root cause is the same non-nullable `Resolve`. One change, one file pair. |
| **RB-17** | CQ-003 + CQ-005 | **Certain** | Agent 04: "Fix them in one ticket; they are listed separately only because the module scope requires it." One shared-file split, five call sites. |
| **RB-14/12** | BIO-016 split into (a) and (b) | **Certain** | Two unrelated CI changes of different size and different value; the rest of BIO-016's "Absent" list is genuinely a production gate and stays on the checklist. |
| **RB-08** | BIO-003, sequenced behind RB-07 | High | Routing through `CasesAdmin` gives BIO-003's missing audit row for free **once** RB-07 has moved auditing to the allow path. Shipping BIO-003 first would mean writing the audit call twice. It can ship standalone if RB-07 slips. |
| **RB-18** | BIO-018, sequenced behind RB-17 | High | Agent 07: "Sequence CQ-003 before BIO-018 so the scoping change lands on a smaller call set." Not a merge, an ordering constraint. |
| **RB-25/26/27** | TE-003/004/005, sequenced behind RB-24 | **Judgement call** | Agent 04 argued BL-010 must be resolved before anything is layered onto the upload folder, and RB-24 (ADR-C-002) is the ticket that resolves it. But the three seams are each independently shippable **today**, against the current paths. If RB-24 is deferred or rejected, unblock all three — the dependency is hygiene, not correctness. |
**Merges considered and rejected:**
- **BIO-008 / BIO-009 / BIO-010 kept as three tickets (RB-02/04/05).** They share a theme
("no BSN in any audit row, log line or persisted error field") and a shared acceptance
criterion (assert on **values**, e.g. no stored string matching `\d{9}`). They were not
merged because they sit in three modules with three different test suites, and BIO-010
is conditional on `Zgw:Enabled` (off by default) which gives it a different risk profile.
Three one-line fixes that each ship alone beat one cross-module sweep. **If a reviewer
prefers one ticket, merging them is defensible** — this is the least settled call here.
- **CQ-002 not merged into BIO-007 (RB-07).** They are the two halves of the same
admin-mutation-observability gap, but one is FE error surfacing and the other is BE
auditing. Agent 07 asked only that they "ship aware of each other". Cross-referenced,
not merged.
- **`SessionStore` not merged across the TE-001 / residual-auth-duplication overlap.**
Both touch `session.store.ts`, but agent 06 is explicit that merging the two apps'
session stores now would cement a citizen DigiD/BSN login as the backoffice's login —
the exact outcome ADR-0002 §3 exists to prevent. RB-10 lands the same seam **twice**, on
purpose. The duplication question reopens only after RB-13, on re-measurement.
- **ADR-C-004 not merged into BIO-002.** Split into RB-09 (BE, S) → RB-13 (FE, M) instead,
because a single ticket spanning both would not be independently deployable.
---
## Tickets that were rejected and split (rule 3)
**CQ-007 → RB-22 then RB-23.** As filed, CQ-007 is the one finding agent 04 marked
"**no** — FE+BE together": the FE must handle a 404 that the BE does not yet return.
Shipping it as one ticket is a coordinated release. Split into the standard
expand/contract pair:
1. **RB-22 (expand, FE).** `BriefStore.load()` tolerates a 404 by calling the existing
`reset()` command once. Deploys against today's backend as a **no-op** — the BE never
404s, so the branch is dead on arrival and provably safe.
2. **RB-23 (contract, BE).** `GET /brief` returns 404 when no brief exists;
`BriefStore.GetOrCreate` splits into `Get` + the already-existing `ResetAndCreate`.
Deploys only once RB-22 is live.
Agent 07 rejected CQ-007's documentation-only alternative outright: "a non-idempotent GET
must be visible in the code, not only in a ticket". That alternative is therefore **not**
on the table.
**No other ticket failed the single-deploy test.** TE-001 lands in two apps but in one
merge; RB-24 touches 30 dependents but is one atomic move; RB-19 is a 900-line diff but
zero-semantic-change.
---
# ADR-fix tickets — architect approval required before any dependent code ticket
None of these five is a code change. All five change what the repo's architecture
documents _claim_. **Three of them require a matching CLAUDE.md correction in the same
diff** (CLAUDE.md's own precedence rule: "the docs win — update this file").
| ID | ADR | What the amendment does | Gates / blocks | CLAUDE.md edit? | Effort | Compliance | Status |
| ------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------ | ------------ | -------- |
| **ADR-C-001** | 0001 | Rewrite the worked example against the real backend; drop the 2 discharged out-of-scope bullets (every path it names no longer exists) | nothing | no | S | — | **done** |
| **ADR-C-003** | 0001 | State that the generated client **is** the wire contract post-codegen; scope `contracts/` to codegen gaps | any ticket deleting the 4 surviving `contracts/*.dto.ts`, or adding a hand-written DTO for a generated endpoint. **No open ticket below is blocked today** — recorded so a future one is. | **yes (§4)** | S | — | **done** |
| **ADR-C-007** | 0003 | Repoint 5 WP-67-stale paths; replace the **factually false** `app-alert` hand-rolled example (it wraps vendored `.feedback` classes) | nothing | **yes (§2)** | S | — | **done** |
| **ADR-C-009** | 0004 | Generalise "the org-templates exception" into a stated four-part test; list both passing surfaces | **RB-07.** Clause (4) is "writes are admin-capability-gated **and** audited". Today they are gated and _not_ audited — sign this before RB-07 and the ADR ratifies a control the code does not implement. | **yes (§4)** | S | **SIGN-OFF** | **done** |
| **ADR-C-005** | 0002 | _(already landed — see "Already done")_ | was the gate on RB-13; now cleared | — | — | — | **done** |
**No ADR-fix is proposed against ADR-0002 §3's non-sharing rule.** Agent 06 considered it
as instructed and rejected it with evidence: `grep -rn "Principal" apps libs` returns one
comment and no type, so the rule was never _tested_, only _unexecuted_. Amending it now
would ratify the omission rather than the evidence. The correct sequence is
ADR-C-005 (done) → **RB-13****re-measure BL-002**; agent 06's expectation is that the
residual duplication drops from 151 lines to under 40 on its own. If RB-13 is still
unstarted at the next backlog cycle, _that_ is when the ADR-fix conversation becomes
legitimate.
---
# Production gates — a release checklist, not tickets
These are **correct for a POC** and must be true before the system holds real BSNs. They
are deliberately kept out of the ticket table: they are acceptance criteria for a release
that does not exist yet (there is no production build artifact at all — **BIO-020**), not
work that can be merged and deployed this week. Where a _part_ of a production-gate
finding was shippable now, that part was pulled out as a ticket and is named below.
**Identity and access (9.1, 9.2, 9.4)**
- [ ] Replace `StubIdentityProvider` with verified DigiD / employee-SSO claims. `X-Role`,
`X-Subject`, `X-Medewerker`, `X-Rollen`, `X-Admin` removed as **inputs**, not ignored. — BIO-001
- [ ] Verify by building both apps `--configuration production` that the backoffice cannot
act as a citizen. — BIO-002 _(the interface half is **RB-09**; the FE half is **RB-13**)_
- [ ] Row-level scoping on every read returning person data; acceptance = a second seeded
citizen cannot see the first's dashboard, notes, BRP address or diplomas. — BIO-013
- [ ] The PII-reveal capability comes from the app overlay, not the coarse role, and is
**not held by the default role**. — BIO-006 _(the `X-Step-Up` literal is in **RB-11**)_
- [ ] Real step-up: a server-verified assurance/recency attribute no client can satisfy
with a constant. — BIO-006
**Cryptography (8.24)**
- [ ] Encryption at rest with documented key custody and rotation. — BIO-014
**Prerequisite: RB-02/04/05 first**, so the BSN is not in three places that do not
need it before deciding what must be encrypted.
- [ ] Document bytes move to encrypted object storage keyed by `DocumentId`. — BIO-014
- [ ] TLS everywhere: `UseHttpsRedirection` + HSTS at the edge. — BIO-015
- [ ] Security response headers (`nosniff`, CSP, `Referrer-Policy`) and a real
`AllowedHosts`. — BIO-015 _(the Swagger gate is **RB-15**)_
**Logging, monitoring and retention (8.15, 8.16)**
- [ ] Audit retention, integrity and access defined — how long, append-only, and who may
read `/beheer/audit` (it reuses `cases:manage`, which `Program.cs:565` already flags
as a placeholder for a dedicated `audit:read`).
- [ ] Log shipping and alerting — the audit trail is a SQLite table with no export path.
- [ ] _(Covered by tickets: allow-path auditing = **RB-07**; no BSN in any audit row, log
line or persisted error field = **RB-02/04/05**.)_
**Data protection (5.12, 5.13)**
- [ ] A DPIA covering BSN, uploaded identity documents and the register, with lawful basis
and retention schedule. Nothing in the repo covers this.
- [ ] Deletion / retention policy for uploaded documents and the audit trail.
- [ ] _(Covered: data minimisation on list endpoints = **RB-03**.)_
**Secure development (8.25, 8.28, 8.29)**
- [ ] Secret scanning in CI (prevention — nothing is committed today, verified). — BIO-016
- [ ] Backend architecture enforcement (NetArchTest/ArchUnitNET) so `Domain/` purity, ZGW
containment (ADR-0005) and "authorization lives in `Authz`" are CI- rather than
review-maintained. — BL-006
- [ ] A coverage ratchet, so a security fix can be verified as not regressed by CI. — BL-009
- [ ] Penetration test / DAST, with BIO-004's object-level authorization and BIO-005's
document linking as named cases.
- [ ] _(Covered: backend dependency scanning = **RB-14**; the authorization regression gate
= **RB-12**.)_
**Change control (8.32)**
- [ ] A production build and deployment artifact exists, separate from the demo compose
file, and its release checklist references this list. — BIO-020
- [ ] Verify **by build, not by reading**: in a production bundle `?role=`, `?subject=`,
`?scenario=`, `?rollen=` and the `⚙ state` panel are all inert — including on the
three hand-written `fetch` paths. — BIO-012 _(the code fix is **RB-11**; this box is
the build-time proof)_
---
# Verified clean — do not "fix"
Each of these was read and judged correct by the agent named. Re-checking them is wasted
effort; "simplifying" them is a regression.
**Security and access control** (agent 07, verified endpoint by endpoint)
- `AccessStore.can()` deny-by-default + `whenReady()` — the pair exists so the guard cannot
read `can()` mid-load and deny an entitled user.
- `capabilityGuard`'s "UX pre-gate, the backend re-enforces" claim — verified true for all
six admin surfaces; every capability the guard checks has a server-side twin.
- `Authz.CanBeoordelen`'s caller-kind derivation — the one capability a forged `X-Role`
cannot reach, and the reason BIO-002 fails _closed_ in that direction.
- The four-eyes rule in `Authz.CanActOn`, Forbidden-before-Conflict ordering.
- The `isDevMode()` gate on the debug panel and on the interceptor chain (the _interceptor_
chain is correctly gated — RB-11 is about the three adapters that bypass it).
- The ZGW client secret never reaching the browser; the notification webhook failing closed
on an unset secret; `ZgwDiagnosticHandler` logging no bodies and being opt-in.
- The upload content-type allow-list enforced **server-side** — which is also why
`nosniff` is a checklist item and not a finding.
- Stamdata having no runtime write endpoint at all.
- `libs/shared/src/kernel/{bsn,pii}.ts` — the standard the rest should be measured against.
- No secrets committed; no `.db` file tracked (both verified by `git check-ignore`/`ls-files`).
**Architecture and structure**
- **ADR-0005 is fully conformed — zero findings** (agent 06). The ZGW anti-corruption layer
is the repo's worked example; the ADR even predicted its own remaining gap and the gap
stayed where predicted.
- **`bhp/behandeling` is the CQRS-light reference implementation** (agent 04). Query
adapters, command adapter and command factory in separate files, write-free read stores.
Do not "clean it up".
- **The FE dependency structure is not a problem area** (baseline §6): 0 violations across
11 `severity: error` rules, textbook instability gradient (`kernel` I=5%, contexts I≥83%).
Do not spend tickets here.
- `BigProfileStore` — the reference implementation of the read/write split (agent 04).
- The `ToDetailDto(now)` / `ToDto(now)` status projection — a real read-model derivation;
do not let a future ticket "simplify" it into a stored status column (agent 04).
- The 7 static backend stores and `[assembly: DisableTestParallelization]` — deliberate,
documented in `Data/Db.cs`, and explicitly _not_ challenged by agents 02, 04 or 07.
RB-30 works **because** the rules never needed the DbContext, not by redesigning stores.
**Baseline rows closed as false gaps** (agent 02, verified — do not ticket them)
- `libs/shared/domain` 0% reach / 3 files, and `libs/beheer/contracts` 0% reach / 1 file.
Both are pure type declarations with **zero executable statements**; 0% is correct and
unimprovable. BL-004 named both as "genuine gaps"; that part of BL-004 is superseded.
- 23 of the 25 CC>10 TS functions are reducers / `parse*` / `validate*` — mandated house
idioms (**BL-001**). A bare CC number is not grounds for a ticket against any of them.
- `createDraftSync` is **acquitted on testability** (explicit deps object, optional
injection, `enabled()` escape hatch, has a spec). RB-21 is a CQRS split, not a fix.
- `httpClientFetch`, `Contracts/Mappers.cs`, `submit-besluit.ts`, `breadcrumb-trail.ts`,
`route-focus.ts`, `AccessStore.can()` — all "missing test, not blocked test", or a seam
that costs more than it returns. Filing them would be volume, not quality.
---
# Out of mandate — recorded so a later phase does not read this file as a step toward them
- **OOM-A — `Program.cs``Features/` folders with handler types.** BL-003's most obvious
invitation, and out of mandate because §7 is explicit that the backend has "no handler
types, no mediator, no `Features/` folders" — there is no structure to extend, only one
to introduce. **RB-19 is a strict prerequisite** if it is ever taken: you cannot cut a
940-line file into vertical slices while five of its seven sections interleave
directions. Agent 03, which would have owned this, did not run.
- **OOM-B — read/write repository split in `backend/Data`.** Would introduce the pattern
where §7 records it absent, and collides with the documented static/no-DI design.
- **OOM-C — no read model, no event sourcing, and none proposed.**
- **OOM-D — BL-011: the FE suite is flaky under parallel load, and BL-009 means nothing
ratchets.** "CI green" alone does not verify any ticket in this backlog. Verify against
`00-baseline.md`'s numbers — **and note that §3a, §3b and §5 are already partly stale**
after the five shipped commits (auth duplication 211→151; `libs/shared/infrastructure`
coverage no longer dragged down by the generated client). **Re-run the baseline before
using it as the before-picture for any ticket below.**
---
## Provenance
| Source finding | Where it went |
| ------------------------------------------------------- | -------------------------------------------------------------------- |
| TE-001…008 | RB-10, RB-11, RB-25, RB-26, RB-27, RB-28, RB-29, RB-30 |
| TE-009 | **shipped** |
| CQ-001, 002, 003+005, 006, 007 | RB-21, RB-20, RB-17, RB-19, RB-22+RB-23 |
| CQ-004 | **shipped** (BE audit half outstanding → RB-07) |
| ADR-C-001, 003, 007, 009 | ADR-fix table |
| ADR-C-002, 004, 008, 010, 011 | RB-24, RB-13, RB-32, RB-31, RB-33 |
| ADR-C-005, 006 | **shipped** |
| BIO-001, 002 | RB-09 + checklist |
| BIO-003, 004, 005, 007, 008, 009, 010, 011, 018, 019 | RB-08, RB-01, RB-06, RB-07, RB-02, RB-04, RB-05, RB-03, RB-18, RB-16 |
| BIO-006 | RB-11 (a+b) + checklist (c) |
| BIO-012, 017 | RB-11, RB-10 |
| BIO-015, 016 | RB-15 + checklist; RB-14 + RB-12 + checklist |
| BIO-013, 014, 020 | checklist only |
| BL-008 | **shipped** |
| BL-005, BL-009, BL-011 | **unowned** — see "Coverage of this backlog" and OOM-D |
| BL-001, BL-002, BL-004 (partly), BL-006, BL-007, BL-010 | absorbed into the tickets/checklist above |