diff --git a/docs/architecture/adr-0020-local-stack-self-seeds.md b/docs/architecture/adr-0020-local-stack-self-seeds.md new file mode 100644 index 0000000..f2805fe --- /dev/null +++ b/docs/architecture/adr-0020-local-stack-self-seeds.md @@ -0,0 +1,92 @@ +# 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`. diff --git a/docs/demo-script.md b/docs/demo-script.md index 20e3420..9191f05 100644 --- a/docs/demo-script.md +++ b/docs/demo-script.md @@ -5,6 +5,33 @@ copy-pasteable walkthrough against a local `make up` stack. --- +## S-B04 — `make local` completes the whole flow with no manual seeding (#110, ADR-0020) + +**Outcome:** the host-browser stack (`make local`) now self-seeds at bring-up — it publishes the BIG +zaaktype and wires the ACL to it, deploys the `diploma-eligibility` DMN, and registers the NRC +abonnement — so a fresh bring-up runs submit → werkbak → openbaar without the manual seeding the +`verify-*` scripts do for CI. (Previously the process stuck at `OpenZaakAanmaken`, the werkbak stayed +empty, and the openbaar register showed nothing.) + +```bash +# 1. Fresh bring-up (self-seeding init containers: local-seed, nrc-subscribe; DMN in flowable-init). +make local + +# 2. Assert the whole flow works with no manual seeding — submit opens a zaak, documents route it to +# the werkbak, and the reference appears in the openbaar register: +make verify-local # → "OK — a fresh local stack completed the flow with no manual seeding ..." + +# 3. Or by hand in the browser: log in at http://localhost:8140 (jan-burger / test123), submit + +# upload a PDF, then approve it in the werkbak at http://localhost:8142 (merel-behandelaar / +# test123); it shows as INGESCHREVEN in the openbaar register at http://localhost:8141. +``` + +> The zaaktype UUID is server-assigned, so `local-seed` writes the real URL into a shared volume as +> `acl.env` and the ACL sources it on startup (ADR-0020). The cleaner long-term fix — the ACL +> resolving its zaaktype by `identificatie` — is tracked separately as S-27 (#113). + +--- + ## S-08d — Walking skeleton complete: browser → submit, end-to-end **Outcome:** the self-service portal is served in the stack and the full front-of-house happy path