On the single self-hosted runner CI jobs run sequentially, so booting OpenZaak once beats once-per-job. Replace the integration + notifications + compose-smoke jobs with one verify-stack job that brings the full stack up once and runs, as clearly-named steps: health (make verify-up, the DoD smoke) → ACL ↔ OpenZaak (verify-acl) → OpenZaak → NRC delivery (verify-nrc) → teardown (always) + log dump on failure. The check logic moves into stack-agnostic runners (run-acl-integration.sh, run-notification-check.sh) that operate on whatever stack is already up, reaching services by container IP. The local single-concern wrappers (make integration oz-only, make verify-notifications oz+nrc) keep working by delegating to the same runners, so nothing is duplicated. make ci now runs the consolidated 'verify' stage. Verified locally: make verify boots the full stack once, ACL integration passes and the NRC notification is delivered, then tears down. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.7 KiB
ADR-0007: Wiring OpenZaak → Open Notificaties (NRC) for notifications
- Status: Accepted
- Date: 2026-06-29
- Deciders: Respellion engineering
- Relates to: S-01-c (#56); completes S-01 (#2); unblocks the Event Subscriber (#7); builds on ADR-0002 (catalogus/seed) and ADR-0006 (runner-safe container harnesses)
Context
S-01 brought OpenZaak + Open Notificaties (NRC) up in compose but deferred the
notification wiring: OpenZaak ran with NOTIFICATIONS_DISABLED=true and NRC's
setup_configuration was empty. The walking skeleton (PRD §12) needs the upstream
event path — a zaak created in OpenZaak must publish a notification NRC fans out to
subscribers — before the Event Subscriber (#7) can consume it.
The OpenZaak↔NRC handshake is intricate and several details are non-obvious; they
were nailed down by iterating setup_configuration against the running stack.
Decision
Provision both sides declaratively via setup_configuration, authenticate with the
existing big-reference-seed client, and run NRC's celery-beat so deliveries happen.
- OpenZaak (
infra/openzaak/setup_configuration/data.yaml): azgw_consumersservicenrc(api_typenrc, the NRC API root) plusnotifications_confignaming it.NOTIFICATIONS_DISABLEDis flipped tofalseonly when NRC is present — the full stack and the local twin set it; OpenZaak-only bring-ups (openzaak-up, the ACL integration test) default it back totrueviaOZ_NOTIFICATIONS_DISABLEDso they don't 500 publishing to an absent NRC. - NRC (
infra/opennotificaties/setup_configuration/data.yaml): thebig-reference-seedJWT credential (to verify OpenZaak's token), azgw_consumersacservice pointing at OpenZaak's Autorisaties API, theautorisaties_apistep delegating authorization to that AC, and thezakenkanaal. NRC's init container switches frommigrateto/setup_configuration.sh; its data.yaml is delivered through therr-nrc-configexternal volume byinfra/seed-config.sh(the samedocker cppattern as OpenZaak — bind mounts don't reach the CI runner's daemon). - celery-beat is required. NRC accepts a notification and writes a
ScheduledNotification; a periodicexecute_notificationstask (celery-beat, everyNOTIFICATION_SEC_INTERVALs) drains it to the worker for delivery. The lean S-01 stack dropped beat — so notifications were accepted but never delivered. Annrc-beatservice is added to every compose; the interval is lowered to 5s.
Verification is a runner-safe smoke (infra/run-notification-check.sh): it seeds a
published BIG zaaktype, registers an abonnement to a webhook sink, creates a zaak, and
asserts the sink receives the zaken/create notification — all from containers
inside the compose network (ADR-0006). Locally it runs via make verify-notifications
(a throwaway oz+nrc stack); in CI it runs as the verify-nrc step of the consolidated
verify-stack job (one shared full-stack bring-up — issue #58).
Consequences
- Positive: the walking-skeleton event path works end to end; #7 can consume real
notifications; the wiring is declarative and reproducible from a fresh
make. - Gotchas captured (see gitea-actions-gotchas.md):
- Single-label hosts aren't URL-valid. OpenZaak/NRC reject
http://openzaak…/http://nrc-web…in URLs they validate (DjangoURLValidator); the verify harness reaches services and registers the sink callback by container IP. - Abonnement callbacks must enforce auth. NRC probes the callback during
registration and refuses it (
no-auth-on-callback-url) unless it returns 401 without the configuredAuthorization; the sink enforces a bearer token.
- Single-label hosts aren't URL-valid. OpenZaak/NRC reject
- Cost: an extra long-running service (
nrc-beat) per stack, and the verify job needs egress (base images +selectielijst.openzaak.nl, since the published zaaktype the check creates a zaak against depends on it — ADR-0006). - Dev-only credentials reused (
big-reference-seed/ its secret) across publish, AC lookup, and seeding — acceptable for the reference app, not production.
Alternatives considered
- NRC with its own (non-AC) authorization — rejected: delegating to OpenZaak's
Autorisaties API is the upstream-intended model and reuses the applicatie that
already grants
heeft_alle_autorisaties. - Keep beat out, deliver synchronously — not an option: Open Notificaties 1.16 delivers via scheduled notifications drained by beat; there is no sync path.
- A persistent abonnement in
setup_configurationinstead of registering one in the verify harness — deferred: the real subscriber is #7; the harness's sink abonnement is throwaway and IP-specific.