Files
register-referentie/docs/architecture/adr-0020-local-stack-self-seeds.md
not 183d0bce31
CI / lint (push) Successful in 1m20s
CI / build (push) Successful in 59s
CI / unit (push) Successful in 1m12s
CI / frontend (push) Successful in 2m42s
CI / mutation (push) Successful in 5m42s
CI / verify-stack (push) Successful in 9m21s
fix(infra): docker-compose.local self-seeds zaaktype, DMN + NRC abonnement (closes #110) (#114)
## 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
2026-07-22 12:44:29 +00: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.