Files
register-referentie/docs/architecture/adr-0007-notification-wiring.md
Niek Otten d49443353e
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 40s
CI / unit (pull_request) Successful in 48s
CI / mutation (pull_request) Successful in 1m36s
CI / verify-stack (pull_request) Successful in 4m37s
refactor(ci): one verify-stack stage for all live-stack checks (closes #58) (refs #46 #56)
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>
2026-06-29 16:48:38 +02:00

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): a zgw_consumers service nrc (api_type nrc, the NRC API root) plus notifications_config naming it. NOTIFICATIONS_DISABLED is flipped to false only 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 to true via OZ_NOTIFICATIONS_DISABLED so they don't 500 publishing to an absent NRC.
  • NRC (infra/opennotificaties/setup_configuration/data.yaml): the big-reference-seed JWT credential (to verify OpenZaak's token), a zgw_consumers ac service pointing at OpenZaak's Autorisaties API, the autorisaties_api step delegating authorization to that AC, and the zaken kanaal. NRC's init container switches from migrate to /setup_configuration.sh; its data.yaml is delivered through the rr-nrc-config external volume by infra/seed-config.sh (the same docker cp pattern 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 periodic execute_notifications task (celery-beat, every NOTIFICATION_SEC_INTERVALs) drains it to the worker for delivery. The lean S-01 stack dropped beat — so notifications were accepted but never delivered. An nrc-beat service 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 (Django URLValidator); 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 configured Authorization; the sink enforces a bearer token.
  • 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_configuration instead of registering one in the verify harness — deferred: the real subscriber is #7; the harness's sink abonnement is throwaway and IP-specific.