## What & why The host-browser stack (`make local`) had drifted behind three slices, so a fresh bring-up couldn't complete the flow: registrations stuck at `OpenZaakAanmaken`, the behandel werkbak stayed empty, and the openbaar register showed nothing. The `verify-*` scripts do this setup for CI at test time; `make local` had no equivalent. This makes the local stack **self-seed at bring-up** so it just works in a browser: - **DMN** — `flowable-init` now also deploys `diploma-eligibility.dmn` (was BPMN-only), so completing `WachtOpDocumenten` routes through the DMN to `Beoordelen` instead of 404ing. - **Zaaktype + ACL** — a `local-seed` one-shot publishes the BIG zaaktype (whose UUID is server-assigned, hence not static in the compose file) and writes the real URLs to `seed-env:/acl.env`; the ACL sources it on startup via an entrypoint override. - **NRC abonnement** — an `nrc-subscribe` one-shot registers the `zaken` subscription at the event-subscriber callback, so notifications reach the projection/openbaar register. Both one-shots reach OpenZaak/NRC by **container IP** (a single-label host fails their Django URLValidator), mirroring the CI verify scripts. Design + trade-offs in **ADR-0020**. Closes #110 ## Definition of Done - [x] Linked Gitea issue (#110). - [x] Failing test committed before the implementation — `test(infra): …` adds `infra/run-local-flow-check.sh` / `make verify-local`; the three gaps' failures were observed live on a fresh `make local` (red), and the fix turns it green. - [x] Implementation makes the test pass; docs commit follows. - [x] Conventional Commits referencing the issue (`refs #110`). - [ ] CI green — running on the restored runner. Infra-only change; the CI `verify-stack` job uses `docker-compose.yml` (untouched). Also validated locally: `make verify-local` passes against a fresh `make local` (see below). - [x] `docker compose up` from a fresh clone reaches green health checks — verified: `make local` healthy in ~2m20s, then `make verify-local` green. - [x] Docs updated — ADR-0020 + demo-script note. - [x] ADR added in `docs/architecture/` — ADR-0020. - [x] Demo note in `docs/demo-script.md`. ## Notes for reviewers - **Infra-only** — no service code changes; the ACL image and the CI stack (`docker-compose.yml`) are untouched. - **Verified end-to-end on a fresh stack** (`make local-down && make local && make verify-local`): ``` >> 2. zaak opened (zaaktype seeded + wired) >> 3. documents accepted 204 (DMN deployed) >> 4. in the werkbak (DMN routing → Beoordelen) >> 5. visible in the openbaar register (NRC abonnement) OK — a fresh local stack completed the flow with no manual seeding ``` - **Follow-up:** the cleaner design — ACL resolving its zaaktype by `identificatie` instead of a pinned server-assigned URL — is split out as **S-27 (#113)**; landing it would remove the `acl.env` injection here. ADR-0020 records this. - The `seed-env` volume carries the generated `acl.env` from `local-seed` to the ACL; a `down --volumes` (as `make local-down` does) resets it cleanly. Reviewed-on: #114
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:
- The ACL pointed at a placeholder zaaktype (
…/00000000-…), so zaak creation failed with OpenZaak400and the registratie process stuck atOpenZaakAanmaken(S-05). flowable-initdeployed onlyregistratie.bpmn, notdiploma-eligibility.dmn, so completingWachtOpDocumenten404'd on the missing decision and never reachedBeoordelen(S-10a/S-13).- 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-initnow deploysdiploma-eligibility.dmnto the DMN engine (/flowable-rest/dmn-api/dmn-repository/deployments) as a separate deployment alongside the BPMN — identical to the CIflowable-init. Idempotent. - Zaaktype + ACL wiring (gap 1). A
local-seedone-shot runs the existinginfra/openzaak/seed_catalogus.py(OZ_PUBLISH=1) against OpenZaak and writes the resultingAcl__Defaults__ZaaktypeUrl/…InformatieobjecttypeUrl/Acl__OpenZaak__BaseUrlintoseed-env:/out/acl.env. The ACL mounts that volume read-only and overrides its entrypoint tosh -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 ACLdepends_on: local-seed (service_completed_successfully). - Abonnement (gap 3). A
nrc-subscribeone-shot registers an abonnement on thezakenkanaal pointing at the event-subscriber's/notificationscallback (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
callbackUrlare validated by Django's URLValidator, which rejects a single-label host likeopenzaak/event-subscriber. The scripts resolve the target's container IP at runtime (asinfra/run-domain-check.shdoes), 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 localcompletes 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-seedstep run aftermake 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 somethingsetup_configurationsupports cleanly in 1.28.2. Rejected as more fragile than reusingseed_catalogus.py.