docs(architecture): ADR-0011 approval status flow + demo note (refs #75)
Some checks failed
CI / lint (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 54s
CI / unit (pull_request) Successful in 1m4s
CI / frontend (pull_request) Successful in 2m0s
CI / mutation (pull_request) Failing after 1m54s
CI / verify-stack (pull_request) Failing after 5m49s
Some checks failed
CI / lint (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 54s
CI / unit (pull_request) Successful in 1m4s
CI / frontend (pull_request) Successful in 2m0s
CI / mutation (pull_request) Failing after 1m54s
CI / verify-stack (pull_request) Failing after 5m49s
Records the two non-obvious decisions: the ACL resolves the zaaktype eindstatus (domain stays ZGW-ignorant), and the event-subscriber projects INGESCHREVEN from the notification alone (hoofdObject + any status-create), never reading OpenZaak. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
64
docs/architecture/adr-0011-approval-status-flow.md
Normal file
64
docs/architecture/adr-0011-approval-status-flow.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# ADR-0011: Approval sets the zaak eindstatus via the ACL and projects INGESCHREVEN from the notification alone
|
||||||
|
|
||||||
|
- **Status:** Accepted
|
||||||
|
- **Date:** 2026-07-13
|
||||||
|
- **Deciders:** Respellion engineering
|
||||||
|
- **Relates to:** S-09b (#75); split from S-09 (#10); builds on ADR-0001 (§8 loose coupling), ADR-0003 (ACL default-fill), ADR-0007 (OZ→NRC wiring), ADR-0008 (read projection), ADR-0009 (external-task worker)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The walking skeleton could submit a registration (INGEDIEND) and show it in the openbaar register,
|
||||||
|
but nothing could **approve** it. S-09b adds a behandelaar approval that must make the entry publicly
|
||||||
|
visible as a terminal status. There is no behandel-portal yet (S-12), so approval is triggered by a
|
||||||
|
**temporary admin endpoint** on the Domain Service.
|
||||||
|
|
||||||
|
Two decisions are non-obvious (§14) and cross service boundaries:
|
||||||
|
|
||||||
|
1. **Who resolves the ZGW statustype?** Approval means "set the zaak to its final status", but the
|
||||||
|
domain must stay ZGW-ignorant (§8.1 — only the ACL talks to ZGW) and does not know statustype URLs.
|
||||||
|
2. **How does the projection learn the new status?** The status is set in OpenZaak, which notifies over
|
||||||
|
NRC; the Event Subscriber projects it. But the subscriber **may not read OpenZaak** (§8.1), and an
|
||||||
|
NRC `status`/`create` notification's `resourceUrl` is the *status* resource, not the zaak, and does
|
||||||
|
not carry the statustype.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**Approval flows Domain → ACL → OpenZaak → NRC → Event Subscriber → projection, using only the
|
||||||
|
notification's own fields on the read side.**
|
||||||
|
|
||||||
|
- **Domain.** `Registration.Approve()` advances INGEDIEND → INGESCHREVEN (requires an opened zaak; a
|
||||||
|
repeat is a no-op). The `ApproveRegistration` use case calls the ACL to set the zaak status, then
|
||||||
|
advances the aggregate. A temporary `POST /registrations/{id}/approve` endpoint drives it.
|
||||||
|
- **ACL.** A new `POST /statussen` operation takes only the zaak URL. The ACL resolves the zaaktype's
|
||||||
|
**eindstatus** from the catalogus (`isEindstatus`, falling back to the highest `volgnummer`) and
|
||||||
|
POSTs a ZGW status against the zaak. The domain never names statustypen — the ACL owns the ZGW
|
||||||
|
translation (§8.1, ADR-0003).
|
||||||
|
- **Event Subscriber.** It binds the NRC `hoofdObject` (always the zaak URL) and keys the projection on
|
||||||
|
it, so a `zaken`/`status`/`create` notification updates the **same** row the zaak-create created,
|
||||||
|
flipping it to INGESCHREVEN. It takes **any** status-create as the approval — in the walking skeleton
|
||||||
|
the only status ever set after creation is the approval — so it never has to read OpenZaak to learn
|
||||||
|
the statustype. The ZGW `resource` is retained in the notification log (new column) so a rebuild
|
||||||
|
reproduces the right status.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- The domain↔ACL boundary stays clean: the domain hands over a zaak URL and says "approve"; ZGW
|
||||||
|
statustype knowledge lives only in the ACL.
|
||||||
|
- The projection remains rebuildable without OpenZaak (§8.1, ADR-0008): the log now records the ZGW
|
||||||
|
resource, which is all a rebuild needs to reproject the status.
|
||||||
|
- The openbaar register shows real lifecycle: INGEDIEND on submit, INGESCHREVEN on approval.
|
||||||
|
- **Walking-skeleton assumption:** "any status-create ⇒ INGESCHREVEN" holds only while approval is the
|
||||||
|
sole post-creation status transition. When more transitions arrive (beoordeling, afwijzing — S-12+),
|
||||||
|
the subscriber must distinguish statustypen. The honest options then are to carry the statustype
|
||||||
|
omschrijving in the notification `kenmerken`, or to have the ACL resolve it and re-notify — recorded
|
||||||
|
here so future-me revisits this rather than assuming it generalises.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Inject the approved statustype URL into the ACL as config** (like the zaaktype URL). Rejected:
|
||||||
|
couples ACL config to seed output and adds compose/run-domain-check plumbing; runtime eindstatus
|
||||||
|
discovery keeps the ACL self-contained for one extra ZGW GET per approval.
|
||||||
|
- **Have the Event Subscriber GET the status/statustype from OpenZaak** to map precisely. Rejected:
|
||||||
|
violates §8.1 (only the ACL talks to ZGW) and makes the projection depend on OpenZaak being up.
|
||||||
|
- **Record the derived status in the notification log** instead of the ZGW resource. Rejected: the log
|
||||||
|
should retain notification *facts*, not projection semantics; the mapping stays in the projector.
|
||||||
@@ -195,5 +195,35 @@ curl -fsS http://localhost:8140/openbaar/register | jq # same public-safe view
|
|||||||
# → [ { "id": "<zaak-uuid>", "status": "INGEDIEND" } ]
|
# → [ { "id": "<zaak-uuid>", "status": "INGEDIEND" } ]
|
||||||
```
|
```
|
||||||
|
|
||||||
> The register shows `INGEDIEND` entries today; the approval transition to a terminal status
|
> The register shows `INGEDIEND` on submit; approval flips it to `INGESCHREVEN` — see S-09b below.
|
||||||
> (e.g. `INGESCHREVEN`) lands with the approval flow (S-09b, #75).
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## S-09b — Approval flow (public visibility flips to INGESCHREVEN)
|
||||||
|
|
||||||
|
**Outcome:** a behandelaar approves a submitted registration via a temporary admin endpoint (no
|
||||||
|
behandel-portal yet — S-12). The approval sets the zaak's final status through the ACL, which flows
|
||||||
|
back to the projection over NRC, and the openbaar register then shows the entry as `INGESCHREVEN`.
|
||||||
|
|
||||||
|
**The path:** `POST /registrations/{id}/approve` (domain) → ACL sets the zaak eindstatus (ZGW
|
||||||
|
`/statussen`) → OpenZaak → NRC → Event Subscriber projects `INGESCHREVEN` → openbaar register.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Full stack up, then drive submit → public INGEDIEND → approve → public INGESCHREVEN:
|
||||||
|
make up
|
||||||
|
make verify-e2e
|
||||||
|
|
||||||
|
# 2. Or by hand: submit (as in S-09), note the reference, then approve it.
|
||||||
|
# The zaak is opened off the request path, so approve once GET shows a zaakUrl.
|
||||||
|
ref="<registration-reference-from-the-confirmation>"
|
||||||
|
curl -fsS http://localhost:8130/registrations/$ref | jq # domain (host port 8130): wait for .zaakUrl
|
||||||
|
curl -fsS -X POST http://localhost:8130/registrations/$ref/approve -i # → 204 No Content
|
||||||
|
|
||||||
|
# 3. The public register now shows the entry as approved.
|
||||||
|
curl -fsS http://localhost:8140/openbaar/register | jq
|
||||||
|
# → [ { "id": "<zaak-uuid>", "status": "INGESCHREVEN" } ]
|
||||||
|
```
|
||||||
|
|
||||||
|
> **End of walking skeleton** (S-09 + S-09b): submit → process → projection → public visibility, from
|
||||||
|
> INGEDIEND through approval to INGESCHREVEN. The subscriber takes any post-creation status-set as the
|
||||||
|
> approval (ADR-0011) — a walking-skeleton assumption that tightens when more transitions arrive (S-12+).
|
||||||
|
|||||||
Reference in New Issue
Block a user