S-10b: the self-service diploma upload is now real. After submitting, the citizen picks a PDF and
uploads it; the portal base64-encodes it client-side → BFF → domain → ACL, which stores it in the
ZGW Documenten (DRC) API as an enkelvoudiginformatieobject and relates it to the zaak, then the WachtOpDocumenten wait completes and the case advances to beoordeling. Per §8.1 only the ACL talks to
ZGW.
ACL (OpenZaakGateway.StoreDocumentAsync + AclService.StoreDiplomaAsync + POST /documenten) reuses the existing gateway patterns (ZGW Bearer, buffered non-chunked body, no CRS — Documenten isn't geo). Unit-tested via the stub handler; an integration test stores a real document against live OpenZaak (verify-acl).
Transport: base64 JSON on every hop (portal encodes client-side) — I deviated from proposal #107's multipart to keep one contract shape and avoid IFormFile/antiforgery/multipart-client plumbing; fine at diploma size (ADR-0018 §Alternatives).
Infra:seed_catalogus.py seeds + publishes a "Diploma" informatieobjecttype and relates it to the zaaktype (while both concept); verify-domain injects its URL into the ACL. No new ZGW scopes (seed applicatie has heeft_alle_autorisaties).
e2e: uploads a real PDF (setInputFiles) after the openbaar INGEDIEND row confirms the zaak is open (so storage doesn't race the OpenZaak worker).
Scope boundary: the ZGW zaak is not set to a cancellation status on 30-day expiry — that's #106 (S-10c).
## What & why
S-10b: the self-service **diploma upload** is now real. After submitting, the citizen picks a PDF and
uploads it; the portal base64-encodes it client-side → BFF → domain → **ACL**, which stores it in the
ZGW **Documenten (DRC) API** as an `enkelvoudiginformatieobject` and relates it to the zaak, then the
`WachtOpDocumenten` wait completes and the case advances to beoordeling. Per §8.1 only the ACL talks to
ZGW.
Closes #103
Mechanism in **ADR-0018** (proposal #107). Builds on S-10a (#102). The zaak-close-on-expiry item is
carved to **#106 (S-10c)**.
## Definition of Done
- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation (red→green per layer).
- [x] Conventional Commits referencing the issue (`refs #103`).
- [x] CI green — all Gitea Actions jobs (pending on this PR).
- [x] `docker compose up` health unaffected (ACL boots on a placeholder informatieobjecttype URL; the real one is injected by verify-domain).
- [x] Docs updated (ADR-0018, demo-script, BACKLOG + S-10c).
- [x] ADR added (`docs/architecture/adr-0018-diploma-upload-via-acl-documenten.md`).
- [x] Demo note in `docs/demo-script.md`.
## Notes for reviewers
- **ACL** (`OpenZaakGateway.StoreDocumentAsync` + `AclService.StoreDiplomaAsync` + `POST /documenten`) reuses the existing gateway patterns (ZGW Bearer, buffered non-chunked body, **no CRS** — Documenten isn't geo). Unit-tested via the stub handler; an **integration test** stores a real document against live OpenZaak (verify-acl).
- **Transport:** base64 JSON on every hop (portal encodes client-side) — I deviated from proposal #107's multipart to keep one contract shape and avoid `IFormFile`/antiforgery/multipart-client plumbing; fine at diploma size (ADR-0018 §Alternatives).
- **Infra:** `seed_catalogus.py` seeds + publishes a "Diploma" `informatieobjecttype` and relates it to the zaaktype (while both concept); `verify-domain` injects its URL into the ACL. No new ZGW scopes (seed applicatie has `heeft_alle_autorisaties`).
- **e2e:** uploads a real PDF (`setInputFiles`) after the openbaar INGEDIEND row confirms the zaak is open (so storage doesn't race the OpenZaak worker).
- **Scope boundary:** the ZGW zaak is not set to a cancellation status on 30-day expiry — that's #106 (S-10c).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
RED: StoreDocumentAsync creates the informatieobject in the Documenten API (base64
inhoud, bestandsomvang, definitief) with a Bearer token and a buffered (non-chunked)
body and no CRS headers, then relates it to the zaak via zaakinformatieobjecten, and
surfaces an OpenZaak rejection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
IZaakGateway.StoreDocumentAsync creates an enkelvoudiginformatieobject (base64
inhoud, buffered non-chunked body, no CRS) and relates it via zaakinformatieobjecten;
AclService.StoreDiplomaAsync default-fills the ZGW document fields (informatieobjecttype,
bronorganisatie, taal nld, creatiedatum); POST /documenten exposes it. Adds the
InformatieobjecttypeUrl default (wired in a following infra commit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
seed_catalogus.py (OZ_PUBLISH) creates a "Diploma" informatieobjecttype, relates it
to the zaaktype (zaaktype-informatieobjecttypen, while both concept), publishes both,
and prints INFORMATIEOBJECTTYPE_URL. verify-domain captures it and recreates the ACL
with Acl__Defaults__InformatieobjecttypeUrl (placeholder default in compose otherwise).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RED: ProvideDocuments now carries the file bytes, stores them against the zaak via
IAclClient before completing the WachtOpDocumenten wait; owner-scoped; best-effort
when no zaak/process exists yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
IAclClient.StoreDiplomaAsync + AclHttpClient (base64 JSON to the ACL /documenten
endpoint). ProvideDocuments stores the uploaded bytes against the zaak (when opened)
before completing WachtOpDocumenten; the domain endpoint accepts the file base64-encoded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RED: the self-service documents endpoint takes the base64 file (+ fileName/contentType)
as JSON — the portal encodes client-side — with the bsn from the token, and forwards
all of it to the domain. IDomainClient.ProvideDocumentsAsync grows accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The self-service documents endpoint takes { contentBase64, fileName, contentType }
as JSON (bsn from the token) and forwards it via IDomainClient.ProvideDocumentsAsync.
Regenerates openapi.json + the Angular client (postSelfServiceRegistrationsIdDocuments
now takes a ProvideDocumentsRequest body).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RED: after submitting, the citizen picks a PDF and uploads it; the component base64-
encodes it client-side and posts { contentBase64, fileName, contentType } keyed by the
reference, then confirms. Replaces the S-10a stub button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the S-10a stub button with a labelled file input (accept application/pdf);
the component base64-encodes the chosen file client-side and posts it (with its name
and type) keyed by the reference, confirming on success and surfacing a retryable
failure. The upload button stays disabled until a file is chosen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ACL integration test: stores a real document against a published Diploma
informatieobjecttype and asserts it is persisted (bestandsnaam, informatieobjecttype,
bestandsomvang) and related to the zaak.
- e2e: the registration journey now selects a PDF and uploads it (setInputFiles)
before the behandelaar step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the ACL-fronted Documenten create+relate, the base64-JSON transport chosen
over proposal #107's multipart, the informatieobjecttype seeding, and the S-10c
(zaak-close) carve-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The behandelaar approval closes the zaak via OpenZaak. A related
informatieobject with indicatieGebruiksrecht left null makes OpenZaak
reject the close (400 indicatiegebruiksrecht-unset), surfacing as a 500
and failing the registration e2e flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records 'no usage-rights restrictions' explicitly so OpenZaak allows the
related zaak to be closed on behandelaar approval, fixing the verify-stack
e2e regression introduced by S-10b diploma upload.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ZGW Documenten API property is indicatieGebruiksrecht (camelCase); the
all-lowercase key was silently dropped on write (DRF camel-case mapping),
leaving the field null so the zaak still could not close. Correct the JSON
name on the DTO and both assertions to match the ZGW spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not
merged commit 0904df8db0 into main2026-07-21 12:15:35 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
S-10b: the self-service diploma upload is now real. After submitting, the citizen picks a PDF and
uploads it; the portal base64-encodes it client-side → BFF → domain → ACL, which stores it in the
ZGW Documenten (DRC) API as an
enkelvoudiginformatieobjectand relates it to the zaak, then theWachtOpDocumentenwait completes and the case advances to beoordeling. Per §8.1 only the ACL talks toZGW.
Closes #103
Mechanism in ADR-0018 (proposal #107). Builds on S-10a (#102). The zaak-close-on-expiry item is
carved to #106 (S-10c).
Definition of Done
refs #103).docker compose uphealth unaffected (ACL boots on a placeholder informatieobjecttype URL; the real one is injected by verify-domain).docs/architecture/adr-0018-diploma-upload-via-acl-documenten.md).docs/demo-script.md.Notes for reviewers
OpenZaakGateway.StoreDocumentAsync+AclService.StoreDiplomaAsync+POST /documenten) reuses the existing gateway patterns (ZGW Bearer, buffered non-chunked body, no CRS — Documenten isn't geo). Unit-tested via the stub handler; an integration test stores a real document against live OpenZaak (verify-acl).IFormFile/antiforgery/multipart-client plumbing; fine at diploma size (ADR-0018 §Alternatives).seed_catalogus.pyseeds + publishes a "Diploma"informatieobjecttypeand relates it to the zaaktype (while both concept);verify-domaininjects its URL into the ACL. No new ZGW scopes (seed applicatie hasheeft_alle_autorisaties).setInputFiles) after the openbaar INGEDIEND row confirms the zaak is open (so storage doesn't race the OpenZaak worker).🤖 Generated with Claude Code
The self-service documents endpoint takes { contentBase64, fileName, contentType } as JSON (bsn from the token) and forwards it via IDomainClient.ProvideDocumentsAsync. Regenerates openapi.json + the Angular client (postSelfServiceRegistrationsIdDocuments now takes a ProvideDocumentsRequest body). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>RED: after submitting, the citizen picks a PDF and uploads it; the component base64- encodes it client-side and posts { contentBase64, fileName, contentType } keyed by the reference, then confirms. Replaces the S-10a stub button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>