feat(openzaak): real notification delivery to the BFF webhook (WP-58)
OpenZaak doesn't serve the Notificaties API itself (it's a separate app, open-notificaties) — standing one up for a real abonnement would triple this harness for a benefit it doesn't need (exactly one subscriber, this repo's own BFF). Instead, an opt-in compose overlay adds a celery worker and points OpenZaak's NotificationsConfig straight at the BFF's webhook via a zgw_consumers Service; bootstrap-notificaties.sh configures it idempotently and verify-notificatie.sh proves a real write delivers to the BFF's audit trail end-to-end. Verified live: preflight proves the webhook's shared-secret gate both ways (204/401), a zaak PATCH triggers real celery delivery, and rerunning both scripts against an already-configured harness stays idempotent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,9 +132,11 @@ to `IZaakSource` per call), so a valid notification's only visible effect right
|
||||
row proving the round-trip works end-to-end. Add real invalidation at the `// ponytail:` marker
|
||||
in `Program.cs` if a cache is ever introduced.
|
||||
|
||||
**Provisioning the `abonnement` is out-of-band, one-time config against a live OpenZaak — not
|
||||
app code.** Register it once (e.g. via OpenZaak's admin UI or a `POST` to its Abonnementen API)
|
||||
pointing at this BFF's public URL:
|
||||
**A real deployment provisioning is out-of-band, one-time config against a live OpenZaak — not
|
||||
app code.** OpenZaak does not serve the Notificaties API itself — it's a separate application
|
||||
(`open-notificaties`, its own image/DB/celery stack). Register the `abonnement` once (e.g. via
|
||||
Open Notificaties' admin UI or a `POST` to its Abonnementen API) pointing at this BFF's public
|
||||
URL:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
@@ -144,6 +146,13 @@ pointing at this BFF's public URL:
|
||||
}
|
||||
```
|
||||
|
||||
`auth` is sent verbatim as the `Authorization` header on every callback (the NRC's
|
||||
`auth_type=api_key` default) — no `Bearer` prefix, matching this endpoint's plain string
|
||||
compare.
|
||||
|
||||
**The dev harness (WP-58) skips the NRC entirely** — see "Notifications-enabled profile"
|
||||
below.
|
||||
|
||||
## Identity — the acting citizen (WP-53)
|
||||
|
||||
Everything above used to hardcode a single owner (`DocumentStore.DemoOwner`) and a single static
|
||||
@@ -224,6 +233,53 @@ enforces ZGW's geo-header requirement in a way no stub-based test could catch, s
|
||||
never rejects an unexpected (or missing) header. That is the harness's whole point — proving
|
||||
the seam against real protocol behaviour, not just the shapes we already assumed.
|
||||
|
||||
### Notifications-enabled profile (WP-58)
|
||||
|
||||
The base harness above runs with `NOTIFICATIONS_DISABLED: 'true'` (no celery worker) — fine for
|
||||
proving the read/write ZGW seam, but it means a write to a notified resource never actually
|
||||
delivers anything. `docker-compose.openzaak.notificaties.yml` is an opt-in overlay that adds the
|
||||
one celery worker OpenZaak needs to deliver a notification, and flips that flag off. The two
|
||||
changes are inseparable: the moment `NOTIFICATIONS_DISABLED` is false, OpenZaak's
|
||||
`NotificationsConfig` must have a client configured or every write to a notified resource 500s
|
||||
and rolls back (`NOTIFICATIONS_GUARANTEE_DELIVERY` defaults true) — so `bootstrap-notificaties.sh`
|
||||
configures that client in the same step.
|
||||
|
||||
A real Notificaties API (NRC) is a separate application this harness doesn't stand up (see the
|
||||
"Notificaties webhook" section above) — reproducing it here (its own DB + celery + a real
|
||||
`abonnement`/kanaal registration) would roughly triple the harness for a benefit this dev loop
|
||||
doesn't need: there's only ever one subscriber (this repo's own BFF). Instead
|
||||
`bootstrap-notificaties.sh` points OpenZaak's `NotificationsConfig` straight at the BFF's webhook
|
||||
via a `zgw_consumers.Service` (`auth_type=api_key`, so the configured secret is sent verbatim as
|
||||
the `Authorization` header — exactly what the endpoint's plain string-compare expects). Same
|
||||
delivery proof (`write → OpenZaak's celery worker → a real HTTP POST → the BFF's audit trail`),
|
||||
far less to stand up and keep alive. A real deployment with more than one subscriber, or that
|
||||
needs kanaal-filtered fan-out, needs a real NRC + `abonnement` — this harness's shortcut doesn't
|
||||
model that.
|
||||
|
||||
The overlay's `celery` worker joins the repo root's own `docker compose up` network (by name,
|
||||
`api`) to reach the BFF — `host.docker.internal:host-gateway` was tried first, but this
|
||||
environment's rootless Podman doesn't route container→host-port traffic through it (DNS
|
||||
resolves, every TCP connect times out); container-to-container is the reliable path regardless
|
||||
of Docker vs. Podman. That means the notifications profile needs the repo root's `docker compose
|
||||
up` (or an equivalent `api` container on that network) running too, with
|
||||
`Zgw__NotificatieAuthorization` set:
|
||||
|
||||
```bash
|
||||
docker compose run --rm -d --name atomic-design-poc-api-1 --service-ports \
|
||||
-e Zgw__NotificatieAuthorization='<a secret>' api # repo root
|
||||
|
||||
cd backend/openzaak
|
||||
docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.notificaties.yml up -d
|
||||
./bootstrap-catalogus.sh
|
||||
BFF_AUTH='<the same secret>' ./bootstrap-notificaties.sh
|
||||
BFF_AUTH='<the same secret>' ./verify-notificatie.sh # proves a real delivery, end to end
|
||||
```
|
||||
|
||||
Verified live in-session: the preflight in `bootstrap-notificaties.sh` proved the BFF's auth gate
|
||||
both ways (204 with the secret, 401 without/wrong), `verify-notificatie.sh` found the delivered
|
||||
`zgw:notificatie`/`allow` audit row for the PATCHed zaak, and re-running both scripts against the
|
||||
already-configured client stayed idempotent (no errors, no duplicate `Service` rows).
|
||||
|
||||
## Config
|
||||
|
||||
```jsonc
|
||||
@@ -247,9 +303,10 @@ the seam against real protocol behaviour, not just the shapes we already assumed
|
||||
"identiteit": "https://open-zaak.example/catalogi/api/v1/informatieobjecttypen/<uuid>",
|
||||
"diploma": "https://open-zaak.example/catalogi/api/v1/informatieobjecttypen/<uuid>"
|
||||
},
|
||||
// WP-52 (Notificaties): NRC base URL (documentation/provisioning only, no outbound call) +
|
||||
// the shared secret NRC must send back on every webhook POST.
|
||||
"NrcBaseUrl": "https://open-zaak.example/notificaties/api/v1",
|
||||
// WP-52 (Notificaties): NRC base URL — a SEPARATE host/app from OpenZaak itself
|
||||
// (documentation/provisioning only, no outbound call) + the shared secret NRC must send
|
||||
// back on every webhook POST.
|
||||
"NrcBaseUrl": "https://open-notificaties.example/api/v1",
|
||||
"NotificatieAuthorization": "<same value registered in the abonnement's `auth` field>"
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user