# WP-56 — Idempotent catalogus provisioning Status: done Phase: 10 — OpenZaak production hardening ## Why `backend/openzaak/bootstrap-catalogus.sh` seeds catalogus/zaaktype/statustype/roltype/zaak via hand-rolled curl+JWT and is explicitly **not idempotent** (fails on `domein`+`rsin` uniqueness on rerun) — fine for a one-shot WP-54 harness, wrong for an environment that needs to be rebuildable. OpenZaak already ships a documented, scripted alternative — the `setup_configuration` mechanism (already used in the harness for the JWTSecret/Applicatie, see `setup_configuration/data.yaml`) — this WP extends that same mechanism to the catalogus content too. ## Read first - `backend/openzaak/bootstrap-catalogus.sh` - `backend/openzaak/setup_configuration/data.yaml` - `backend/openzaak/docker-compose.openzaak.yml` (`web-init` service) - OpenZaak's own `setup_configuration` / `openzaak_config_cli` docs (upstream) ## Decisions (pre-made, don't relitigate) - Move catalogus/zaaktype/statustype/roltype provisioning into `setup_configuration`, run by the existing `web-init` one-shot service, instead of the separate curl script. - Keep `bootstrap-catalogus.sh` only for whatever content `setup_configuration` genuinely can't express (e.g. a demo zaak instance) — confirm what's left at kickoff. - Provisioning must be safe to run against an already-provisioned instance — either genuinely idempotent, or the compose is structured to only run it once per fresh volume (document which, don't leave it ambiguous). ## Files - `backend/openzaak/setup_configuration/data.yaml` - `backend/openzaak/bootstrap-catalogus.sh` (trim to whatever remains) - `backend/openzaak/docker-compose.openzaak.yml` - `backend/openzaak/README.md` ## Steps 1. Express the catalogus/zaaktype/statustype/roltype definitions currently created by curl as `setup_configuration` YAML. 2. Wire it into the `web-init` command alongside the existing JWTSecret/Applicatie config. 3. Trim `bootstrap-catalogus.sh` to only what setup_configuration can't cover, if anything. 4. Test: tear down + `docker compose up` twice in a row (fresh volume, then existing volume); confirm no failure on rerun. 5. Update the README describing the provisioning flow. ## Acceptance criteria - [x] ~~Catalogus/zaaktype/statustype/roltype provisioning is declarative (`setup_configuration`), not imperative curl.~~ Not achievable — see Deviation below; replaced by: provisioning is safe to rerun against an already-seeded instance. - [x] Running the compose stack up twice in a row doesn't error (verified: `bootstrap-catalogus.sh` run 3× in a row against the same instance, all reused/no duplicates/no errors). - [x] WP-54's `OpenZaakIntegrationTests` still pass unchanged (same content, different provisioning mechanism). ## Deviation from the original plan Confirmed by reading the `django_setup_configuration` steps actually installed inside the `openzaak/open-zaak:1.29.1` image (`/app/src/openzaak/config/setup_configuration/steps/` + every third-party `contrib/setup_configuration/` package): the ONLY app-registered configuration steps are sites/credentials/applicaties (already used by `data.yaml`) and Selectielijst API config. There is no step for catalogus/zaaktype/statustype/roltype/zaak content anywhere in this OpenZaak version — the WP's core premise ("move provisioning into setup_configuration") is not achievable. This was explicitly anticipated by the WP's own Risks section ("if a piece genuinely can't be expressed declaratively, keep it in a clearly-labeled idempotent script rather than forcing a bad fit") and Decisions block ("either genuinely idempotent, or the compose is structured to only run it once... document which"). Chose **genuinely idempotent**: `bootstrap-catalogus.sh` now looks up every resource by its natural key before creating it (catalogus by `domein`+`rsin`, zaaktype by `catalogus`+ `identificatie`, statustype by `zaaktype`+`volgnummer` (no server-side volgnummer filter, so listed by zaaktype and matched client-side), resultaattype by existence-only (publish just needs ≥1), roltype by `zaaktype`+`omschrijvingGeneriek`, zaaktype-publish by checking `concept` on the zaaktype detail first, zaak by `identificatie`, status/rol by existence-under-the-zaak). Real gotcha hit and fixed: OpenZaak's query-parameter names are camelCase (`omschrijvingGeneriek`), not the Django model's snake_case (`omschrijving_generiek`) the filter is registered under internally — the snake_case form 400s with "Onbekende query parameters" (unknown query parameter). Verified for real: fresh instance → full run (all "created") → `dotnet test --filter Category=Integration` green → reran the script twice more against the same live instance (all "exists", identical URLs each time, no duplicates) → integration test still green. Not attempted: moving anything into `setup_configuration/data.yaml` (nothing there to move, per the finding above). ## Verification `docker compose -f backend/openzaak/docker-compose.openzaak.yml up` twice in a row (fresh volume, then existing volume); `cd backend && dotnet test --filter Category=Integration` against the harness. ## Out of scope Secrets/TLS (WP-55, unrelated but sequenced first in the table only by number), client scopes (WP-57), notifications (WP-58). ## Risks OpenZaak's `setup_configuration` coverage for zaaktype/besluittype content may be incomplete upstream — if a piece genuinely can't be expressed declaratively, keep it in a clearly-labeled idempotent script rather than forcing a bad fit.