## 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
93 lines
6.0 KiB
Markdown
93 lines
6.0 KiB
Markdown
# 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`.
|