Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-05.md
T
ehoandClaude Opus 5 0298ecc506 fix(uploads): delete the dead POST /registrations (RB-06)
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>
2026-08-27 11:04:03 +02:00

2.3 KiB

RB-05 — drop the BSN-bearing query and body snippet from the ZGW failure message

Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-009 · 99-backlog.md RB-05

What was wrong

ZgwHttpClient.SendWithRetryAsync built its failure message as

$"ZGW {req.Method} {req.RequestUri} failed: {(int)res.StatusCode} {snippet}"

with snippet being up to 500 characters of the response body. That message is not transient: Program.cs's submit endpoint catches it and stores it as Aanvraag.ZgwError in SQLite, and logs it.

Two BSN paths into it:

  • the query string. ZGW filters travel as query parameters, and the citizen-scoped zaken list filters on rol__betrokkeneIdentificatie__natuurlijkPersoon__inpBsn=<BSN>.
  • the body snippet. OpenZaak's error responses echo the offending request, so a rejected POST /rollen (whose body carries BetrokkeneIdentificatie(aanvraag.Owner)) comes back with the BSN in it.

The two "returned null body" throws in GetAsync/PostAsync interpolated the same url.

What changed

File Change
Zgw/ZgwHttpClient.cs Redact(url) (path only) at all three sites; snippet → res.ReasonPhrase
ZgwDivergenceTests.cs new A_recorded_divergence_carries_no_response_body_and_no_query_string

Status + path is enough to route a failure to the right endpoint. The diagnostic detail that was lost already has a deliberate home: ZGW_DEBUG_HTTP=1 wires ZgwDiagnosticHandler, which logs the full url and request bytes — opt-in, dev-only, and not persisted.

The test

Fails the statustypen GET (the only call in that fixture whose url carries a query string) after the zaak POST succeeds, then asserts on the persisted ZgwError: no "stub failure" (the body snippet), no "?" (the query string), but still the path and the 503. Confirmed it fails without the fix — restoring the old interpolation turns it red on both counts.

Verification

dotnet format --verify-no-changes clean. dotnet test: 253 passed, 1 failed — the pre-existing OpenZaakIntegrationTests.Admin_cases_…, which needs a live container.