# ADR-0021: The ACL resolves its zaaktype by identificatie, not a pinned URL - **Status:** Accepted - **Date:** 2026-07-22 - **Deciders:** Respellion engineering - **Relates to:** S-27 (#113), proposed in #117. The cleaner design deliberately split out of S-B04 (#110, ADR-0020), which fixed the local stack with an infra-only bootstrap. ## Context The ACL was handed a **pinned zaaktype URL** (`Acl__Defaults__ZaaktypeUrl`) and diploma informatieobjecttype URL. OpenZaak assigns those UUIDs at creation, so the URL is not knowable when the compose file is written — every stack had to seed the catalogus and then capture + inject the resulting URLs out of band: `run-domain-check.sh` for CI, and the `local-seed` → `acl.env` bootstrap (ADR-0020) for `make local`. Brittle, and a stale/placeholder URL failed opaquely (OpenZaak 400). ## Decision **The ACL resolves its zaaktype (by `identificatie`) and diploma informatieobjecttype (by `omschrijving`) from OpenZaak's Catalogi API, instead of being handed the URLs.** - **Config:** `AclDefaults.ZaaktypeUrl`/`InformatieobjecttypeUrl` → `ZaaktypeIdentificatie` (`BIG-REGISTRATIE`) / `InformatieobjecttypeOmschrijving` (`Diploma`). - **Lookup (gateway, §8.1):** `GET /catalogi/api/v1/zaaktypen?status=definitief&identificatie=…` → the published zaaktype URL; `GET /catalogi/api/v1/informatieobjecttypen?status=definitief` matched on `omschrijving`. Reuses the gateway's existing catalogus-query machinery. - **Timing = lazy + cached (`CachedZaaktypeCatalog`).** Resolve on first use (first zaak open / document store) and cache for the process lifetime. Lazy avoids a startup ordering coupling — the ACL never crash-loops when it boots before the catalogus is published. A **failed** resolution is not cached, so it is retried on the next call (e.g. once the zaaktype is published); a restart re-resolves. - **Failure mode:** no published match → a clear "No published zaaktype with identificatie '…' found in OpenZaak — is the BIG catalogus seeded and published?" error, replacing the opaque placeholder 400. ## Consequences **Positive** - No stack captures or injects a server-assigned URL any more: `run-domain-check.sh` drops the `ACL_ZAAKTYPE_URL`/`ACL_INFORMATIEOBJECTTYPE_URL` capture+inject, `docker-compose.yml`/`.local.yml` drop the placeholder URL env, and `local-seed`/`acl.env` shrink to a single line. The ACL self-configures from the catalogus it already talks to. - The failure mode is legible (a named error instead of a 400 on a zeros-UUID). **Negative / costs** - The ACL still needs its OpenZaak **BaseUrl** pointed at a **URL-valid host (a container IP)**, so the base-URL injection from ADR-0020 stays (the local `acl.env` now carries only that; CI keeps `ACL_OPENZAAK_BASEURL`). This is **not** something S-27 can remove: OpenZaak validates the `zaaktype` field on zaak-create with Django's URLValidator and **rejects a single-label host** (`http://openzaak:8000/…` → `zaaktype: bad-url, "Voer een geldige URL in."`, confirmed empirically). So ADR-0020's `seed-env` volume + ACL entrypoint shim are **simplified, not deleted**. - New branching in the gateway/resolver → unit + integration test surface; the mutation ratchet covers it (§5). - A seed step still **creates + publishes** the zaaktype (this ADR changes only discovery). Reaching OpenZaak's Catalogi API to *seed* likewise needs the IP host (its query params hit the same URLValidator) — unchanged from before. ## Alternatives considered - **Resolve at startup** (eager). Simpler cache, but reintroduces the ordering coupling (crash-loop if the catalogus isn't published yet). Rejected in favour of lazy. - **Per-request resolution** (no cache). No stale-cache risk, but a Catalogi lookup on every ACL operation. Rejected; a process-lifetime cache with restart-to-refresh is enough here. - **Keep the pinned URL** (status quo / ADR-0020 only). Rejected — the brittleness this ADR removes is exactly what S-27 was carved out to fix.