Commit Graph

103 Commits

Author SHA1 Message Date
7ef63c7ae9 feat(projection): persist the read projection and expose webhook + read APIs (refs #7)
Add the projection persistence and the two services around it:

- Projection.ReadModel: a shared EF Core (Npgsql) read model owning the projection
  schema — register_projection + the subscriber's processed_notifications log — plus
  EfProjectionStore / EfNotificationLog (atomic record-or-skip on the PK for idempotency)
  and the initial migration. One rebuildable store, written by the subscriber and read
  by projection-api (ADR-0008).
- EventSubscriber.Api: POST /notifications NRC callback (enforces the abonnement bearer,
  401 without it per ADR-0007), POST /admin/rebuild, /health. Migrates on start.
- ProjectionApi.Api: GET /register, GET /register/{id}, /health — the read side.

dotnet-ef pinned as a local tool for migrations; NuGetAuditMode=direct so EF's
design-time-only tooling transitive doesn't flag the shipped build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:55:04 +02:00
017cd5e66b feat(event-subscriber): project zaak-created notifications into the read projection (refs #7)
Implement NotificationProjector: a zaken/zaak/create notification records the delivery
in the notification log (atomic record-or-skip for idempotency, §8.6) and upserts an
INGEDIEND projection row keyed by zaak id; other channels/actions are ignored. Rebuild
clears the projection and replays the log — no OpenZaak access needed (§8.1). bsn/naam
are deferred (ADR-0008).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:48:08 +02:00
c70840e5b7 test(event-subscriber): project zaak-created notifications into the read projection (refs #7)
Failing unit + acceptance tests for the Event Subscriber's NotificationProjector:
a zaken/zaak/create notification yields one INGEDIEND projection row, duplicate
deliveries collapse to one row, non-zaak/non-create notifications are ignored, and
a rebuild repopulates the projection from the durable notification log (PRD §8.4).

The projector is a no-op stub so the tests compile and fail on the assertions; the
implementation follows in the green commit. The notification log doubles as the
idempotency guard and rebuild source so a rebuild needs no OpenZaak access (§8.1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:47:16 +02:00
32c98f00db Merge pull request 'feat(infra): wire OpenZaak → Open Notificaties notifications (closes #56)' (#57) from feat/56-nrc-notification-wiring into main
All checks were successful
CI / lint (push) Successful in 52s
CI / build (push) Successful in 40s
CI / unit (push) Successful in 48s
CI / mutation (push) Successful in 1m35s
CI / verify-stack (push) Successful in 4m44s
Reviewed-on: #57
2026-06-30 12:29:43 +00:00
d49443353e refactor(ci): one verify-stack stage for all live-stack checks (closes #58) (refs #46 #56)
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 40s
CI / unit (pull_request) Successful in 48s
CI / mutation (pull_request) Successful in 1m36s
CI / verify-stack (pull_request) Successful in 4m37s
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>
2026-06-29 16:48:38 +02:00
a256db1a23 arch(infra): ADR-0007 + runbooks for the OZ→NRC notification wiring (refs #56)
All checks were successful
CI / lint (pull_request) Successful in 52s
CI / build (pull_request) Successful in 40s
CI / unit (pull_request) Successful in 49s
CI / mutation (pull_request) Successful in 1m35s
CI / integration (pull_request) Successful in 3m24s
CI / notifications (pull_request) Successful in 3m14s
CI / compose-smoke (pull_request) Successful in 4m2s
Records the wiring decision (AC-delegated auth, required celery-beat) and the two
non-obvious gotchas: single-label hosts aren't URL-valid (reach services by IP) and
abonnement callbacks must enforce auth. Documents the new notifications CI job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:29:12 +02:00
4d07285dcd test(infra): verify-notifications smoke + CI job for the OZ→NRC path (refs #56)
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>
2026-06-29 14:29:12 +02:00
f3e9db7147 feat(infra): wire OpenZaak → Open Notificaties notifications (refs #56)
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>
2026-06-29 14:29:12 +02:00
86cc65f4d9 Merge pull request 'test(acl): ACL integration test against real OpenZaak (closes #46)' (#54) from test/46-acl-openzaak-integration into main
All checks were successful
CI / lint (push) Successful in 50s
CI / mutation (push) Successful in 1m37s
CI / integration (push) Successful in 3m28s
CI / compose-smoke (push) Successful in 3m53s
CI / build (push) Successful in 40s
CI / unit (push) Successful in 48s
Reviewed-on: #54
2026-06-29 10:48:00 +00:00
4474585606 ci(acl): run the ACL integration test in CI inside the compose network (closes #55) (refs #46)
All checks were successful
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 47s
CI / mutation (pull_request) Successful in 1m31s
CI / integration (pull_request) Successful in 3m43s
CI / compose-smoke (pull_request) Successful in 4m1s
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>
2026-06-29 12:28:43 +02:00
3829cb0b68 ci(acl): keep the integration lane local-only; document the runner gap (refs #46)
All checks were successful
CI / lint (pull_request) Successful in 49s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 50s
CI / mutation (pull_request) Successful in 1m31s
CI / compose-smoke (pull_request) Successful in 3m54s
The hosted Gitea runner starts the OpenZaak stack as sibling containers via the
host daemon, so a process on the runner can't reach the published ports — the seed
and dotnet test get Connection refused on localhost:8000. Drop the (non-working)
integration CI job; make integration stays the local / host-runner gate. Document
the limitation in gitea-actions-gotchas.md §5 and the CI runbook, and track running
it inside the compose network in #55. ADR-0006 updated accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:04:44 +02:00
855a5565fe ci(acl): run the ACL integration test as a Gitea Actions job (refs #46)
Some checks failed
CI / lint (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 46s
CI / mutation (pull_request) Successful in 1m37s
CI / build (pull_request) Successful in 41s
CI / integration (pull_request) Failing after 5m16s
CI / compose-smoke (pull_request) Successful in 4m11s
New integration job: setup-dotnet + make integration (stack up, OZ_PUBLISH=1 seed,
Integration-category tests, tear down), with on-failure log dump + teardown like
compose-smoke. Documents the job and the new make target in the CI runbook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:43:38 +02:00
09de500fb8 arch(acl): ADR-0006 — provision the ACL integration test against the compose stack (refs #46)
Records why the integration test targets the running compose stack rather than a
Testcontainers graph (no .NET compose support; not hermetic anyway due to the
Selectielijst dependency), the opt-in publish seed, and the chunked-body bug the
test caught. Proposed in #53.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:43:38 +02:00
4322c607cb fix(acl): buffer the zaak POST body so OpenZaak accepts it (refs #46)
OpenZaak runs behind uwsgi, which rejects a chunked request body with 400.
JsonContent streams without a Content-Length (Transfer-Encoding: chunked), so
buffer it first. Only a real OpenZaak surfaces this — the integration test from
the previous commit now passes. A unit test asserts a Content-Length is sent
(captured before the stub reads/buffers the body), guarding the fix in the fast
lane and killing the Stryker mutant that would otherwise survive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:43:27 +02:00
d0582cef65 test(acl): integration test opens a real zaak against OpenZaak (refs #46)
S-04a: the deferred S-04 acceptance criterion. A gated Acl.IntegrationTests
project (Category=Integration) drives the real OpenZaakGateway against the
running compose stack — real ZGW JWT auth and the real POST /zaken contract a
stubbed HttpMessageHandler cannot exercise. The lane is kept out of the fast
checks: make unit filters Category!=Integration, Stryker is pinned to Acl.Tests,
and a new make integration target brings the stack up, seeds a published zaaktype
and tears down.

Red: against real OpenZaak the gateway POST fails 400 — JsonContent streams the
body chunked and OpenZaak's uwsgi rejects it. Fixed in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:43:17 +02:00
f2e575b427 feat(infra): publish the BIG zaaktype on demand via OZ_PUBLISH (refs #46)
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>
2026-06-29 11:43:05 +02:00
fd5fa5ac3c Merge pull request 'test(acl): ACL mutation-score baseline with Stryker.NET (closes #47)' (#52) from feat/47-acl-mutation-baseline into main
All checks were successful
CI / lint (push) Successful in 50s
CI / build (push) Successful in 41s
CI / unit (push) Successful in 48s
CI / mutation (push) Successful in 1m25s
CI / compose-smoke (push) Successful in 4m1s
Reviewed-on: #52
2026-06-29 09:00:29 +00:00
5f3dd31925 fix(ci): pin upload-artifact to @v3 — @v4 refuses to run on Gitea (refs #47)
All checks were successful
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 46s
CI / unit (pull_request) Successful in 43s
CI / mutation (pull_request) Successful in 1m49s
CI / compose-smoke (pull_request) Successful in 4m2s
The artifact step failed the mutation job: upload-artifact@v4 bundles
@actions/artifact v2, which hard-aborts on any non-github.com server ("not
supported on GHES"), even though Gitea 1.25 stores artifacts fine. @v3 uses the
older protocol Gitea speaks and has no GHES guard — a drop-in swap (same inputs).
Document it as gotcha §4 and correct the CI runbook note.

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:28:07 +02:00
347713766e ci(acl): publish the Stryker HTML report as a CI artifact (refs #47)
Some checks failed
CI / lint (pull_request) Successful in 52s
CI / build (pull_request) Successful in 41s
CI / unit (pull_request) Successful in 47s
CI / mutation (pull_request) Failing after 1m50s
CI / compose-smoke (pull_request) Successful in 3m55s
Add an upload-artifact step to the mutation job so the ACL mutation report is
downloadable from the run summary. `if: always()` uploads it even when the
ratchet fails — exactly when the survivors matter. A glob handles Stryker's
timestamped output directory. First use of actions/upload-artifact (@v4, pinned);
Gitea 1.25.x supports it. Document it in the CI runbook.

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:21:26 +02:00
7ecc184111 arch(acl): ADR-0005 adopt Stryker.NET for mutation testing (refs #47)
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 45s
CI / mutation (pull_request) Successful in 1m24s
CI / compose-smoke (pull_request) Successful in 4m1s
Record the decision to adopt Stryker.NET (pinned local tool, solution mode on
Acl.slnx) and to set the first repo-wide mutation baseline on the ACL: observed
95%, enforced break threshold 90%. Document the ratchet, local run, and report
location in the CI runbook; add the ADR to the docs nav.

Proposed in #51 (adr-proposal). Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:59:41 +02:00
e8510bf9c3 ci(acl): run the mutation ratchet as a parallel CI job (refs #47)
Add a `mutation` job mirroring the unit job (checkout + pinned setup-dotnet,
then `make mutation`). It runs in parallel with lint/build/unit/compose-smoke
and gates merges on the ACL mutation baseline (CLAUDE.md §5/§15). The job calls
the same make target developers run, so the pipeline stays a mirror of `make ci`.

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:58:22 +02:00
6ac2fca384 test(acl): add Stryker config + mutation make target recording the 95% baseline (refs #47)
Configure Stryker.NET for the ACL in solution mode (Acl.slnx), so both
Acl.Application and Acl.Infrastructure — the two projects under test — are
mutated while Acl.Api (untested) is skipped. Record the repo-wide mutation
baseline as the ratchet (CLAUDE.md §5): observed score 95%, enforced break
threshold 90% (one-mutant headroom over the ~20-mutant surface). The ACL is the
first service with branching logic, so it sets the baseline; later slices
ratchet it up deliberately, never down.

Add a `mutation` make target (`dotnet tool restore` + `dotnet stryker`) and wire
it into the `make ci` aggregate, keeping `make ci` an exact mirror of the
pipeline.

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:58:00 +02:00
10816f5303 test(acl): kill surviving mutants — assert CRS headers, guards, error paths, JWT claims (refs #47)
Stryker exposed thin ACL tests (35% mutation score): the suite never asserted
the geo CRS headers, the ArgumentNullException guards, the non-success and
empty-body error paths, or the structure of the minted ZGW JWT — so mutating
any of those survived.

Strengthen the unit tests to kill those mutants:
- assert Accept-Crs / Content-Crs are EPSG:4326,
- assert OpenZaakAsync rejects a null request/registration without calling out,
- assert a non-2xx response throws and an empty body throws InvalidOperationException,
- decode the Bearer token and assert the HS256 header + acl identity claims.

Raises the ACL mutation score to 95%. The one remaining survivor mutates only
the exception *message* text (an equivalent mutant — message strings are not
worth a brittle assertion).

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:57:51 +02:00
89b097d015 build(acl): pin Stryker.NET as a local dotnet tool (refs #47)
Add a tool manifest pinning dotnet-stryker 4.15.0 so `make mutation` runs
the same mutation tester locally and in CI from a fresh clone (`dotnet tool
restore`), with no global install. Ignore the generated StrykerOutput/ report
directory.

Refs #47.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:57:38 +02:00
5a83216395 Merge pull request 'ci(infra): Gitea Actions CI pipeline + full-stack compose smoke (closes #30)' (#50) from ci/30-gitea-actions-ci into main
All checks were successful
CI / lint (push) Successful in 49s
CI / build (push) Successful in 43s
CI / unit (push) Successful in 46s
CI / compose-smoke (push) Successful in 3m54s
Reviewed-on: #50
2026-06-25 12:34:43 +00:00
f9e123dfcb docs(infra): tighten gitea-actions-gotchas, add local compose (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 41s
CI / unit (pull_request) Successful in 49s
CI / compose-smoke (pull_request) Successful in 4m0s
Restructure for scannability: a shared root-cause intro, a quick-reference
table (gotcha → fix → where), and consistent Symptom/Why/Fix sections with
tighter prose. Documents infra/docker-compose.local.yml as the no-make/Windows
path and drops the now-stale "no bind mounts remain" line (the local compose
uses them, which is fine locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:00:51 +02:00
e87113da24 feat(infra): add bind-mount local compose for no-make/Windows dev (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 51s
CI / compose-smoke (pull_request) Successful in 3m59s
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>
2026-06-25 12:00:32 +02:00
dda4c58e1c fix(infra): portable health poll instead of compose --wait (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 48s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 44s
CI / compose-smoke (pull_request) Successful in 3m56s
`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>
2026-06-25 10:57:52 +02:00
b349dff496 refactor(infra): use upstream images verbatim, seed config via docker cp (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 49s
CI / build (pull_request) Successful in 44s
CI / unit (pull_request) Successful in 44s
CI / compose-smoke (pull_request) Successful in 4m15s
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>
2026-06-25 10:22:14 +02:00
6d8e1d0830 refactor(infra): bake config via dockerfile_inline, drop Dockerfile files (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 46s
CI / compose-smoke (pull_request) Successful in 4m7s
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>
2026-06-25 09:32:35 +02:00
a0aa22c80b fix(infra): smoke waits on durable services, not the whole project (refs #30)
All checks were successful
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 50s
CI / compose-smoke (pull_request) Successful in 4m52s
Run 28 got the full stack healthy but `compose-smoke` still failed. The last
compose line before the error was:

  container infra-flowable-init-1 exited (0)

`docker compose up --wait` treats a service that exits as a failure of the
"stay running" condition unless something depends on it via
`service_completed_successfully`. oz-init/nrc-init are fine (openzaak/nrc-web
depend on them), but flowable-init deploys the BPMN and exits 0 with no
dependant, so whole-project `--wait` failed the instant it finished — even
though everything else was healthy and nrc-init now exits 0.

Smoke now:
  1. `up -d` starts the full stack (one-shots run + deploy as before), then
  2. `up -d --wait <WAIT_SVCS>` waits only for the durable health-checked
     services (openzaak nrc-web acl bff).

Also drops the external `curl localhost:8080/health`: the containerized CI
runner can't reach published host ports at localhost, and each service's
healthcheck already runs inside its container — so `--wait` succeeding IS the
smoke. Documented in docs/runbooks/gitea-actions-gotchas.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:03:37 +02:00
12049a0f35 fix(infra): nrc-init runs migrations only, not setup_configuration (refs #30)
Some checks failed
CI / lint (pull_request) Successful in 49s
CI / build (pull_request) Successful in 43s
CI / unit (pull_request) Successful in 47s
CI / compose-smoke (pull_request) Failing after 3m34s
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>
2026-06-24 13:18:32 +02:00
9ff7937055 fix(infra): bake config into images so compose-smoke passes on CI (refs #30)
Some checks failed
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 40s
CI / unit (pull_request) Successful in 45s
CI / compose-smoke (pull_request) Failing after 3m31s
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>
2026-06-24 13:06:56 +02:00
88de47d1bb fix(infra): harden oz-db healthcheck and raise compose-up timeout (refs #30)
Some checks failed
CI / lint (pull_request) Successful in 52s
CI / build (pull_request) Successful in 44s
CI / unit (pull_request) Successful in 45s
CI / compose-smoke (pull_request) Failing after 1m53s
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>
2026-06-24 11:55:15 +02:00
8528664660 fix(infra): pin OpenZaak/NRC image tags; add smoke log capture on failure (refs #30)
Some checks failed
CI / lint (pull_request) Successful in 55s
CI / build (pull_request) Successful in 44s
CI / unit (pull_request) Successful in 46s
CI / compose-smoke (pull_request) Failing after 1m27s
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>
2026-06-24 09:46:33 +02:00
f32fc4e8c0 ci(infra): switch runner label to ubuntu-latest (refs #30)
Some checks failed
CI / lint (pull_request) Successful in 1m27s
CI / build (pull_request) Successful in 48s
CI / unit (pull_request) Successful in 47s
CI / compose-smoke (pull_request) Failing after 3m47s
Self-hosted respellion-linux runner not required — Gitea's hosted
ubuntu-latest runner has Docker + Compose v2 out of the box, so
make smoke works without any manual registration step.

Updates docs/runbooks/ci.md to reflect the new runner label and
removes the act_runner self-hosted setup as the primary path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 09:32:38 +02:00
eaca611842 ci(infra): ACL Dockerfile + full compose stack for smoke test (refs #30)
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / unit (pull_request) Has been cancelled
CI / compose-smoke (pull_request) Has been cancelled
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>
2026-06-24 09:25:53 +02:00
28041228bd test(acl): BDD acceptance scenario for opening a zaak (closes #5) (#49)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-08 11:25:26 +00:00
4b2af5c635 feat(acl): ACL skeleton — OpenZaak default-fill (refs #5) (#45)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-04 07:50:45 +00:00
71b76a0ef9 feat(workflow): Flowable + registratie.bpmn external task (closes #4) (#44)
Some checks failed
CI / lint (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
2026-06-04 07:16:48 +00:00
c904c64597 feat(infra): Keycloak with four mock realms (closes #3) (#43)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 14:16:49 +00:00
195a76aaf2 feat(infra): Open Notificaties up + shared network (closes #2) (#42)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 13:59:02 +00:00
0409eb42c5 feat(infra): seed BIG catalogus + JWT client for OpenZaak (refs #2) (#41)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 13:42:44 +00:00
8c5bbe05a9 feat(infra): OpenZaak + Postgres + Redis up in compose (refs #10) (#40)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 13:16:31 +00:00
e85774d482 docs: MkDocs scaffold + ADR-0001 + README quickstart (closes #32) (#39)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 12:13:32 +00:00
ada2e807a3 chore: contributor workflow — templates, git-cliff, gitea-workflow doc (closes #31) (#38)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 12:09:24 +00:00
d4a89e6e62 ci: Gitea Actions pipeline + runner runbook (refs #30) (#37)
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled
CI / unit (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
2026-06-03 12:04:19 +00:00
dfd6224fea feat(infra): containerize BFF + compose-up smoke (closes #29) (#36) 2026-06-03 11:46:27 +00:00
7d67ecbde1 chore: remove bootstrap scripts from main (#35) 2026-06-03 11:40:14 +00:00
dfbaf7640a feat(bff): placeholder BFF + /health endpoint (closes #28) (#34) 2026-06-03 11:38:08 +00:00