Files
register-referentie/docs/architecture/adr-0020-local-stack-self-seeds.md
notandClaude Opus 4.8 a693137c7c
CI / lint (pull_request) Successful in 1m19s
CI / build (pull_request) Successful in 56s
CI / unit (pull_request) Successful in 1m6s
CI / frontend (pull_request) Successful in 2m31s
CI / mutation (pull_request) Successful in 6m0s
CI / verify-stack (pull_request) Failing after 10m33s
docs(infra): ADR-0020 + demo note for the local-stack self-seed (refs #110)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 11:34:36 +02:00

6.0 KiB

ADR-0020: The local stack self-seeds the zaaktype, DMN, and NRC abonnement at bring-up

  • Status: Accepted
  • Date: 2026-07-22
  • Deciders: Respellion engineering
  • Relates to: S-B04 (#110). Local-stack twin of the seeding the verify-* scripts do for CI (infra/run-domain-check.sh, infra/verify-notification-driver.py). Superseded in part by S-27 (#113), which would let the ACL resolve its zaaktype by identificatie and remove the URL injection.

Context

infra/docker-compose.local.yml is the host-browser-friendly stack (make local) — the one a developer clicks through the portals with. It had drifted behind three slices, so a fresh bring-up could not complete the flow:

  1. The ACL pointed at a placeholder zaaktype (…/00000000-…), so zaak creation failed with OpenZaak 400 and the registratie process stuck at OpenZaakAanmaken (S-05).
  2. flowable-init deployed only registratie.bpmn, not diploma-eligibility.dmn, so completing WachtOpDocumenten 404'd on the missing decision and never reached Beoordelen (S-10a/S-13).
  3. No NRC abonnement was registered, so notifications reached NRC and went nowhere — the projection and the openbaar register stayed empty (S-06).

The CI stack (infra/docker-compose.yml) does not hit this because its verify-* scripts seed the zaaktype, deploy the DMN, and register the abonnement at test time. The local stack has no such harness — a developer just runs make local and browses. The non-obvious wrinkle is (1): the zaaktype UUID is assigned by OpenZaak at creation, so the ACL's zaaktype URL is not knowable when the compose file is written and cannot be a static value.

Decision

Make the local stack self-seed at bring-up via one-shot init containers, and hand the ACL its server-assigned zaaktype URL through a shared-volume env file it sources on startup.

  • DMN (gap 2). flowable-init now deploys diploma-eligibility.dmn to the DMN engine (/flowable-rest/dmn-api/dmn-repository/deployments) as a separate deployment alongside the BPMN — identical to the CI flowable-init. Idempotent.
  • Zaaktype + ACL wiring (gap 1). A local-seed one-shot runs the existing infra/openzaak/seed_catalogus.py (OZ_PUBLISH=1) against OpenZaak and writes the resulting Acl__Defaults__ZaaktypeUrl / …InformatieobjecttypeUrl / Acl__OpenZaak__BaseUrl into seed-env:/out/acl.env. The ACL mounts that volume read-only and overrides its entrypoint to sh -c 'set -a; . /seed/acl.env; set +a; exec dotnet Acl.Api.dll', so the real values override the compose placeholders before the app reads config. The ACL depends_on: local-seed (service_completed_successfully).
  • Abonnement (gap 3). A nrc-subscribe one-shot registers an abonnement on the zaken kanaal pointing at the event-subscriber's /notifications callback (infra/local/register-abonnement.py). It is a leaf — nothing depends on it — so it can wait for the event-subscriber without forming a cycle with the ACL bootstrap.
  • Reach OpenZaak/NRC by container IP, not service name. Both the seed's ZTC calls and the abonnement's callbackUrl are validated by Django's URLValidator, which rejects a single-label host like openzaak / event-subscriber. The scripts resolve the target's container IP at runtime (as infra/run-domain-check.sh does), keeping the seeded URLs valid and host-consistent — the ACL's base URL is set to the same OpenZaak IP that owns the zaaktype URL.
  • Acceptance. make verify-local (infra/run-local-flow-check.sh) submits against a fresh stack and asserts the zaak opens, the case reaches the werkbak after documents, and the reference appears in the openbaar register — the red-to-green test for all three gaps.

Consequences

Positive

  • A fresh make local completes the full demo (submit → werkbak → openbaar) with no manual seeding — the slice's stated outcome.
  • Reuses the proven CI mechanisms (seed_catalogus.py, the DMN deploy, the abonnement driver) rather than inventing new ones; the only genuinely new piece is the entrypoint-sourced env file.
  • No service code changes — the fix is entirely in infra/ (compose + two small scripts), so the ACL image and the CI stack are untouched.

Negative / costs

  • The two compose files diverge further: the CI stack seeds at test time, the local stack at bring-up. Mitigated by reusing the same underlying scripts and cross-referencing them.
  • The ACL entrypoint override couples the local ACL to the seed-written file path (/seed/acl.env); if the seed fails, the ACL fails to start (loud, healthcheck-visible — preferred over silently running with a placeholder).
  • Container-IP-based URLs are re-derived on each bring-up; a keep-volumes restart with a changed OpenZaak IP relies on OpenZaak rebuilding hyperlinked URLs from the request host (it does) so the idempotent re-seed reports current-IP URLs.

Alternatives considered

  • ACL resolves its zaaktype by identificatie (BIG-REGISTRATIE) at startup. The cleaner, less-brittle design — no server-assigned URL to capture — and it would help the CI stack too. But it changes a service's runtime behaviour and its config contract, needs new ACL tests + mutation coverage, and still needs a seed step to create the zaaktype. Deliberately split out as its own slice with its own ADR (S-27 / #113) rather than folded into this infra-only fix.
  • A documented make local-seed step run after make local. Smallest change, but it fails the slice's "no manual seeding" outcome — the local stack is exactly the one meant to just work in a browser. Rejected.
  • Fixed zaaktype UUID via OpenZaak setup_configuration/fixtures. OpenZaak assigns UUIDs on POST; declaratively creating a fully published zaaktype (statustypen + resultaattypen validated against the Selectielijst + roltypen + iot relations) is not something setup_configuration supports cleanly in 1.28.2. Rejected as more fragile than reusing seed_catalogus.py.