POST /registrations passed its Documents list straight to Submit, which calls
DocumentStore.Link on every digital documentId in it — and linking a document
blocks its owner from ever deleting it (DeleteOwned returns 409 Linked). That
path had no ForeignIds ownership check, so any authenticated citizen could
post another citizen's document id and permanently block them from deleting
their own diploma scan. POST /applications/{id}/submit, the endpoint actually
in use, has had that guard since it was written.
Deleted rather than guarded: the endpoint is dead. No frontend caller, and
the whole registratie flow goes through /applications/{id}/submit.
RegistratieRequest went with it, and so did SubmissionRules.RejectRegistratie
— reachable only from here, and contradicted by the live path, which treats a
handmatig diploma as "does not auto-approve" rather than a 422 rejection. Its
own message said as much while being returned as a rejection. That last part
is a judgement call beyond the ticket's wording; reverting the two
SubmissionRules hunks restores it in isolation.
Coverage moved rather than vanished: the problem+json shape assertion is now
on /change-requests (the other endpoint on the same Submit helper), and the
linked-delete 409 test goes through the real submit path.
swagger.json, the generated client and the behaviour spec regenerated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.7 KiB
RB-06 — delete the dead POST /registrations
Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-010 · 99-backlog.md RB-06
What was wrong
POST /registrations took a Documents list and passed it straight to Submit, which
calls DocumentStore.Link(...) on every digital documentId in it. Linking a document
blocks its owner from deleting it (DeleteOwned → 409 Linked).
There was no ForeignIds ownership check on that path. The real submit endpoint,
POST /applications/{id}/submit, has had one since it was written:
if (documentIds is { Count: > 0 } && DocumentStore.ForeignIds(documentIds, ctx.Zorgverlener().Bsn) is { Count: > 0 } foreignIds)
return Results.Problem(detail: $"Onbekend of niet-eigen document(en): …", statusCode: 400);
So any authenticated citizen could post another citizen's document id and permanently block them from deleting their own diploma scan.
Deleted rather than guarded
The ticket allowed either. Deleted, because the endpoint is dead: no frontend caller (the
generated client's registrations method was unreferenced), and the whole registratie flow
goes through POST /applications/{id}/submit.
| File | Change |
|---|---|
Program.cs |
endpoint deleted |
Contracts/Dtos.cs |
RegistratieRequest deleted (no other reference) |
Domain/Submissions/SubmissionRules.cs |
RejectRegistratie deleted — see below |
backend/swagger.json, api-client.ts |
regenerated (npm run gen:api) |
EndpointTests.cs, SubmissionRuleTests.cs |
retargeted, see below |
Why RejectRegistratie went with it
It was reachable only from this endpoint, and the live path deliberately contradicts
it. RejectRegistratie("handmatig") returned a 422 rejection; the modern submit does
"registratie" => (null, req.DiplomaHerkomst == "duo"),
— a manual diploma is not rejected, it simply does not auto-approve and goes to a behandelaar. Its own message even said so ("doorgestuurd voor handmatige beoordeling") while being returned as a rejection. Leaving it behind would have left an obsolete rule with a passing spec, which is exactly how it gets reintroduced.
This is the one judgement call in this ticket — the backlog row says "delete the dead
endpoint", not "delete the rule". Reverting just the SubmissionRules/SubmissionRuleTests
hunks restores it without touching anything else.
Test coverage that moved rather than vanished
Registration_with_manual_diploma_is_rejected_with_problem_detailswas the only test asserting theSubmithelper'sapplication/problem+jsonrejection shape. That assertion moved intoChange_request_with_bad_phone_is_rejected_with_problem_details—/change-requestsis the other endpoint on the same helper.User_delete_blocked_with_409_once_linked_to_submissioncoveredDocumentStore.Linkblocking a delete. Retargeted toPOST /applications/{id}/submit, i.e. the path that is actually in use.POST /registrationswas the only other caller ofLink.Registration_with_duo_diploma_succeedswas deleted outright —Change_request_with_valid_phone_succeedsis the same assertion on the same helper.
Verification
npm run ci. dotnet test: 249 passed, 1 failed — the pre-existing
OpenZaakIntegrationTests.Admin_cases_…, which needs a live container.