All checks were successful
## What & why Before this change the self-service confirmation and the openbaar register showed **different** identifiers, so a citizen could not look their registration back up (#78). Now both surface the same **reference**: - **domain → ACL (write):** the domain `registrationId` is set as the zaak's `identificatie` on `POST /zaken`. - **event-subscriber → ACL (read):** the subscriber reads the zaak's `identificatie` back through the ACL (§8.1 — only the ACL talks to ZGW) via a new `POST /zaken/reference`, and stores it on the projection row **and** the `processed_notifications` replay log. - **BFF + openbaar:** the public view exposes `id/status/reference` (never bsn/naam) and searches by id or reference; the register's "Referentie" column shows the reference. Storing the reference in the replay log keeps ADR-0008's **rebuild-is-log-only** invariant intact — `/admin/rebuild` reproduces the reference without re-reading the ACL. Decision recorded in **ADR-0012**. ## Definition of Done - [x] Linked issue: #78 - [x] Tests written first; red → green per layer - [x] Unit + acceptance green (`make unit`): domain 49, acl 27, bff 20, event-subscriber 19, acceptance 7 - [x] Frontend lint + test green (`nx run-many -t lint test`) - [x] Mutation ≥ break(90): acl 100%, event-subscriber 100%, bff 100%, domain 98.41% (pre-existing FlowableWorkflowClient baseline, untouched) - [x] e2e extended: confirmation reference == register reference - [x] openapi.json + api-client regenerated (drift guard green) - [x] ADR-0012 added; demo-script note appended - [x] `Acl__BaseUrl` wired for the subscriber in compose closes #78 Reviewed-on: #79
251 lines
12 KiB
Markdown
251 lines
12 KiB
Markdown
# Demo script
|
|
|
|
A running log of demoable outcomes, one section per slice. Each entry is a short,
|
|
copy-pasteable walkthrough against a local `make up` stack.
|
|
|
|
---
|
|
|
|
## S-08d — Walking skeleton complete: browser → submit, end-to-end
|
|
|
|
**Outcome:** the self-service portal is served in the stack and the full front-of-house happy path
|
|
runs in a real browser — **mock DigiD login → submit → confirmation** — closing the walking skeleton
|
|
(portal → BFF → domain → Flowable → ACL → OpenZaak, with the openbaar register reading the projection).
|
|
|
|
```bash
|
|
# 1. Bring the whole stack up (portal served on :8140, BFF :8080, Keycloak :8180).
|
|
make up
|
|
|
|
# 2. Automated happy path — Playwright, inside the compose network (issuer-consistent):
|
|
make verify-e2e # → login as jan-burger → submit → "ontvangen" confirmation
|
|
|
|
# 3. By hand: open the portal, log in as jan-burger / test123, click "Registratie indienen".
|
|
open http://localhost:8140
|
|
```
|
|
|
|
> The portal is served same-origin with the BFF (nginx proxies `/self-service` + `/openbaar`), so no
|
|
> CORS; the OIDC authority comes from `/config.json` at runtime. See `docs/frontend-decisions.md`.
|
|
|
|
---
|
|
|
|
## S-08c — Self-service submit form (NL Design System + DigiD)
|
|
|
|
**Outcome:** a zorgprofessional logs in via mock DigiD and submits a BIG registration through the
|
|
self-service portal (NL Design System styling); the page confirms with the reference returned by the
|
|
BFF. The bsn comes from the DigiD token, so it's a confirm-and-submit flow (no bsn field).
|
|
|
|
```bash
|
|
# 1. Bring the backend + Keycloak up (BFF on :8080, Keycloak on :8180).
|
|
make up
|
|
|
|
# 2. Serve the portal (dev server); it redirects to Keycloak for DigiD login.
|
|
pnpm nx serve self-service # → http://localhost:4200
|
|
|
|
# 3. In the browser: log in as the mock DigiD user jan-burger / test123, then submit.
|
|
# The page shows the returned registration reference.
|
|
```
|
|
|
|
> First real UI. The full **login → submit → success** happy path is automated in **S-08d**
|
|
> (Playwright, against the compose-served app). Component tests + an axe WCAG 2.1 AA check on the
|
|
> submit page run headless in the `frontend` CI lane. See `docs/frontend-decisions.md`.
|
|
|
|
---
|
|
|
|
## S-08a — Nx workspace + self-service portal skeleton
|
|
|
|
**Outcome:** the frontend foundation — an Nx (pnpm) monorepo with the `self-service` Angular app
|
|
(standalone + signals), lint/test/build green in a CI Node lane. The login + submit form follow in
|
|
S-08c.
|
|
|
|
```bash
|
|
# From a fresh clone (Node 24 + pnpm 11):
|
|
pnpm install # native builds are pre-approved in pnpm-workspace.yaml
|
|
pnpm nx test self-service # Vitest component test
|
|
pnpm nx build self-service # production build
|
|
pnpm nx serve self-service # → http://localhost:4200 (placeholder page)
|
|
# Or the CI-equivalent one-shot:
|
|
make frontend # install + nx lint/test/build
|
|
```
|
|
|
|
> Nx manages only `apps/`+`libs/`; the .NET services stay on `dotnet`/the Makefile. NL Design System
|
|
> and the real form arrive in S-08c (#67); see `docs/frontend-decisions.md`.
|
|
|
|
---
|
|
|
|
## S-07 — BFF: the portals' single backend
|
|
|
|
**Outcome:** the BFF validates Keycloak `digid` tokens on the self-service submit (forwarding the
|
|
bsn to the domain) and serves the openbaar register anonymously with only public-safe fields — the
|
|
front door the portals (S-08/S-09) will talk to.
|
|
|
|
**The path:** portal → BFF `POST /self-service/registrations` (token-gated) → domain; and
|
|
BFF `GET /openbaar/register` (anonymous) → projection-api. See ADR-0010.
|
|
|
|
```bash
|
|
# 1. Bring the full stack up.
|
|
make up
|
|
|
|
# 2. Drive the BFF end-to-end (401 without a token, 202 with a real digid token, anonymous openbaar).
|
|
make verify-bff # → "OK — BFF: 401 without token, 202 with a digid token, anonymous ..."
|
|
|
|
# 3. Try it by hand (BFF on host port 8080).
|
|
# a) A digid access token for the mock user jan-burger (bsn 123456782):
|
|
tok=$(curl -s -X POST http://localhost:8180/realms/digid/protocol/openid-connect/token \
|
|
-d grant_type=password -d client_id=big-portal -d username=jan-burger -d password=test123 \
|
|
| python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
|
|
|
|
# b) Submit — without the token it is 401; with it, 202:
|
|
curl -s -o /dev/null -w "no token -> %{http_code}\n" -X POST http://localhost:8080/self-service/registrations
|
|
curl -s -o /dev/null -w "with token-> %{http_code}\n" -X POST http://localhost:8080/self-service/registrations \
|
|
-H "Authorization: Bearer $tok"
|
|
|
|
# c) The openbaar register is anonymous and exposes only id + status (never the bsn):
|
|
curl -fsS http://localhost:8080/openbaar/register | jq
|
|
```
|
|
|
|
> The self-service token is validated against Keycloak's `digid` realm; the openbaar lookup needs no
|
|
> token (S-09). The generated contract lives at `services/bff/openapi.json` — S-08's client is built
|
|
> from it.
|
|
|
|
---
|
|
|
|
## S-05 — BIG Domain Service: submit a registration
|
|
|
|
**Outcome:** submitting a registration starts a Flowable process; the external-task worker
|
|
opens a zaak via the ACL and records it on the aggregate — the upstream half of the skeleton
|
|
that produces the zaak S-06 then projects.
|
|
|
|
**The path:** domain `POST /registrations` → Flowable `registratie` process → `OpenZaakAanmaken`
|
|
worker → ACL → OpenZaak; `GET /registrations/{id}` shows the opened zaak (ADR-0009).
|
|
|
|
```bash
|
|
# 1. Bring the full stack up (seeds config, builds our services, waits for health).
|
|
make up
|
|
|
|
# 2. Drive the full path end-to-end. This also seeds a published BIG zaaktype and points the
|
|
# ACL at it (the zaak's zaaktype URL is server-assigned, so it isn't known at bring-up).
|
|
make verify-domain # → "OK — the domain opened a zaak and recorded it on the registration"
|
|
|
|
# 3. Submit one yourself (domain on host port 8130). Returns 202 + a Location to read back.
|
|
loc=$(curl -fsS -D - -o /dev/null -X POST http://localhost:8130/registrations \
|
|
-H 'Content-Type: application/json' -d '{"bsn":"123456782"}' | sed -n 's/\r$//; s/^[Ll]ocation: //p')
|
|
|
|
# 4. The worker opens the zaak off the request path (eventual consistency, ADR-0009); poll
|
|
# until zaakUrl is filled. (Step 2 must have run first, so the ACL knows the zaaktype.)
|
|
curl -fsS "http://localhost:8130$loc" | jq
|
|
# → { "registrationId": "...", "status": "Ingediend", "zaakUrl": "http://.../zaken/api/v1/zaken/<uuid>" }
|
|
```
|
|
|
|
> Registration state is in-memory for this slice (ADR-0009); the rebuildable read model is the
|
|
> projection (S-06), fed by the very zaak this flow opens.
|
|
|
|
---
|
|
|
|
## S-06 — Event Subscriber + read projection
|
|
|
|
**Outcome:** a zaak created in OpenZaak flows through NRC to the Event Subscriber, which
|
|
projects it into a rebuildable read projection the projection-api serves.
|
|
|
|
**The path:** OpenZaak → (notification) NRC → (abonnement callback) Event Subscriber →
|
|
`register_projection` → projection-api `GET /register`.
|
|
|
|
```bash
|
|
# 1. Bring the full stack up (seeds config, builds our services, waits for health).
|
|
make up
|
|
|
|
# 2. Register the Event Subscriber's abonnement and create a zaak, then read it back.
|
|
# (The verify-projection check does exactly this end-to-end and asserts the result.)
|
|
make verify-projection # → "OK — projection-api serves zaak <uuid> with status INGEDIEND"
|
|
|
|
# 3. Observe the projection directly via the read API (host port 8120).
|
|
curl -fsS http://localhost:8120/register | jq
|
|
# → [ { "id": "<zaak-uuid>", "status": "INGEDIEND", "bsn": null, "naamPlaceholder": null } ]
|
|
|
|
# 4. Idempotency + rebuild: replays don't duplicate; a rebuild repopulates from the
|
|
# notification log (no OpenZaak access needed — ADR-0008).
|
|
curl -fsS -X POST http://localhost:8110/admin/rebuild # Event Subscriber, host port 8110
|
|
curl -fsS http://localhost:8120/register | jq 'length' # → unchanged
|
|
```
|
|
|
|
> `bsn` / `naam_placeholder` are deferred (ADR-0008) — the notification doesn't carry them and
|
|
> the subscriber may not read OpenZaak directly (§8.1). They surface in a later slice.
|
|
|
|
---
|
|
|
|
## S-09 — Openbaar Register portal (public visibility)
|
|
|
|
**Outcome:** the entry a zorgprofessional submits via self-service becomes publicly visible in the
|
|
anonymous openbaar register portal — closing the walking-skeleton loop (submit → process → projection
|
|
→ public visibility).
|
|
|
|
**The path:** self-service submit → BFF → domain → (zaak) OpenZaak → NRC → Event Subscriber →
|
|
projection → openbaar portal reads the BFF's public-safe `GET /openbaar/register`.
|
|
|
|
```bash
|
|
# 1. Bring the full stack up (self-service :8140, openbaar :8141).
|
|
make up
|
|
|
|
# 2. Submit a registration via the self-service portal (mock DigiD: jan-burger / test123),
|
|
# or drive the whole happy path automatically (login → submit → public visibility):
|
|
make verify-e2e
|
|
|
|
# 3. Open the public register — no login. It lists the submitted entry (id + status only).
|
|
# Only public-safe fields cross the BFF: bsn / naam never appear.
|
|
open http://localhost:8141/ # search box; searches the BFF by referentie
|
|
curl -fsS http://localhost:8140/openbaar/register | jq # same public-safe view via the BFF proxy
|
|
# → [ { "id": "<zaak-uuid>", "status": "INGEDIEND" } ]
|
|
```
|
|
|
|
> The register shows `INGEDIEND` on submit; approval flips it to `INGESCHREVEN` — see S-09b below.
|
|
|
|
---
|
|
|
|
## 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+).
|
|
|
|
## #78 — One reference across both portals (ADR-0012)
|
|
|
|
Before this change the self-service confirmation and the openbaar register showed **different**
|
|
identifiers, so a citizen could not look their registration back up. Now both show the same
|
|
**reference**: the domain `registrationId` is set as the zaak's `identificatie` by the ACL, and the
|
|
Event Subscriber enriches the projection with it by reading the zaak through the ACL (§8.1) — storing
|
|
it in the replay log so rebuild stays log-only (ADR-0008).
|
|
|
|
**The path:** domain passes `registrationId` → ACL sets it as `zaak.identificatie` → NRC →
|
|
Event Subscriber asks the ACL for the reference → projection row + replay log → openbaar register.
|
|
|
|
```bash
|
|
# Submit as in S-09 and note the reference on the confirmation, then find it in the public register:
|
|
ref="<registration-reference-from-the-confirmation>"
|
|
curl -fsS "http://localhost:8140/openbaar/register?q=$ref" | jq
|
|
# → [ { "id": "<zaak-uuid>", "status": "INGEDIEND", "reference": "<same-ref-as-confirmation>" } ]
|
|
```
|
|
|
|
> The openbaar register's "Referentie" column and its search now use this reference — the exact value
|
|
> the citizen saw on submit. Asserted end-to-end by the Playwright happy path.
|