From 424ceb604b514233ce72682c70edfa898ab7b355 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Thu, 27 Aug 2026 19:21:01 +0200 Subject: [PATCH] docs(backlog): CD batch 4 complete All six tickets RB-18 to RB-23 merged, one commit per ticket. Records the two incomplete tickets that the agents reported, RB-22's deliberate departure from the runResult idiom, and how RB-19 was verified as a pure reorder. Adds five dispatch lessons. The stale worktree base is now the rule at 11 of 13 agent-runs. A spend limit killed four agents mid-flight and a message resumed each one from its own transcript, so no work was redone. Co-Authored-By: Claude Opus 5 --- .../refactor-backlog/99-backlog.md | 70 +++++++++---------- .../refactor-backlog/_status.md | 43 +++++++++--- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md b/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md index b41a430..fe54364 100644 --- a/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md +++ b/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md @@ -100,41 +100,41 @@ deployed first_, not _must ship together_. 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 | S–M | 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** | **implemented** | -| **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** | open | -| **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** | open | -| **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** | open | -| **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**) | S–M | Low | P2 | 5 | RB-25 | **SIGN-OFF** | open | -| **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 | S–M | Low | P2 | 5 | — | **SIGN-OFF** | open | -| **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 | — | — | open | -| **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** | open | -| **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 | — | — | open | +| 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 | S–M | 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** | open | +| **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** | open | +| **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** | open | +| **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**) | S–M | Low | P2 | 5 | RB-25 | **SIGN-OFF** | open | +| **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 | S–M | Low | P2 | 5 | — | **SIGN-OFF** | open | +| **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 | — | — | open | +| **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** | open | +| **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 | — | — | open | --- diff --git a/docs/project/refactor-backlog-setup/refactor-backlog/_status.md b/docs/project/refactor-backlog-setup/refactor-backlog/_status.md index e7cb9f0..679cc2e 100644 --- a/docs/project/refactor-backlog-setup/refactor-backlog/_status.md +++ b/docs/project/refactor-backlog-setup/refactor-backlog/_status.md @@ -14,15 +14,15 @@ ## Phase 3 — implementation -| CD batch | Tickets | Status | Notes | -| -------- | ------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | RB-01, RB-02, RB-03, RB-04, RB-05, RB-06 | **complete** | Six commits on `refactor/adr-c-006-shared-route-guards`, one per ticket, each with `implementation/rb-0N.md`. `npm run ci` green. Every ticket left a test that was verified red without its fix. Carryover: RB-01's residual belongs to **RB-09** (the content endpoint is reached by a plain browser navigation with no identity header — BIO-002); `Pii.MaskTail` now lives in `Domain/People/Pii.cs`, **use it in RB-11** rather than hand-rolling a second masker; RB-06 additionally deleted `SubmissionRules.RejectRegistratie` (judgement call, recorded). | -| 2 | RB-07, RB-08, RB-09, RB-10, RB-11 | **complete** | All five merged; `npm run ci` green **on the fixed gate** (see below). **RB-07** unblocks signing **ADR-C-009** and closes CQ-004's outstanding half. **RB-09** makes Production throw at startup when no real `IIdentityProvider` exists — note an environment that is neither Development nor Production (e.g. `Staging`) still fails fast, but at `GetRequiredService`, with a worse message. **RB-10** landed `parseStoredSession` twice, once per app, deliberately — TE-001/BL-002 say extract-to-shared contradicts ADR-0002; **RB-13** differentiates them. **RB-11** corrected a factual error in BIO-012 (the proefbrief error mapping was inlined, not already a separate function) and left the step-up as a literal moved one layer up to its only caller — BIO-006(c) stays a production gate. **RB-01's residual is still open** and is not solved by RB-09: the upload-content link is a plain browser navigation with no credential. | -| 3 | RB-12, RB-13, RB-14, RB-15, RB-16, RB-17 | **complete** | All six merged; `npm run ci` green (14 steps — RB-14 added one — backend 260/260). **RB-12 rejected the ticket's binary framing:** of 47 routes only 16 use one of the five admin wrappers; of the remaining 31 only 10 are genuinely public, the other 21 are ownership-scoped inline (`ctx.Zorgverlener()`/`ctx.Caller()`) or use another mechanism. The allow-list therefore carries **a reason per route**, not a blanket "public" label. Known limitation: detection is `.Gate("XAdmin")` metadata declared at mapping time — **a declaration, not a derivation**, so it cannot catch a route that declares a gate it does not have. **This is RB-19's safety net; read `rb-12.md` before starting RB-19.** **RB-13** measured `ssp/auth` ↔ `bhp/auth` duplication at **32 lines each side, down from 168** (backlog expected <40); each app holds only its own `Principal` variant, which is ADR-C-004's own proposed resolution, and ADR-0002's "Known debt" section became an amendment. **RB-14** could not be built as written — `dotnet list package --vulnerable` exits 0 on a High advisory (verified), so a bare `- run:` would have been a gate that enforces nothing; `scripts/dotnet-audit.sh` matches the output instead and is shared by `ci.yml` and `ci-local.sh`. **RB-15** used a third environment name (`Staging`) in its test, since RB-09 makes Production fail to boot at all. | | -| 4 | RB-18..RB-23 | in progress | Split into three waves to keep the merge order honest, because three of the six tickets touch `Program.cs`. **Wave A (dispatched, parallel):** RB-18, RB-20, RB-21, RB-22 — no file overlap between them. **Wave B:** RB-23, which must merge after RB-22 (expand/contract pair: the FE must tolerate the 404 before the BE returns it). **Wave C:** RB-19 alone and last — it is the only **High**-risk ticket, it reorders all 48 endpoints in `Program.cs`, and landing it last means it reorders the final content instead of conflicting with RB-18's and RB-23's edits to the same file. RB-19 also needs RB-12's route-table test as its safety net; read `rb-12.md` first, including its stated limitation that detection is a declaration, not a derivation. | -| 5 | RB-24..RB-30 | not started | RB-25/26/27 all depend on RB-24. | -| 6 | RB-31, RB-32, RB-33 | not started | | -| ADR-fix | ADR-C-001, ADR-C-003, ADR-C-007, ADR-C-009 | **complete** | All four signed and landed by the architect on 2026-08-27, in one commit; doc-only, no code touched. Three carried the mandatory matching `CLAUDE.md` edit in the same diff (§4 twice, §2 once). **ADR-C-009's RB-07 gate was satisfied first** — all four clauses of its new test were verified against both `OrgTemplateStore` and `FeatureFlagStore` before signing, so the ADR does not ratify a control the code lacks. **Two findings were wrong and are corrected in the notes:** ADR-C-001 told us to keep an out-of-scope bullet reading "`SessionStore` is in-memory", which RB-10/RB-13 made false (the session now persists to `localStorage`; only multi-tab sync is still open), and ADR-C-007 flagged only the `.alert` half of ADR-0003's point 4 — its "header/side-nav use `.nav` + a local blue bar" clause is equally false (`site-header` composes the vendored `.titlebar`/`.logo__*`). ADR-C-007 also over-listed one path: `public/cibg-huisstijl/` never moved. ADR-C-003's open question was decided explicitly — **the 4 hand-written `contracts/*.dto.ts` stay**, because NSwag emits every property optional and flattens `RegistrationStatusDto` into five optional strings, which would make an illegal state representable (CLAUDE.md §3). Gates released: ADR-C-003 (contracts cleanup) and ADR-C-009 (a third runtime-editable surface). Still pending, untouched: **ADR-C-008 → RB-32** — 9 `CIBG-GAP` markers vs 8 register rows, missing row is `language-switcher`. | +| CD batch | Tickets | Status | Notes | +| -------- | ------------------------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | RB-01, RB-02, RB-03, RB-04, RB-05, RB-06 | **complete** | Six commits on `refactor/adr-c-006-shared-route-guards`, one per ticket, each with `implementation/rb-0N.md`. `npm run ci` green. Every ticket left a test that was verified red without its fix. Carryover: RB-01's residual belongs to **RB-09** (the content endpoint is reached by a plain browser navigation with no identity header — BIO-002); `Pii.MaskTail` now lives in `Domain/People/Pii.cs`, **use it in RB-11** rather than hand-rolling a second masker; RB-06 additionally deleted `SubmissionRules.RejectRegistratie` (judgement call, recorded). | +| 2 | RB-07, RB-08, RB-09, RB-10, RB-11 | **complete** | All five merged; `npm run ci` green **on the fixed gate** (see below). **RB-07** unblocks signing **ADR-C-009** and closes CQ-004's outstanding half. **RB-09** makes Production throw at startup when no real `IIdentityProvider` exists — note an environment that is neither Development nor Production (e.g. `Staging`) still fails fast, but at `GetRequiredService`, with a worse message. **RB-10** landed `parseStoredSession` twice, once per app, deliberately — TE-001/BL-002 say extract-to-shared contradicts ADR-0002; **RB-13** differentiates them. **RB-11** corrected a factual error in BIO-012 (the proefbrief error mapping was inlined, not already a separate function) and left the step-up as a literal moved one layer up to its only caller — BIO-006(c) stays a production gate. **RB-01's residual is still open** and is not solved by RB-09: the upload-content link is a plain browser navigation with no credential. | +| 3 | RB-12, RB-13, RB-14, RB-15, RB-16, RB-17 | **complete** | All six merged; `npm run ci` green (14 steps — RB-14 added one — backend 260/260). **RB-12 rejected the ticket's binary framing:** of 47 routes only 16 use one of the five admin wrappers; of the remaining 31 only 10 are genuinely public, the other 21 are ownership-scoped inline (`ctx.Zorgverlener()`/`ctx.Caller()`) or use another mechanism. The allow-list therefore carries **a reason per route**, not a blanket "public" label. Known limitation: detection is `.Gate("XAdmin")` metadata declared at mapping time — **a declaration, not a derivation**, so it cannot catch a route that declares a gate it does not have. **This is RB-19's safety net; read `rb-12.md` before starting RB-19.** **RB-13** measured `ssp/auth` ↔ `bhp/auth` duplication at **32 lines each side, down from 168** (backlog expected <40); each app holds only its own `Principal` variant, which is ADR-C-004's own proposed resolution, and ADR-0002's "Known debt" section became an amendment. **RB-14** could not be built as written — `dotnet list package --vulnerable` exits 0 on a High advisory (verified), so a bare `- run:` would have been a gate that enforces nothing; `scripts/dotnet-audit.sh` matches the output instead and is shared by `ci.yml` and `ci-local.sh`. **RB-15** used a third environment name (`Staging`) in its test, since RB-09 makes Production fail to boot at all. | | +| 4 | RB-18..RB-23 | **complete** | All six merged, one commit per ticket, each on its own merge. `npm run ci` green on the combined tree after every merge (14 steps, exit 0). Ran as three waves, because three of the six touch `Program.cs`: **A** = RB-18/20/21/22 in parallel (no file overlap), **B** = RB-23 after RB-22 (expand/contract), **C** = RB-19 alone and last, so it reordered final content. **Two tickets were incomplete, both reported rather than worked around.** RB-23 found `BriefStore.GetOrCreate` had a **second, unmentioned call site** — `GET /brief/preview` — so the split forced that endpoint to change too or the file would not compile; it got the same `Get` + 404 treatment. RB-18's real scope is **one** endpoint, not the nine BIO-018's stale line numbers implied: `Submit` has exactly one call site (`POST /change-requests`). **RB-22 deliberately left the `runResult` idiom** for `BriefAdapter.load()`: it hand-rolls try/catch to read the HTTP status, because `runResult` folds the error to a string and structurally cannot carry a 404. It still reuses the shared `problemDetail` mapper and models the outcome as the `BriefLoadFailure` union, not a sentinel string. Accepted — reviewed the diff before merging. Its once-only bound is stronger than the ticket asked: `recoverFromMissingBrief` never re-enters `load()`, so CQ-007's retry loop is absent, not merely capped. **RB-22 mispredicted one thing harmlessly:** it expected the regenerated client to parse a `ProblemDetails` 404, but `Results.NotFound()` declares no body so it throws a plain `SwaggerException` (matching the 17 other bare-404 endpoints). `isHttpNotFound` reads only `.status`, so it tolerated both — the pair held because the FE half was written defensively. **RB-19 verification, recorded because RB-12's test cannot do it:** RB-12 proves a `.Gate(...)` marker is present, not that it matches the wrapper the handler calls (its own stated declaration-vs-derivation limit). Checked centrally instead — the sorted list of all 47 route strings is identical before and after, **and so is every (route, `.Gate` marker, wrapper actually called in the handler) triple**, with zero gate/handler mismatches. `gen:api` produced an ordering-only diff in `swagger.json` + `api-client.ts` (only the two moved _and documented_ endpoints changed position; the other three moves are `.ExcludeFromDescription()`), committed rather than left to fail the drift job. | +| 5 | RB-24..RB-30 | not started | RB-25/26/27 all depend on RB-24. | +| 6 | RB-31, RB-32, RB-33 | not started | | +| ADR-fix | ADR-C-001, ADR-C-003, ADR-C-007, ADR-C-009 | **complete** | All four signed and landed by the architect on 2026-08-27, in one commit; doc-only, no code touched. Three carried the mandatory matching `CLAUDE.md` edit in the same diff (§4 twice, §2 once). **ADR-C-009's RB-07 gate was satisfied first** — all four clauses of its new test were verified against both `OrgTemplateStore` and `FeatureFlagStore` before signing, so the ADR does not ratify a control the code lacks. **Two findings were wrong and are corrected in the notes:** ADR-C-001 told us to keep an out-of-scope bullet reading "`SessionStore` is in-memory", which RB-10/RB-13 made false (the session now persists to `localStorage`; only multi-tab sync is still open), and ADR-C-007 flagged only the `.alert` half of ADR-0003's point 4 — its "header/side-nav use `.nav` + a local blue bar" clause is equally false (`site-header` composes the vendored `.titlebar`/`.logo__*`). ADR-C-007 also over-listed one path: `public/cibg-huisstijl/` never moved. ADR-C-003's open question was decided explicitly — **the 4 hand-written `contracts/*.dto.ts` stay**, because NSwag emits every property optional and flattens `RegistrationStatusDto` into five optional strings, which would make an illegal state representable (CLAUDE.md §3). Gates released: ADR-C-003 (contracts cleanup) and ADR-C-009 (a third runtime-editable surface). Still pending, untouched: **ADR-C-008 → RB-32** — 9 `CIBG-GAP` markers vs 8 register rows, missing row is `language-switcher`. | **Standing caveat for every batch:** `dotnet test` reports one failure, `OpenZaakIntegrationTests.Admin_cases_returns_the_seeded_zaak_mapped_through_real_HTTP_and_JWT`, @@ -76,6 +76,31 @@ least one of these. Put all of it in the prompt. 6. **Agent worktrees live inside the repo**, so `prettier --check .` walks into them — fixed by ignoring `.claude/worktrees/` in both `.prettierignore` and `.gitignore`. +7. **The stale base is now the rule, not the exception.** Batch 4 ran six agents; **five were + handed a bad base**, three of them the same unrelated `ae7781e` lineage. Across batches 2-4 that + is **11 of 13 agent-runs**. Every one self-corrected at step zero. Keep the named-expected-commit + check as the first instruction in every prompt — it is the highest-value line in there. Give the + _current_ HEAD, not the batch's starting commit, when tickets merge sequentially. +8. **A spend limit can kill every agent mid-flight; resume, do not restart.** All four wave-A agents + died on an org monthly-spend 429, three of them at the final CI re-run. Their worktrees kept the + uncommitted work intact. Sending each agent a message resumed it from its own transcript and it + finished from exactly where it stopped — nothing was redone. Check `git -C status` + before assuming work is lost. +9. **The `99-backlog.md` conflict is mechanical and has a fixed recipe.** It fired on three of five + merges. Cause: the central prettier pass reflows the table's column widths, so the whole table + conflicts even though the two sides differ in only one or two status cells. Recipe: take HEAD's + table, flip the incoming ticket's cell, re-run prettier. Verify by parsing both sides cell-by-cell + and printing only the differing cells before discarding either side — do not eyeball a 33-row + table. A ticket's cell reads `**done**` once merged (matching RB-01..RB-17), not `implemented`. +10. **For a zero-semantic-change commit, ask for evidence CI cannot give.** RB-19's diff is 181 lines + in `Program.cs` and no test can prove it changed nothing. The sorted-route-list diff, plus the + route/gate/handler-triple comparison, is what actually made it reviewable. Ask for it in the + prompt and re-run it centrally before merging. +11. **`gen:api` is not always a no-op on a pure reorder.** OpenAPI operation order follows mapping + order, so a reorder legitimately changes `swagger.json` and `api-client.ts`. Tell the agent to + prove the diff is ordering-only (sort every line of both versions, diff, expect empty) and to + commit the regenerated pair, or CI's drift job fails on a correct change. + **Telling agents to report a ticket as wrong pays off.** Three did: BIO-012 was factually wrong about the proefbrief error mapping (RB-11), RB-12's wrapper/public binary did not fit the code, and RB-14 as worded would have shipped a non-gate. None of the three would have been caught by a review