Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
T
eho 6372d452a4 Merge RB-28 — add BLOB_PRESENTER, unlock the blob-to-browser success paths
TE-006: StamdataStore.download(), BriefStore.previewLetter() and
OrgTemplateStore.proefbrief() each ended in raw DOM blob calls jsdom cannot
meaningfully execute, so their success paths were unassertable and
download()'s two-clause guard true-branch was permanently dark.
BLOB_PRESENTER mirrors the SESSION_PORT shape; all three commands go through
it. download()'s branch coverage goes from 40.5% to 67.6%, and
org-template.store.ts gets its first spec at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md
#	libs/shared/docs/behaviour-spec.mdx
2026-08-28 08:39:09 +02:00

46 KiB
Raw Blame History

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; GetOrCreateGet 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 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 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 open

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-13re-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.csFeatures/ 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