Add the openbaar nginx image (serves the Angular app, reverse-proxies /openbaar to
the BFF, same-origin) and wire it into the compose stack on :8141, anonymous (no
Keycloak dependency). Add it to the health-wait list so the e2e can rely on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The verify-e2e lane downloaded ~150 MB of Chromium (npx playwright install) on
every verify-stack run. Use the official mcr.microsoft.com/playwright image with
browsers pre-baked; npm install still pins @playwright/test from tests/e2e, and
the image tag is kept in lockstep with that version. Verified the exact
create + docker cp + start flow launches the baked browser with no download.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nginx listens on IPv4 only (listen 80), but 'localhost' inside the container resolves
to ::1 first, so the wget healthcheck got connection-refused and self-service never
went healthy — timing out the CI stack bring-up. Probe 127.0.0.1 instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tests/e2e Playwright spec drives DigiD login (jan-burger/test123) → submit →
confirmation against the compose-served portal. run-e2e-check.sh runs it inside the
compose network (node container, browser installed at runtime) so the token issuer
(keycloak:8080) matches the BFF authority (ADR-0010). Wired as verify-e2e (Makefile +
verify chain + a verify-stack CI step).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the self-service nginx service (build the app image, depends_on bff healthy +
keycloak started, health-checked, host port 8140). Add it to WAIT_SVCS and the CI
log dump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the bff service in compose (Keycloak authority + downstream domain/projection
URLs, depends_on domain/projection healthy + keycloak started). run-bff-check.sh
verifies the BFF end-to-end against the up stack: 401 without a token, 202 with a
real digid token minted via direct grant against keycloak:8080 (host-consistent
issuer, ADR-0010), and an anonymous public-safe openbaar register (never a bsn).
Wired as verify-bff (Makefile + verify chain + CI step). Stryker baseline for the
BFF's pure logic (OpenbaarProjection) at 100% (break 90); Program/HTTP adapters are
covered by the endpoint tests + verify-bff. CI uploads the bff mutation report.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dockerfile (multi-stage, .NET 10) + .dockerignore for the BIG Domain Service; a
'domain' service in infra/docker-compose.yml (health-checked, depends on acl healthy
and flowable-init completed). run-domain-check.sh drives the full path against the up
stack — seed a published zaaktype, recreate the acl pointed at it (host-consistent),
POST /registrations, and assert the worker opens a zaak and records it. Wired as the
verify-domain Makefile target + a verify-stack CI step; domain added to WAIT_SVCS and
the log dump. seed_catalogus.py now emits a machine-readable ZAAKTYPE_URL line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add projection-db + the two services to both compose files (host ports 8110/8120), their
Dockerfiles (repo-root context — they share Projection.ReadModel), and a runner-safe
verify-projection check (infra/run-projection-check.sh) that registers the abonnement at the
real subscriber, creates a zaak and asserts projection-api serves an INGEDIEND row. Wire it
into make (verify-projection, verify, WAIT_SVCS) and the CI verify-stack job, and run the
event-subscriber Stryker ratchet in `make mutation` + upload its report.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the single self-hosted runner CI jobs run sequentially, so booting OpenZaak once
beats once-per-job. Replace the integration + notifications + compose-smoke jobs with
one verify-stack job that brings the full stack up once and runs, as clearly-named
steps: health (make verify-up, the DoD smoke) → ACL ↔ OpenZaak (verify-acl) →
OpenZaak → NRC delivery (verify-nrc) → teardown (always) + log dump on failure.
The check logic moves into stack-agnostic runners (run-acl-integration.sh,
run-notification-check.sh) that operate on whatever stack is already up, reaching
services by container IP. The local single-concern wrappers (make integration oz-only,
make verify-notifications oz+nrc) keep working by delegating to the same runners, so
nothing is duplicated. make ci now runs the consolidated 'verify' stage.
Verified locally: make verify boots the full stack once, ACL integration passes and
the NRC notification is delivered, then tears down.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make verify-notifications brings the stack up, seeds a published BIG zaaktype, and
asserts a zaak-create notification is delivered to a webhook-sink abonnement. The
sink + driver run as containers inside the compose network and reach OpenZaak/NRC by
container IP (the runner can't reach published ports, and a single-label host isn't
URL-valid). The sink enforces a bearer token because NRC refuses an unauthenticated
callback. New 'notifications' Gitea Actions job runs it (Docker-only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the S-01-c wiring so a zaak created in OpenZaak is published to NRC:
- OpenZaak: a zgw_consumers 'nrc' service + notifications_config (setup_configuration),
publishing as big-reference-seed. NOTIFICATIONS_DISABLED stays true for OpenZaak-only
bring-ups (OZ_NOTIFICATIONS_DISABLED) so the ACL integration test doesn't 500; the
full/local stacks and stack-up set it false.
- NRC: the JWT credential, an 'ac' service + autorisaties_api delegation to OpenZaak's
Autorisaties API, and the 'zaken' kanaal. nrc-init now runs setup_configuration; its
data.yaml is delivered via the rr-nrc-config volume (seed-config.sh nrc), mirroring oz.
- nrc-beat added to every stack: NRC accepts a notification then drains it via a
scheduled execute_notifications task — without beat, nothing is delivered. Interval 5s.
Applied across the standalone, full, and local-bind-mount composes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hosted runner can't reach the stack's published ports (sibling containers),
so run the seed and the test as containers joined to the OpenZaak network,
reaching it by container IP — a single-label host like 'openzaak' isn't URL-valid
for OpenZaak's own URLValidator, but an IPv4 literal is. Code is delivered via
image build / docker cp (bind mounts don't reach the daemon either).
- infra/run-integration.sh: up -> wait healthy (docker inspect) -> seed published
zaaktype (python container on the net) -> build + run the test image on the net
-> always tear down. Plain docker primitives only (portable docker/podman).
- services/acl/Dockerfile.integration: builds + runs Acl.IntegrationTests; dotnet
lives in the image, so the CI job needs only Docker (no setup-dotnet).
- make integration now delegates to the script; re-added the Gitea Actions job.
Supersedes the local-only gap documented earlier; #55 is no longer needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenZaak rejects a zaak against a concept zaaktype (not-published). Add an
opt-in OZ_PUBLISH path that creates the relations publish requires — two
statustypen, a roltype, and a resultaattype whose Selectielijst procestype is
matched onto the zaaktype — then publishes. Default stays concept (ADR-0002);
only the ACL integration test flips it on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds infra/docker-compose.local.yml: the same full stack as the canonical
infra/docker-compose.yml, but the three config inputs (OpenZaak data.yaml,
Keycloak realms, Flowable BPMN) are bind-mounted from the repo instead of
streamed into external volumes by seed-config.sh.
Bind mounts are valid here because a local daemon (Docker Desktop on Windows/
macOS, or rootless Podman on Linux) can see the working directory — the seed
dance only exists for the containerized CI runner, where it can't. So this file
runs with a plain `docker compose up`: no make, no seed step, no bash.
docker compose -f infra/docker-compose.local.yml up -d --build
docker compose -f infra/docker-compose.local.yml up -d --build --wait # Docker Desktop
Linux/macOS convenience wrappers `make local` / `make local-down` added too.
Verified on podman: Keycloak boots from this file and imports the bind-mounted
realms (digid realm returns 200). docs/runbooks/ci.md documents the Windows path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`make smoke` errored locally because podman-compose doesn't implement
`docker compose up --wait` (`unrecognized arguments: --wait`).
Replace the `--wait` step with infra/wait-healthy.sh, which polls each durable
health-checked service ($(WAIT_SVCS)) via `docker ps` + `docker inspect
'{{.State.Health.Status}}'`. This:
- works on both docker compose (CI) and podman-compose (local) — only plain
docker primitives, no `--wait`;
- reads the in-container healthcheck, so it needs no host port access (the CI
runner can't reach published ports);
- ignores the one-shot init jobs, sidestepping the "--wait fails when a
consumer-less one-shot exits 0" issue (flowable-init).
Verified on podman-compose: wait-healthy.sh reports bff healthy (rc=0); podman
exposes .State.Health.Status (starting -> healthy) and the name filter matches
both `_` and `-` container naming.
Docs: gitea-actions-gotchas.md updated (the two `--wait` sections folded into one
"portable health poll" section).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops the inline-build images for the upstream services. The compose now
references the published images directly (openzaak/open-zaak,
openzaak/open-notificaties, keycloak, curl, flowable-rest) with no build for
them, and the config they need is streamed into external named volumes by
infra/seed-config.sh:
rr-oz-config -> oz-init /app/setup_configuration (data.yaml)
rr-kc-realms -> keycloak /opt/keycloak/data/import (realm exports)
rr-fl-bpmn -> flowable-init /work (registratie.bpmn)
How: the seeder creates each volume, `docker create`s a throwaway helper that
mounts it, `docker cp`s the files in, and removes it. docker cp streams over the
Docker API, so it works in Docker-in-Docker (the CI runner) where bind mounts
mount empty. It uses plain `docker create`/`cp` — NOT `docker compose create`,
which podman-compose (local dev) lacks. `external: true` fixed names keep the
volumes identical across docker compose and podman-compose.
Consequence: bare `docker compose up` no longer self-seeds, so use `make up`
(seeds then starts). Every `*-up` target seeds first; `*-down` removes the
external volume. acl/bff are still built (they're our apps, not upstream images).
Verified end-to-end on podman-compose: `make keycloak-up` seeds rr-kc-realms,
the upstream Keycloak mounts it, and --import-realm imports all four realms
(digid realm returns 200). Seeder runs in ~2s.
Docs updated: gitea-actions-gotchas.md, ci.md, openzaak.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the three standalone Dockerfiles (openzaak, opennotificaties,
keycloak) with `build.dockerfile_inline` recipes in the compose files, so the
config bake has no separate Dockerfile artifacts to maintain. Behaviour is
identical: each derived image still COPYies its config in.
- oz-init / keycloak / flowable-init: 2-line inline Dockerfiles.
- Open Notificaties needs no bake at all now — nrc-init runs migrations only,
so all NRC services use the plain base image (removes a whole derived image).
Why dockerfile_inline and not `docker cp` into named volumes: docker cp avoids
images entirely but needs `docker compose create`, which podman-compose (the
local dev runtime) does not implement — it would break `make openzaak-up` etc.
locally. dockerfile_inline works on both podman-compose and the CI runner
(verified both: oz-init + keycloak inline builds locally; flowable-init inline
has been green on CI since run 27).
Docs updated: gitea-actions-gotchas.md and openzaak.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With OpenZaak now coming up, nrc-init ran for the first time and failed:
nrc-init-1 | CommandError: No steps enabled, aborting.
NRC's setup_configuration/data.yaml is intentionally empty ({}) — the
OZ<->NRC wiring is deferred to S-06 — but /setup_configuration.sh runs
`manage.py setup_configuration` regardless, and NRC 1.16.1 aborts when no
steps are enabled. (This was masked until now: oz-init failed first, so
openzaak never became healthy and nrc-init, which waits on it, never ran.)
The documented intent is "init runs migrations only", so nrc-init now runs
`manage.py migrate` directly instead of /setup_configuration.sh, and the
dead RUN_SETUP_CONFIG env is dropped from the NRC services. nrc-web still
migrates + creates the superuser itself via /start.sh.
Also:
- Makefile: bump compose `--wait-timeout` 300 -> 420. The serial
oz-db -> oz-init -> openzaak(healthy) -> nrc-init -> nrc-web(healthy)
chain runs ~260 s on the runner; 420 s gives comfortable headroom.
- ci.yaml: widen the on-failure log dump to oz-init, openzaak, nrc-init,
nrc-web, flowable-init, keycloak, acl, bff for full diagnosability.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the compose-smoke failure (found in the runner logs):
oz-init-1 | CommandError: Yaml file
`/app/setup_configuration/data.yaml` does not exist.
The ubuntu-latest runner runs the job inside a container, so
`docker compose up` starts the stack as SIBLING containers via the host
daemon. A relative bind mount (./openzaak/setup_configuration) resolves to
a path inside the job container that the daemon can't see, so Docker mounts
an empty dir and the init container can't find data.yaml. The same trap hit
nrc-init (data.yaml), flowable-init (the BPMN) and keycloak (realm import).
Fix: bake the assets into small derived images instead of bind-mounting:
- infra/openzaak/Dockerfile -> register-referentie/openzaak:dev
- infra/opennotificaties/Dockerfile-> register-referentie/opennotificaties:dev
- infra/keycloak/Dockerfile -> register-referentie/keycloak:dev
- flowable-init: build.dockerfile_inline bakes workflows/registratie.bpmn
Base versions stay build args (OPENZAAK_TAG / OPENNOTIFICATIES_TAG), so the
pinning is unchanged. Applied to both the consolidated compose and the
per-service composes, so local Podman and CI use one mechanism — no bind
mounts, no SELinux `:z`, no world-readable requirement.
Verified locally: `podman build` of the OpenZaak and BPMN images produces
the file at the expected in-container path.
Docs: docs/runbooks/gitea-actions-gotchas.md explains the DinD bind-mount
trap and the bake fix; openzaak.md and ci.md point at it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three root-cause fixes for the oz-init CI failure:
1. Smoke timeout: add --wait-timeout 300 to `docker compose up --wait`
so CI has 5 minutes instead of the 60-second default in older Compose
v2 releases (migrations alone take 50 s locally).
2. PostGIS race: the old healthcheck used pg_isready which only checks
TCP connectivity — it passes before the postgis/postgis init scripts
have run SELECT PostGIS_Version(). The new check adds a psql probe so
oz-init does not start until PostGIS is actually available.
3. Remove :z from volume mounts: the SELinux re-label flag is
Podman/Fedora-specific and a no-op (or unexpected) under Docker on
ubuntu-latest; plain :ro is correct for both runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
latest bumped to OpenZaak 1.29.0 (2026-06-18) and open-notificaties
updated (2026-06-22), breaking oz-init in compose-smoke. Pin all four
compose files to stable patch releases:
open-zaak: 1.28.2 (was :latest -> 1.29.0)
open-notificaties: 1.16.1 (was :latest)
Tags are still overridable via OPENZAAK_TAG / OPENNOTIFICATIES_TAG env vars.
Also adds two if: failure() steps to the compose-smoke CI job: one that
dumps the last 100 lines of oz-init / nrc-init / acl / bff logs, and one
that tears the stack down cleanly, so future failures are self-diagnosing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the ACL multi-stage Dockerfile and .dockerignore, and expands
infra/docker-compose.yml from the BFF-only stub to the full development
stack (OpenZaak, NRC, Keycloak, Flowable, ACL, BFF). Without these
files a fresh checkout cannot satisfy `make smoke`'s `docker compose
up --build --wait` step, so `make ci` could never go green.
`make lint && make build && make unit` verified green locally.
`make smoke` requires Docker Compose v2 (`--wait` flag); on this dev box
only podman-compose is available — smoke will be verified on the
respellion-linux CI runner once it is registered (see docs/runbooks/ci.md).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>