Commit Graph
43 Commits
Author SHA1 Message Date
not 9d7e8e5b65 ci(k8s): gate the Helm chart in CI + a compose↔chart drift check (closes #168) (#171)
CI / k8s (push) Successful in 5s
CI / lint (push) Successful in 1m27s
CI / build (push) Successful in 1m22s
CI / unit (push) Successful in 1m12s
CI / frontend (push) Successful in 2m7s
CI / mutation (push) Successful in 3m9s
CI / verify-stack (push) Successful in 6m20s
## What & why

The Helm chart landed in #167 with two gaps written into ADR-0033: `make k8s-lint` existed
but no CI job ran it, and *"a second deployment description to keep in step with compose —
nothing enforces that today; a drift check belongs in CI (follow-up)"*. Both are closed here.

**`make k8s-drift`** (`infra/helm/check-drift.py`, stdlib only) compares what each stack
actually deploys rather than diffing two files that differ by design: workload names and
resolved container images, taken from `docker compose config --format json` and a rendered
chart. The six differences that exist today are declared in `DEVIATIONS` with the reason
each was forced — the four `*-init` Django services folded into their web pods, and the two
bootstrap Jobs compose runs from the host — so only a *new* difference fails.

**A `k8s` CI job** runs `k8s-lint` then `k8s-drift` on every push and PR. No cluster, no
marketplace action: helm is fetched as the pinned static binary the Talos runbook already
gives developers.

Closes #168

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation — the red commit reports all six
      real differences; the green commit declares them.
- [x] Implementation makes the test pass.
- [x] Conventional Commits referencing the issue (`refs #168`).
- [x] CI green — awaiting the run on this PR (`make k8s-lint` and `make k8s-drift` pass locally).
- [x] `docker compose up` unaffected — no service, image or compose file is touched.
- [x] Docs updated — `docs/runbooks/ci.md` (job table + the one place local and CI now
      differ), `docs/runbooks/kubernetes-talos.md` §7/§"not ported", and ADR-0033's cost note.
- [x] No ADR needed: no new dependency (python stdlib, and helm/docker were already
      prerequisites of the `k8s-*` targets), no boundary moved, no §8 rule bent.
- [x] Not user-visible, so no demo note.

## Notes for reviewers

Verified by hand that both drift classes fail the check, not just that it passes today:

- bumping `OPENZAAK_TAG` in compose alone → reports `openzaak` and `oz-celery` with both
  image strings;
- adding a workload to `values.yaml` alone → reports it by name.

Deliberate limits (there is a `ponytail:` note in the script):

- **Names and images only**, as sets — no per-workload env, ports or volumes. Those differ
  by design in four documented places, so comparing them would mean re-encoding every
  deviation field by field for very little more signal.
- **The three observability workloads are rendered with `enabled=true`** by the check, even
  though both stacks default them off, so their images can't drift unwatched.
- **`k8s-lint`/`k8s-drift` are not in `make ci`**, to avoid making `helm` a hard
  prerequisite for everyone. That is now the only local/CI difference; it's called out in
  `docs/runbooks/ci.md`.

Follow-ups filed while reviewing the chart, not addressed here: #169 (the published docs
omit every ADR after 0010 and all runbooks but `ci.md`) and #170 (the production-posture
ADR #25 asked for — secrets are still plain text in `values.yaml`).Reviewed-on: #171
2026-09-18 13:25:24 +00:00
not 17f1f2f809 docs(nav): publish every ADR and runbook, gated by a nav check (closes #169) (#172)
CI / lint (push) Successful in 1m46s
CI / build (push) Successful in 1m29s
CI / unit (push) Successful in 1m5s
CI / frontend (push) Successful in 1m41s
CI / mutation (push) Successful in 3m30s
CI / verify-stack (push) Successful in 5m50s
## What & why

`docs/` is the source of truth (CLAUDE.md §12), but only pages listed in `mkdocs.yml`'s nav
are published — and mkdocs' own `validation.nav.omitted_files: warn` keeps the build green
while dropping the rest. So the site had quietly stopped at **ADR-0010** and
**`runbooks/ci.md`**: 31 pages, including every ADR from 0011 to 0034, six of the seven
runbooks, and `synthetic-data.md`, existed in the repo and nowhere else.

- `infra/check-docs-nav.py` fails when a page under `docs/` is not in the nav. It runs in
  `make lint`, so the existing CI job gates it — python3 only, no new tooling, and no
  mkdocs install needed to check it.
- The nav now lists all 34 ADRs, all 7 runbooks and `synthetic-data.md`.
- ADR-0033's `Slice:` header said "none yet"; #25 closed it.
- The landing page gained a pointer to the Talos runbook.

Closes #169

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the fix — the red commit lists all 31 missing pages.
- [x] Implementation makes the test pass.
- [x] Conventional Commits referencing the issue (`refs #169`).
- [ ] CI green — awaiting the run on this PR (`python3 infra/check-docs-nav.py` passes locally;
      `make lint` also needs the .NET SDK, which CI has).
- [x] `docker compose up` unaffected — docs and `mkdocs.yml` only, plus one `make lint` line.
- [x] Docs updated — that is the change.
- [x] No ADR needed: no dependency, no boundary, no §8 rule touched.
- [x] Not user-visible, so no demo note.

## Notes for reviewers

- The check is a **substring test**, not a YAML parse (there's a `ponytail:` note in the
  script): a page's path either appears in `mkdocs.yml` or it doesn't. That keeps it
  dependency-free — `mkdocs.yml` can't be read by `yaml.safe_load` anyway, it carries a
  `!!python/name:` tag for the mermaid fence. It does not check that an entry *points at a
  file that exists*; mkdocs' `not_found: warn` covers that direction.
- ADR labels in the nav are shortened by hand (`"ADR-0013: Behandel-portal wiring"`), since
  several H1s are a full sentence.

**Known gap, not fixed here:** CLAUDE.md §12 says the site is "published via a Gitea Actions
workflow to Gitea Pages", and no such workflow exists — `mkdocs build` is never run, by CI or
by any make target. Gitea has no built-in Pages, so publishing needs a decision (a
`gitea-pages` server, an artifact, or a static host) rather than a patch. Worth its own issue
if the published site is actually wanted; until then this PR makes the nav correct for whoever
runs `mkdocs serve`.Reviewed-on: #172
2026-09-18 12:55:20 +00:00
not 1dd8bd4e1b S-24/#25 · Helm chart + Kubernetes deployment, and Caddy for the portals (#166) (#167)
CI / lint (push) Successful in 1m17s
CI / build (push) Successful in 1m12s
CI / unit (push) Successful in 1m26s
CI / frontend (push) Successful in 2m58s
CI / mutation (push) Successful in 9m1s
CI / verify-stack (push) Successful in 8m53s
## What & why

Two changes, made and verified together on a real cluster.

**S-24 / #25 — a Helm chart for the platform.** One chart, `infra/helm/big-reference`,
whose `values.yaml` is a near-literal transcription of `infra/docker-compose.yml`, rendered
by three generic templates (Deployment, Job, Service) over a `workloads` map. Adding a
service is a values edit. `make k8s-lint` renders and schema-checks the whole stack without
a cluster. The issue asked for a *sketch*; this is deployed and verified end to end (see
below), which is more than it asked for — the part it asked for that is **not** here is the
production-posture write-up (HA, secrets, backup), see Known gaps.

**#166 — Caddy replaces nginx in the portals.** nginx resolves a variable `proxy_pass`
upstream itself, using only the `resolver` directive and never `/etc/resolv.conf`'s search
domains. That had cost two workarounds in one script: rewriting the resolver address for
rootless podman, and injecting a full FQDN so the bare `bff` name could resolve on
Kubernetes. Caddy dials per request through the system resolver, so `reverse_proxy
bff:8080` works on every engine unchanged; `apps/portal-nginx-resolver.sh` and the chart's
`BFF_HOST` env are deleted.

Closes #25
Closes #166

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation — twice: the Caddyfile contract test
      before the Caddyfiles, `make k8s-lint` before the chart.
- [x] Implementation makes the test pass.
- [x] Conventional Commits referencing the issues (`refs #25` / `refs #166`).
- [ ] CI green — awaiting the run on this PR (`make k8s-lint`, `dotnet format` and the new
      unit self-check pass locally; the compose e2e and mutation lanes are CI's).
- [ ] `docker compose up` from a fresh clone reaches green health checks within 3 minutes —
      the portal images were rebuilt and verified standalone, but a full `make up` run has
      not been done on this branch. Please confirm in review or let CI's smoke test speak.
- [x] Docs updated — `docs/runbooks/kubernetes-talos.md` (new), `frontend-decisions.md`,
      `demo-script.md`, and the docs that named nginx.
- [x] ADR added — ADR-0033 (chart) and ADR-0034 (Caddy).
- [ ] Demo note in `docs/demo-script.md` — not added: the deployment target is not a
      user-visible slice, and the Caddy swap is invisible to the demo script beyond the
      wording fix included here.

## How it was verified

Brought up from scratch on a single-node Talos v1.14.0 VM (6 vCPU / 10 GB, virtio disk)
under virt-manager: **29 pods ready and four bootstrap Jobs complete in under three
minutes, zero restarts**, using ~4.4 GB of the VM's 10 GB.

- Full Common Ground path: portal Caddy → BFF → domain → Flowable → ACL → OpenZaak +
  Objecten → NRC → event-subscriber → projection → public register (`INGEDIEND`, reference
  matching the submitted registration).
- Werkbak read with an MFA'd medewerker token → 200.
- The browser flow driven with Playwright against `http://localhost:30140`: secure context,
  `crypto.subtle` present, Keycloak form reached, login completed, **no console errors**.
- Routing checked against a stub BFF: SPA fallback serves deep links, each portal proxies
  its own groups, and a portal does *not* proxy a neighbour's group.

## Notes for reviewers

Three bugs this shook out, each fixed at the cause rather than the symptom:

1. **`command` vs `args`.** Compose's `command:` replaces the image CMD; Kubernetes'
   replaces the ENTRYPOINT. Transcribing one to the other broke every upstream image that
   relies on its entrypoint — postgres refused to run as root, Keycloak tried to exec
   `start-dev`. The chart now `fail`s at render time on `command`.
2. **Concurrent migrations.** Both `/setup_configuration.sh` and `/start.sh` run
   `manage.py migrate`; compose serialises them with `depends_on`, Kubernetes has no such
   edge, so the init Job and its web pod raced (`relation "zgw_consumers_service" already
   exists`). The four Django services now do both steps in order in the web pod — which
   also deletes four workloads.
3. **`emptyDir` databases are wiped by any pod-template change.** `make k8s-reseed` now
   also restarts `event-subscriber` and `projection-api`, which create the projection
   schema on start and otherwise keep writing to a schema-less database.

Known gaps / follow-ups:

- **Secrets.** `values.yaml` carries the dev credentials in plain text (`admin/admin`, the
  ZGW client secret, the two Objecten tokens) and the chart has no `Secret` objects. Fine
  for a laptop demo, and exactly what #25's "production posture" ADR should address — I
  suggest a follow-up issue rather than stretching this PR.
- **No CI gate for the chart yet.** `make k8s-lint` exists but is not wired into
  `.gitea/workflows/ci.yaml`, and nothing enforces that the chart and the compose file stay
  in step. Worth a small follow-up.
- **This is two slices in one PR.** They were built and verified together and the diff is
  entangled (the chart was written against Caddy from the start), so splitting now would
  mean re-creating an nginx-shaped chart to throw away. Happy to split if you'd rather.
- **Rebased onto #161** (merged as #165) rather than merged, to keep the history linear.
  One conflict, in the `unit:` target where both branches add a self-check line — resolved
  by keeping both. #161's `infra/host-browser.yml` arrived with
  `/usr/share/nginx/html/config.json` and is fixed to `/usr/share/caddy/` inside the
  `feat(portals)` commit, so no commit on this branch leaves that overlay pointing at a
  path the images no longer have.Reviewed-on: #167
2026-09-10 08:53:58 +00:00
not d6b3f9764f fix(e2e): bound the Playwright run and make a failed login say why (closes #161) (#165)
CI / build (push) Successful in 1m9s
CI / lint (push) Successful in 1m27s
CI / unit (push) Successful in 1m32s
CI / frontend (push) Successful in 3m19s
CI / mutation (push) Successful in 6m24s
CI / verify-stack (push) Successful in 9m33s
## What & why

#161 is really two defects, and the second one is why the first was undiagnosable.

**A wedged suite consumed the job, and took the post-mortem with it.** Nothing bounded the
Playwright run, so CI stopped the job mid-suite — and `if: always()` does not survive that. Run
739's job metadata shows every step after the e2e as a **0-second failure** stamped at the kill:

```
14 failure  09:48:17 -> 10:14:54  Self-service e2e (Playwright …)
15 failure  10:14:54 -> 10:14:54  verify-stack check summary       ← if: always()
16 failure  10:14:54 -> 10:14:54  e2e spec summary                 ← if: always()
17 failure  10:14:54 -> 10:14:54  Dump container logs on failure   ← if: failure()
18 failure  10:14:54 -> 10:14:54  Tear down                        ← if: always()
```

So the per-spec summary, the container-log dump and the teardown never ran, and the log lost
whatever the killed process had buffered — leaving the single `✘` line the issue was filed from.
`globalTimeout` now makes Playwright stop and *report*: the JSON report is written and those steps
still get their turn. (A `timeout-minutes` on the job would have reproduced the same failure, so
there isn't one.) The "~24-minute gap" is that kill, not necessarily a hang — note run 739 shows
`run_attempt: 2`, and `concurrency.cancel-in-progress` kills an in-flight run on any re-run or push.

**A login that never got its form ate the 90-second test timeout.** Playwright actions auto-wait
until the *test* timeout, not `expect.timeout` — so a portal that serves its page but never
bootstraps (its `config.json` fetch or the OIDC discovery behind `authorize()` failed; `main.ts`
only `console.error`s) spent 90s to report `locator.fill: Test timeout of 90000ms exceeded`: the
symptom, not the cause. That is catalogus.spec's 1.8 minutes. Both Keycloak forms are now asserted
visible first, with a 20s budget and a message naming the step that never happened.

Verified against a real blank-bootstrap portal — the beheer image served with a `config.json` that
is not JSON — which fails in **20.2s** with *"the Keycloak login form never appeared — the portal
did not reach Keycloak (check its config.json fetch and the OIDC discovery …)"*.

**And the summary now says why.** The per-spec table (#136) rendered a verdict icon and nothing
else, so even a surviving summary cost a log dive. Failing specs now carry their first error,
flattened for a table cell (ANSI stripped, newlines collapsed, `|` escaped, clipped) — shape
verified against a real @playwright/test 1.61 failing report, with a stdlib assert self-check on
`make unit`.

Closes #161

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation.
- [x] Implementation makes the test pass; refactor commit follows (login helper dedup).
- [x] Conventional Commits referencing the issue (`refs #161`).
- [ ] CI green — all Gitea Actions jobs.
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (untouched).
- [x] Docs updated — `docs/runbooks/gitea-actions-gotchas.md` §9.
- [x] ADR — not needed: no boundary, dependency or coupling rule touched (test/CI infra only).
- [x] Demo note — not applicable: nothing user-visible.

## Notes for reviewers

**What this does not do: identify why the beheerder login failed that once.** The evidence to do
that was destroyed by defect 2, which is what this PR fixes. The suite ran green here five times
today (catalogus.spec 1.1–5.3s each) — but a local box is not the loaded CI runner, so that is weak
evidence and I am not claiming the flake is gone. What changes is that the next occurrence is
bounded and self-describing: it fails in 20s naming the failing step, the JSON report survives, and
the summary prints the error. Please keep #161 in mind rather than treating this as proof.

**Two follow-ups I did not pull into this PR:**
- *All four portals show a permanently blank page if their startup fetch fails* — `main.ts` does
  `fetch('config.json').then(bootstrap).catch(console.error)`, one shot, no UI and no recovery. That
  is a real product gap (the deliberately-broken portal above is exactly what a user would see) and
  wants its own slice, not a test-infra PR.
- `retries: 1` is untouched. CLAUDE.md §15 says flaky tests are fixed rather than retried, but
  removing retries while a real flake is unexplained would trade a rare red for a frequent one.
  Worth revisiting once #161 recurs (or doesn't) with the new diagnostics.

The login-helper rename (`medewerker-login.ts` → `keycloak-login.ts`, citizen logins routed through
`loginBurger`) is its own no-behaviour-change commit: the three citizen specs each duplicated the
same three-line login, so guarding the login path once meant routing them through it first.Reviewed-on: #165
2026-09-04 10:53:35 +00:00
not 2125fb0cfd feat(infra): Objecten publishes register events to NRC (closes #152) (#154)
CI / build (push) Successful in 1m14s
CI / lint (push) Successful in 1m28s
CI / unit (push) Successful in 1m28s
CI / frontend (push) Successful in 3m8s
CI / mutation (push) Successful in 6m35s
CI / verify-stack (push) Successful in 9m27s
## What & why

S-19b-1. A write to the Objecten API now produces a **delivered** notification on the
`objecten` kanaal in Open Notificaties. ADR-0028 switched Objecten's notifications off on
purpose — there was no broker, worker, kanaal or abonnement, so wiring only the client side
would have dropped every message on the floor. This slice builds the real path and turns it
back on.

- `objecten-celery` worker (mirrors `oz-celery`) + `CELERY_BROKER_URL`/`RESULT_BACKEND` on
  objecten-redis db 1 (db 0 is already the cache). `notifications_api_common` only *queues*
  the send; without a worker every register write is silently undelivered.
- `nrc` service + `notifications_config` in Objecten's `setup_configuration`, reusing the
  `big-reference-seed` credential OpenZaak publishes with (NRC authorizes it via OpenZaak's
  AC, which grants it `heeft_alle_autorisaties` — no second credential needed).
- The `objecten` kanaal in NRC's `setup_configuration`. The name is fixed by the Objects API
  (`NOTIFICATIONS_KANAAL`), not chosen here; publishing to an unregistered kanaal is exactly
  what the red check reported first.
- `NOTIFICATIONS_DISABLED: "false"` in both compose files.
- Writers address Objecten as `objecten.local` — see *Notes for reviewers*.
- `make verify-objecten-notifications` — registers an abonnement on `objecten` pointing at a
  throwaway sink, writes a `RegisterRecord` exactly as the ACL does on approval, asserts the
  delivery. One assertion covering the whole chain: Objecten -> objecten-celery -> NRC ->
  nrc-beat -> callback. Wired into the CI `verify-stack` job and the summary table.

**ADR-0029** records the decisions; ADR-0028's ceiling now points at it.

Closes #152

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation (dc9ca2c, red at the first hop:
      `NRC POST /api/v1/abonnement -> 400 "Kanaal met deze naam bestaat niet."`).
- [x] Implementation makes the test pass (4488962, + two fixes found by CI, below).
- [x] Conventional Commits referencing the issue (`refs #152`).
- [x] CI green — all six jobs on a5fd47e, including `verify-stack` end to end (e2e included).
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes
      (`verify-stack`'s bring-up step).
- [x] Docs updated — ADR-0029 added, ADR-0028's ceiling annotated, BACKLOG.md split.
- [x] ADR added in `docs/architecture/`.
- [x] Demo note in `docs/demo-script.md` if user-visible — n/a, infrastructure only; nothing
      consumes the kanaal until S-19b-2 (#153).

## Notes for reviewers

**The one genuinely non-obvious bit: writers address Objecten as `objecten.local:8000`, not
`objecten:8000`.** NRC types a notification's `hoofdObject`/`resourceUrl` as DRF `URLField`,
so Django's `URLValidator` runs on them — and it rejects a **single-label** host. Objecten
fills both from the object url DRF built with `request.build_absolute_uri`, i.e. *the Host
the caller used*. Writing via the plain service name returns 201 and then fails every
publish in the background, forever, with

```
400 {"hoofdObject":["Voer een geldige URL in."],"resourceUrl":["Voer een geldige URL in."]}
```

So the `objecten` service carries an `objecten.local` network alias and every writer uses it
— `Acl__Objecten__BaseUrl`, `ObjectenGatewayIntegrationTests`, this slice's verify driver.
An alias rather than a bare dotted `SITE_DOMAIN` so the host still *resolves*: a subscriber
following `resourceUrl` reaches the record, which S-19b-2 will do. Readers keep the plain
name. Same class of constraint as ADR-0028's Objecttypen base-URL rule.

**Ceiling, stated in the ADR:** nothing enforces the alias — a future writer using
`objecten:8000` gets a 201 and silently no notification. If a second writer ever appears,
rename the compose service rather than adding a lint.

**Two CI-only failures on the way here**, both worth knowing:
1. `SITE_DOMAIN` was my first guess at the mechanism and is simply not what builds those
   URLs — dropped in d76abf2.
2. The check correlated the delivery on the `reference` inside the record it wrote. An NRC
   notification carries `kanaal`/`resource`/`kenmerken`/`hoofdObject`/`resourceUrl` and
   **never the record data**, so it correlates on the object URL now (a5fd47e).

**Cost:** one more long-running container on the memory-tight runner. It inherits the capped
`UWSGI_PROCESSES: "1"` env, which the celery command ignores; if `verify-stack` gets tight
again, celery concurrency is the next knob.

**Follow-up:** S-19b-2 (#153) sources the projection from these events. Nothing subscribes to
the `objecten` kanaal in the product yet — only the verify check does.Reviewed-on: #154
2026-08-28 10:11:54 +00:00
not d37d4c96c6 S-18c · RegisterRecord objecttype defined + registered (closes #141) (#146)
CI / build (push) Successful in 1m29s
CI / lint (push) Successful in 1m37s
CI / unit (push) Successful in 2m13s
CI / frontend (push) Successful in 4m31s
CI / mutation (push) Successful in 7m0s
CI / verify-stack (push) Successful in 8m52s
## What & why

S-18c, the **final** slice of the S-18 (#19) split (after S-18a #142, S-18b #143). Defines the **RegisterRecord** objecttype — the schema S-19 (#20) will write canonical register records against on approval — and registers it in the Objecttypen API at startup.

Closes #141

### What

- **Schema** (`infra/objecttypen-registerrecord/registerrecord.schema.json`): public-safe by construction — `id`, `status` (enum `INGEDIEND`/`INGESCHREVEN`), `reference` only, `additionalProperties: false`, `dataClassification: open`. Mirrors the BFF's `OpenbaarEntry` — **no `bsn`/`naam`** (ADR-0027).
- **Registration**: a `registerrecord-init` compose one-shot (stdlib Python on the stack network) POSTs the objecttype + a **published** version over the API once Objecttypen is healthy. The Objecttypen `setup_configuration` (3.4.2) only provisions tokens — no declarative objecttype step — so this follows the ADR-0020 self-seed pattern. **Idempotent**: if a `RegisterRecord` with a version already exists it is a no-op.
- **Wiring**: schema + `register.py` streamed into the external `rr-registerrecord-config` volume by `seed-config.sh registerrecord` (main) / bind-mounted (local); added to `SEED`, `CFG_VOLS`, and the CI log-dump. `registerrecord-init` is a one-shot (not in `WAIT_SVCS`).
- **Smoke**: `verify-registerrecord` (`run-registerrecord-check.sh` + `registerrecord-check.py`) asserts the objecttype exists, has a **published** version, and that version's schema carries `id`/`status`/`reference`; added as a verify-stack step + a row in the #136 summary.
- **ADR-0027**: records the public-safe schema decision (mirror the BFF public view, not the internal projection; API-seeded one-shot). The slice issue #141 flagged the schema as ADR-worthy, so no separate adr-proposal issue was opened.

## Verified locally (end to end, real compose)

Seeded `rr-registerrecord-config`, brought Objecttypen up, ran `registerrecord-init` → `registered RegisterRecord <uuid> v1 (published)`. `make verify-registerrecord` → **OK — RegisterRecord v1 published, fields=['id', 'reference', 'status']**. Re-running the one-shot → **no-op** (idempotent). `docker compose config` clean on both files; schema + script + ci.yaml validated.

## Definition of Done

- [x] Failing smoke committed first (`test(infra): …`, "no objecttype named RegisterRecord"); implementation makes it pass.
- [x] Conventional Commits referencing #141.
- [x] CI green (verify-stack registerrecord step — validated locally; runner already unstarved by #145).
- [x] `docker compose up` reaches health (one-shot registers after Objecttypen healthy).
- [x] Docs: ADR-0027 + demo note.
- [x] Closed by the merging PR (`closes #141`).

This closes out the S-18 (#19) split — Objecttypen (S-18a) + Objecten (S-18b) + RegisterRecord (S-18c) are all up. Next: **S-19 (#20)** — ACL writes the register record to Objecten on approval, against this schema.

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #146
2026-07-27 15:14:16 +00:00
not 0a97fa4bf7 S-18b · Objecten API up in compose, wired to Objecttypen (closes #140) (#143)
CI / build (push) Successful in 1m29s
CI / lint (push) Successful in 1m45s
CI / unit (push) Successful in 2m6s
CI / frontend (push) Successful in 4m21s
CI / mutation (push) Successful in 7m0s
CI / verify-stack (push) Failing after 30m21s
## What & why

S-18b, second of the S-18 (#19) split (after S-18a #139/#142). Stands up the upstream Maykin **Objecten API** in the compose stack and wires it to the Objecttypen API — the authoritative object store the ACL will write register records to (S-19).

Closes #140

### What

- **Compose** (main + local): `objecten-db` (**PostGIS** — objects carry geometry), `objecten-redis`, `objecten-init` (RUN_SETUP_CONFIG → migrate + provision token + register the Objecttypen service), `objecten` web (health on `/admin/`, host `:8021`). Verbatim upstream image `maykinmedia/objects-api` pinned to `3.4.0` (nearest release to objecttypes-api `3.4.2`; the two speak over the stable Objecttypes API v2).
- **Seed**: `infra/seed-config.sh objecten` streams `infra/objecten/setup_configuration/data.yaml` into the external `rr-objecten-config` volume — same pattern as S-18a. The data.yaml (1) registers **Objecttypen** as a trusted `zgw_consumers` service (`api_type: orc`, api-key auth with the S-18a dev token) so an object can reference its objecttype, and (2) provisions a dev **static API token** so peers (the ACL, S-19) can write objects.
- **Wiring**: added to `WAIT_SVCS`, `CFG_VOLS`, the `SEED` invocations, `seed-config.sh`, and the CI log-dump. `objecten-init` waits on `objecttypen` being healthy so the service registration is meaningful end to end.
- **Smoke**: `verify-objecten` (`infra/run-objecten-check.sh` + `objecten-check.py`) asserts unauth → 401, token → 200 on `/api/v2/objects`; added as a verify-stack step + a row in the #136 check-summary table.

## Verified locally (end to end, real compose)

Seeded + brought up the real `infra/docker-compose.yml` objecten chain (pulls in objecttypen via `depends_on`): `objecten-init` ran setup_configuration — `token_configuration_success` **and** "Successfully executed step: Configuration to connect with external services" — the web reached healthy, and `make verify-objecten` → **"OK — no-auth 401, token 200"**. Confirmed the registered service via the Objecten django shell:

```
objecttypen | orc | http://objecttypen:8000/api/v2/ | api_key
```

YAML (both compose files + ci.yaml) + shell + python all validated; `docker compose config` clean on both files.

## Definition of Done

- [x] Failing smoke committed first (`test(infra): …`, "no running objecten container"); implementation makes it pass.
- [x] Conventional Commits referencing #140.
- [ ] CI green (verify-stack objecten step).
- [x] `docker compose up` reaches health (objecten healthy on first poll locally).
- [x] Demo note in `docs/demo-script.md`.
- [x] Closed by the merging PR (`closes #140`).

No new ADR: follows the established verbatim-image + seed-config CG-module pattern (S-18a/ADR-0023-era).

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #143
2026-07-27 09:53:01 +00:00
not 23ea91de32 feat(infra): Objecttypen API up in compose with a seeded static token (closes #139) (#142)
CI / build (push) Successful in 1m17s
CI / lint (push) Successful in 1m29s
CI / unit (push) Successful in 1m34s
CI / frontend (push) Successful in 3m24s
CI / mutation (push) Successful in 6m33s
CI / verify-stack (push) Failing after 12m2s
## What & why

S-18a, first of the S-18 (#19) split. Stands up the upstream Maykin **Objecttypen API** in the compose stack — the objecttype catalogue the register record (S-18b/S-18c, S-19) will build on.

Closes #139

### What

- **Compose** (main + local): `objecttypen-db` (Postgres), `objecttypen-redis`, `objecttypen-init` (RUN_SETUP_CONFIG → migrate + provision token), `objecttypen` web (health on `/admin/`, host `:8020`). Verbatim upstream image `maykinmedia/objecttypes-api` pinned to `3.4.2`.
- **Seed**: `infra/seed-config.sh objecttypen` streams `infra/objecttypen/setup_configuration/data.yaml` into the external `rr-objecttypen-config` volume — same pattern as OpenZaak/NRC. The data.yaml provisions a dev **static API token** (`tokenauth` setup_configuration step) so peers (Objecten, ACL) can authenticate.
- **Wiring**: added to `WAIT_SVCS`, `CFG_VOLS`, the `SEED` invocations, and the CI log-dump.
- **Smoke**: `verify-objecttypen` (`infra/run-objecttypen-check.sh` + `objecttypen-check.py`) asserts unauth → 401, token → 200; added as a verify-stack step + a row in the #136 check-summary table.

### Split note

#19 was oversized (two CG modules + config + objecttype) → split (§13) into **S-18a** (this), **S-18b** (#140, Objecten wired to Objecttypen), **S-18c** (#141, RegisterRecord objecttype).

## Verified locally (end to end, real compose)

Seeded + brought up the real `infra/docker-compose.yml` objecttypen chain: `objecttypen-init` ran setup_configuration (`token_configuration_success`), the web reached healthy, and `make verify-objecttypen` → **"OK — no-auth 401, token 200"**. YAML (both compose files + ci.yaml) + shell + python all validated.

## Definition of Done

- [x] Smoke check validates the outcome (live, against the running stack).
- [x] Conventional Commits referencing #139.
- [ ] CI green — see note.
- [x] `docker compose up` reaches health (objecttypen healthy on first poll locally).
- [x] Demo note in `docs/demo-script.md`.

## Note on CI

Additive (a new service + its own smoke step). The fast jobs are unaffected. The **verify-stack** job still can't go green until the pre-existing 1.27/act_runner-2.0.0 bring-up P0 is resolved (fails on plain `main` too) — but the objecttypen bring-up itself is validated locally above. No new ADR: this follows the established verbatim-image + seed-config CG-module pattern (ADR-0023-era).

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #142
2026-07-27 08:51:14 +00:00
not fff88ca23d ci: richer step reports via Gitea 1.27 job summaries (closes #136) (#137)
CI / build (push) Successful in 1m16s
CI / lint (push) Successful in 1m29s
CI / unit (push) Successful in 1m44s
CI / frontend (push) Successful in 3m47s
CI / mutation (push) Successful in 6m40s
CI / verify-stack (push) Successful in 8m15s
## What & why

Use the standard `$GITHUB_STEP_SUMMARY` (Gitea 1.27 + act_runner 2.0.0) to surface on the run page what was previously buried in logs or download-only artifacts. All five quick wins from #136, **reporting-only** — no job's pass/fail gating changes.

Closes #136

### Items

1. **Mutation scores** — added the `markdown` reporter to each `stryker-config.json`; the `mutation` job concatenates each service's `mutation-report.md` into the summary (`if: always()`). Also reveals where `make mutation` stopped on a ratchet break.
2. **Per-frontend tests** — the 4 apps' `test` targets emit vitest JSON to `test-output/{projectName}.json` (Nx token interpolation); `infra/vitest-summary.py` renders a per-frontend table.
3. **Per-service unit tests** — `make unit` now writes TRX; `infra/trx-summary.py` renders a per-service table (service name derived from the `services/<name>/` path, so `domain` shows, not `big.tests`).
4. **e2e per-spec results** — Playwright writes `playwright-report.json`; `run-e2e-check.sh` copies it out of the container (capturing the exit code first); `infra/playwright-summary.py` renders a per-spec table. Turns a red e2e into a one-glance "which spec".
5. **verify-stack check table** — each live-stack check has an `id`; a final `if: always()` step tabulates each check's //⏭️.

Docs: `gitea-actions-gotchas.md` §8 (version requirement + `$GITHUB_STEP_SUMMARY` guard + step-level `always()` note).

### Notes

- Every summary write is guarded with `[ -n "${GITHUB_STEP_SUMMARY:-}" ]`, so it no-ops on an unsupported runner / locally.
- New helper scripts are stdlib-only Python, matching the existing `infra/*.py` check scripts (no new dependency — a few lines of parsing rather than a test-logger package).
- `TestResults/` and `test-output/` gitignored.
- This is also the first PR-run exercising the #135 verify-stack fix end to end.

## Verified locally

`make unit` (TRX) ✓ · 4 apps' vitest JSON ✓ · ACL Stryker markdown report ✓ · all four parsers + the two summary shell blocks ✓ · `ci.yaml` + `run-e2e-check.sh` syntax ✓. The rendered summaries themselves only appear on the run page — this PR's CI run is the end-to-end check.

## Definition of Done

- [x] Each item writes to `$GITHUB_STEP_SUMMARY` (guarded), renders on the run page.
- [x] No change to any job's pass/fail gating.
- [x] Conventional Commits referencing #136 (one per item + docs).
- [ ] CI green; summaries visible on the run.
- [x] Runbook note (gotchas §8).

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #137
2026-07-24 13:27:53 +00:00
not 4698c869f3 feat(portal-beheer): beheer portal + read-only catalogus viewer (closes #130) (#133)
CI / build (push) Successful in 4m31s
CI / lint (push) Successful in 4m48s
CI / unit (push) Successful in 1m50s
CI / frontend (push) Successful in 4m44s
CI / mutation (push) Successful in 7m12s
CI / verify-stack (push) Canceled after 0s
## What & why

S-15a, the first of the S-15 (#16) split. A new **beheer** portal (medewerker realm, like behandel) shows the ZTC catalogus — the published zaaktypen — **read-only**. A beheerder logs in and sees the seeded BIG-REGISTRATIE zaaktype.

Closes #130

### The vertical

portal → BFF `GET /beheer/catalogi/zaaktypen` (medewerker realm + `beheerder` role) → ACL `GET /catalogi/zaaktypen` → ZGW Catalogi API.

- **ACL**: new read-only `GET /catalogi/zaaktypen` listing published zaaktypen (reuses the ADR-0021 Catalogi client; public-safe `identificatie`/`omschrijving`).
- **BFF**: new typed `IAclClient` + `Downstream:Acl:BaseUrl`, and `GET /beheer/catalogi/zaaktypen` behind a new `beheerder` policy (reuses the medewerker bearer scheme + realm-role lifting). OpenAPI spec + generated Angular client regenerated.
- **Keycloak**: `beheerder` realm role + `bram-beheerder` test user in the medewerker realm.
- **Frontend**: new `apps/beheer` Angular app (copied from behandel) with a read-only catalogus page; `SECURE_API_ROUTES=['/beheer/']`.
- **Infra**: `beheer` compose service (port 8143), added to `WAIT_SVCS` + CI log-dump; a Playwright e2e (beheerder login → catalogus shows BIG-REGISTRATIE).

### New boundary → ADR-0025

The BFF now reaches the **ACL directly** for the catalogus read — a new service-to-service edge (§14). The catalogus is neither a domain nor a projection concern, and §8.1 means only the ACL may read ZGW; routing through the domain would pollute it with a non-domain passthrough. §8.1/§8.3 stay intact. Recorded in **ADR-0025**.

## Definition of Done

- [x] Failing test committed before each implementation (red→green per layer: ACL, BFF, frontend).
- [x] Conventional Commits referencing #130.
- [ ] CI green — pending Gitea Actions run.
- [x] `docker compose up` brings up `beheer` (health-gated in `WAIT_SVCS`).
- [x] Docs — ADR-0025 + demo-script S-15a note.
- [x] Demo note in `docs/demo-script.md`.

## Verified locally

lint (`dotnet format`) ✓ · .NET unit (Acl 57 / Big 152 / EventSubscriber 19 / Bff 40) ✓ · frontend lint+test (8 projects) ✓ · frontend build (4 apps) ✓. Mutation ratchet: added a gateway unit test for the new `ListZaaktypenAsync` mapping so the ACL score holds. verify-stack (compose smoke + e2e) runs in CI.

## Notes for reviewers

- The BFF drops the ZGW URL from `BeheerZaaktype` (public-safe: identificatie + omschrijving only).
- The catalogus e2e asserts on the stable seeded `BIG-REGISTRATIE` (not a per-test reference), safe on the shared verify stack.
- Follow-ups: **S-15b** (#131) default-fill CRUD, **S-15c** (#132) medewerker-realm MFA.

🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #133
2026-07-24 11:08:17 +00:00
not d5dfbdc0b2 feat(obs): Prometheus metrics on /metrics + golden-signal Grafana dashboard (closes #124) (#129)
CI / build (push) Successful in 1m47s
CI / lint (push) Successful in 1m59s
CI / unit (push) Successful in 1m57s
CI / frontend (push) Successful in 3m58s
CI / mutation (push) Successful in 6m57s
CI / verify-stack (push) Successful in 8m29s
## What & why

S-16c, the last of the S-16 (#17) split, on top of the backplane (#122) and distributed tracing (#123). The five .NET services now expose OpenTelemetry **metrics** in Prometheus format at `/metrics`; Prometheus scrapes each (one job per service); and Grafana ships a pre-built **Request path — golden signals** dashboard (traffic / errors / latency / saturation), split by service.

Closes #124

### How

- Each service adds `.WithMetrics(AddAspNetCoreInstrumentation + AddHttpClientInstrumentation + AddMeter("System.Runtime") + AddPrometheusExporter)` and maps `/metrics`. Same shape as the S-16b tracing wiring already in these `Program.cs` files.
- `infra/observability/prometheus/prometheus.yml`: one scrape job per service (`acl`, `domain`, `bff`, `event-subscriber`, `projection-api`), reached by compose service name.
- `infra/observability/grafana/provisioning/dashboards/`: dashboard provider + `golden-signals.json` (baked into the Grafana image by the existing `COPY provisioning/`).
- `verify-metrics` (new CI verify-stack step + Makefile target): generates BFF traffic and asserts Prometheus scraped the golden-signal metric from every service. Mirrors `verify-tracing`.

### Dependency (CLAUDE.md §13/§14)

Adds `OpenTelemetry.Exporter.Prometheus.AspNetCore` `1.17.0-beta.1` (matched to the `1.17.0` core already in use). It gives the OTel-native `/metrics` pull endpoint; replacing it would mean hand-rolling Prometheus exposition over a `MeterListener`; the risk is that it is a **prerelease** package (the whole OTel .NET Prometheus line is `-beta`) — pinned, wired only in `Program.cs`, and gated by `verify-metrics`. Recorded in **ADR-0024**.

## Definition of Done

- [x] Linked Gitea issue (#124).
- [x] Failing test committed before the implementation (`test(bff): /metrics exposes http-server request duration`).
- [x] Implementation makes the test pass.
- [ ] CI green — pending Gitea Actions run.
- [x] `docker compose up` reaches green health within 3 min (backplane images unchanged in shape; not on the health gate, ADR-0023).
- [x] Docs updated — demo-script S-16c entry.
- [x] ADR added — ADR-0024.
- [x] Demo note in `docs/demo-script.md`.

## Notes for reviewers

- `/health` polls are counted as traffic (metrics aren't path-filtered, unlike traces). Fine for a demo dashboard and honest — real load stacks on top.
- `projection-api` has no Stryker config (unchanged); the four mutated services carry the metrics wiring in `Program.cs`, same as the merged S-16b tracing code.
- Metric names verified against a live service: `http_server_request_duration_seconds{,_bucket,_count}`, label `http_response_status_code`, `dotnet_process_cpu_time_seconds_total`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #129
2026-07-24 08:31:41 +00:00
not 88338396f6 feat(obs): distributed traces across the .NET services (S-16b, closes #123) (#126)
CI / verify-stack (push) Successful in 12m13s
CI / build (push) Successful in 1m50s
CI / lint (push) Successful in 1m58s
CI / unit (push) Successful in 2m8s
CI / frontend (push) Successful in 4m29s
CI / mutation (push) Successful in 11m51s
## What & why

S-16b, second of the S-16 split, on top of the #125 backplane. The five .NET services now emit OpenTelemetry traces so a request is **one connected trace** across them.

- Each host wires `AddOpenTelemetry().WithTracing(...)` with `AddAspNetCoreInstrumentation` (incoming) + `AddHttpClientInstrumentation` (outgoing) + `AddOtlpExporter` to **Tempo**.
- Because every cross-service call already goes through a typed `HttpClient` (§8 boundaries), the W3C `traceparent` propagates with no manual code — bff → domain → acl → openzaak and bff → projection-api stitch into a single trace.
- Service name + OTLP endpoint come from `OTEL_*` env set per app service in compose. `/health` is filtered out so liveness polls don't flood the traces.

No new ADR — ADR-0023 already records the stack + the two documented gaps (browser-side tracing is out of scope, so the trace begins at the BFF; the async Flowable-poll boundary is a separate trace).

Closes #123

## Definition of Done

- [x] Failing test committed first (`verify-tracing` fails with no instrumentation).
- [x] Implementation makes it pass — **validated locally end to end**: a real connected trace spanning `bff` + `projection-api` was found in Tempo (BFF→projection→db + Tempo subset, no OpenZaak/egress).
- [x] Conventional Commits referencing the issue (`refs #123`).
- [ ] CI green — awaiting Gitea Actions (verify-tracing added to verify-stack after verify-bff).
- [x] `docker compose up` health unaffected — services boot healthy even when Tempo is unreachable (exporter no-ops; verified).
- [x] Docs — demo-script + BACKLOG.
- [x] ADR — none needed (covered by ADR-0023).

## Notes for reviewers

- **Per-service wiring, no shared lib:** the block is duplicated across the five hosts by design — services don't share code across boundaries here (§8), same as the duplicated typed clients.
- **Packages:** OpenTelemetry.Extensions.Hosting / Instrumentation.AspNetCore / Instrumentation.Http / Exporter.OpenTelemetryProtocol, all 1.17.0, pinned per-csproj (no central props file).
- **The check** generates anonymous BFF→projection traffic (no auth, no OpenZaak), then queries Tempo (TraceQL search → fetch trace → assert both service.names present) from a python:3-slim container in-network — same idiom as run-projection-check.sh.
- **Next:** #124 (S-16c) adds `/metrics` + Prometheus scrape targets + golden-signal Grafana dashboards.

Reviewed-on: #126
2026-07-23 14:38:26 +00:00
not 4274fd30d1 feat(infra): observability backplane — Tempo + Prometheus + Grafana (S-16a, closes #122) (#125)
CI / mutation (push) Successful in 6m22s
CI / verify-stack (push) Successful in 11m53s
CI / lint (push) Successful in 1m24s
CI / build (push) Successful in 1m6s
CI / unit (push) Successful in 1m23s
CI / frontend (push) Successful in 2m54s
## What & why

S-16a, the first of the **S-16 split** (#17 closed → #122/#123/#124, §13). Stands up a local, CI-friendly observability backplane so traces (S-16b) and metrics (S-16c) have somewhere to land, viewable in one Grafana.

- **Grafana Tempo** — OTLP trace ingest (gRPC 4317 / HTTP 4318), local storage.
- **Prometheus** — scrapes itself for now; service `/metrics` targets arrive in S-16c.
- **Grafana** — Tempo + Prometheus datasources auto-provisioned with fixed uids (`tempo`, `prometheus`), exposed on :3000.

All three are small **built images** with config baked in (`infra/observability/`), on the existing `cg` network. **No OTLP collector** (Tempo ingests OTLP directly; Prometheus scrapes) and **no config-volume seeding** — the tools aren't verbatim CG peer modules, so a 3-line `COPY` Dockerfile is the simpler path that still reaches sibling containers on the CI runner (**ADR-0023**).

### Verified, not assumed

`make verify-observability` (new CI `verify-stack` step, run early) asks Grafana to reach both datasources — Prometheus via its health method, Tempo via the datasource proxy (Tempo's plugin implements no health method) — so it proves the datasources are wired, not merely that containers booted. Validated locally against the three containers (no external egress): Grafana healthy, both datasources reachable.

Closes #122

## Definition of Done

- [x] Failing test committed first (`verify-observability` fails with no backplane).
- [x] Implementation makes it pass; verified locally.
- [x] Conventional Commits referencing the issue (`refs #122`).
- [ ] CI green — awaiting Gitea Actions (verify-stack now includes the observability step; `docker compose config` validates locally).
- [ ] `docker compose up` reaches green health within 3 min — new containers are lightweight and off the health-gate list.
- [x] Docs — ADR-0023, demo-script, BACKLOG sync.
- [x] ADR added — `docs/architecture/adr-0023-observability-stack.md`.
- [x] Demo note in `docs/demo-script.md`.

## Notes for reviewers

- **No app changes** — this is pure infra; the five services are untouched (instrumentation is #123/#124).
- **Ports:** Grafana 3000 (admin/admin, anonymous viewer on), Prometheus 9090; Tempo internal to `cg`.
- **CI:** the three containers are added to the failure log-dump list; deliberately **not** added to `WAIT_SVCS` (the check polls Grafana itself, so no in-image healthcheck tool is needed). Trades ~3 small image builds per run.
- **Next:** #123 wires OTLP export + `AddAspNetCoreInstrumentation`/`AddHttpClientInstrumentation` into the five hosts so a request becomes one connected trace in Tempo.

Reviewed-on: #125
2026-07-23 12:26:22 +00:00
not 183d0bce31 fix(infra): docker-compose.local self-seeds zaaktype, DMN + NRC abonnement (closes #110) (#114)
CI / lint (push) Successful in 1m20s
CI / build (push) Successful in 59s
CI / unit (push) Successful in 1m12s
CI / frontend (push) Successful in 2m42s
CI / mutation (push) Successful in 5m42s
CI / verify-stack (push) Successful in 9m21s
## What & why

The host-browser stack (`make local`) had drifted behind three slices, so a fresh bring-up couldn't complete the flow: registrations stuck at `OpenZaakAanmaken`, the behandel werkbak stayed empty, and the openbaar register showed nothing. The `verify-*` scripts do this setup for CI at test time; `make local` had no equivalent.

This makes the local stack **self-seed at bring-up** so it just works in a browser:

- **DMN** — `flowable-init` now also deploys `diploma-eligibility.dmn` (was BPMN-only), so completing `WachtOpDocumenten` routes through the DMN to `Beoordelen` instead of 404ing.
- **Zaaktype + ACL** — a `local-seed` one-shot publishes the BIG zaaktype (whose UUID is server-assigned, hence not static in the compose file) and writes the real URLs to `seed-env:/acl.env`; the ACL sources it on startup via an entrypoint override.
- **NRC abonnement** — an `nrc-subscribe` one-shot registers the `zaken` subscription at the event-subscriber callback, so notifications reach the projection/openbaar register.

Both one-shots reach OpenZaak/NRC by **container IP** (a single-label host fails their Django URLValidator), mirroring the CI verify scripts. Design + trade-offs in **ADR-0020**.

Closes #110

## Definition of Done

- [x] Linked Gitea issue (#110).
- [x] Failing test committed before the implementation — `test(infra): …` adds `infra/run-local-flow-check.sh` / `make verify-local`; the three gaps' failures were observed live on a fresh `make local` (red), and the fix turns it green.
- [x] Implementation makes the test pass; docs commit follows.
- [x] Conventional Commits referencing the issue (`refs #110`).
- [ ] CI green — running on the restored runner. Infra-only change; the CI `verify-stack` job uses `docker-compose.yml` (untouched). Also validated locally: `make verify-local` passes against a fresh `make local` (see below).
- [x] `docker compose up` from a fresh clone reaches green health checks — verified: `make local` healthy in ~2m20s, then `make verify-local` green.
- [x] Docs updated — ADR-0020 + demo-script note.
- [x] ADR added in `docs/architecture/` — ADR-0020.
- [x] Demo note in `docs/demo-script.md`.

## Notes for reviewers

- **Infra-only** — no service code changes; the ACL image and the CI stack (`docker-compose.yml`) are untouched.
- **Verified end-to-end on a fresh stack** (`make local-down && make local && make verify-local`):
  ```
  >> 2. zaak opened            (zaaktype seeded + wired)
  >> 3. documents accepted 204 (DMN deployed)
  >> 4. in the werkbak         (DMN routing → Beoordelen)
  >> 5. visible in the openbaar register (NRC abonnement)
  OK — a fresh local stack completed the flow with no manual seeding
  ```
- **Follow-up:** the cleaner design — ACL resolving its zaaktype by `identificatie` instead of a pinned server-assigned URL — is split out as **S-27 (#113)**; landing it would remove the `acl.env` injection here. ADR-0020 records this.
- The `seed-env` volume carries the generated `acl.env` from `local-seed` to the ACL; a `down --volumes` (as `make local-down` does) resets it cleanly.

Reviewed-on: #114
2026-07-22 12:44:29 +00:00
not 3abf8f7ccf feat(behandel): behandel-portal — werkbak + beoordeling (closes #13) (#87)
CI / lint (push) Successful in 1m14s
CI / build (push) Successful in 53s
CI / unit (push) Successful in 1m3s
CI / frontend (push) Successful in 2m30s
CI / mutation (push) Successful in 4m59s
CI / verify-stack (push) Successful in 7m5s
## What & why

Finishes **S-12 · Behandel-portal — werkbak + beoordeling**. The backend sub-slices (S-12a/b/c-1/c-2) were merged, but the slice's stated outcome — a behandel *portal* with medewerker login, a werkbak, and decide — had no frontend. This adds it.

- **`libs/auth`**: `MedewerkerAuthService` + `provideMedewerkerAuth` (Keycloak `medewerker` realm), a `roles`/`hasRole` surface on the shared `AuthService`, and a realm-roles protocol mapper so the SPA can read `behandelaar`/`teamlead` from the token. The BFF remains the security boundary (ADR-0013).
- **`apps/behandel`**: a new Nx Angular app mirroring self-service — medewerker OIDC login and a **werkbak** page listing registrations awaiting beoordeling (`GET /behandel/werkbak`) with per-row **Goedkeuren/Afwijzen** actions (`POST /behandel/registrations/{id}/decide`) that refresh the list. NL DS/Utrecht, standalone + signals.
- **e2e**: the walking-skeleton happy path now approves through the real portal (behandelaar logs in, finds the row by reference, clicks Goedkeuren) instead of the temporary admin endpoint.
- **infra/docs**: behandel service in compose (`:8142`, depends on Keycloak); added to the smoke `WAIT_SVCS` + CI log dump; `frontend-decisions.md` and `demo-script.md` updated.

Closes #13

## Definition of Done

- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation.
- [x] Implementation makes the test pass; refactor commit if structure improved.
- [x] Conventional Commits referencing the issue (`refs #13`).
- [ ] CI green — all Gitea Actions jobs.
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes. *(behandel image + container verified locally; full stack gated in CI.)*
- [x] Docs updated if behaviour, contracts, or operations changed.
- [x] ADR added — ADR-0013 (merged with the backend sub-slices) already covers the wiring; no new decision here.
- [x] Demo note in `docs/demo-script.md`.

## Notes for reviewers

- Verified locally: auth + behandel + all frontend projects pass lint & unit tests (incl. axe WCAG 2.1 AA); production build green; the behandel Docker image builds and serves with the correct baked `medewerker` config + SPA fallback.
- The full compose-up smoke, e2e, and mutation are CI-gated (known local full-stack verify limits).
- **Follow-ups (not in scope):** the `WerkbakItem` contract has no citizen name (werkbak shows the BSN) — adding one is a BFF+domain contract change; and the domain's temporary admin `approve` endpoint is now unused by the e2e and could be removed.

Reviewed-on: #87
2026-07-16 08:31:57 +00:00
not bc9831c113 S-09: Openbaar Register portal — public lookup (#76)
CI / lint (push) Successful in 1m9s
CI / build (push) Successful in 53s
CI / unit (push) Successful in 1m4s
CI / frontend (push) Successful in 1m57s
CI / mutation (push) Successful in 5m19s
CI / verify-stack (push) Successful in 6m31s
Anonymous openbaar portal completing the walking skeleton (submit → projection → public visibility).

closes #10
2026-07-13 14:35:34 +00:00
notandClaude Opus 4.8 2e00ad38ba ci(portal-self-service): run Vitest ahead of the production build to stop worker-start timeout (refs #68)
CI / lint (pull_request) Successful in 1m11s
CI / build (pull_request) Successful in 54s
CI / unit (pull_request) Successful in 1m3s
CI / frontend (pull_request) Successful in 2m0s
CI / mutation (pull_request) Successful in 4m5s
CI / verify-stack (pull_request) Failing after 10m20s
The frontend lane ran `nx run-many -t lint test build`, so the ~5min
self-service production build shared nx's task pool with the Vitest test
worker. @angular/build:unit-test's Vitest worker has hard-coded 60s/90s
startup timeouts (not configurable); on a CPU-constrained CI runner the
concurrent build starved the worker and it failed with "Timeout waiting
for worker to respond" — flaky, since it passed on the prior commit.

Split the target into a light lint+test phase and a separate build phase
so tests get CPU and the worker starts well inside its window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:51:56 +02:00
notandClaude Opus 4.8 490e7347b0 test(e2e): walking-skeleton Playwright happy path + verify-e2e lane (refs #68)
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>
2026-07-01 14:08:10 +02:00
notandClaude Opus 4.8 4f311c9b5a ci(portal-self-service): serve the self-service app in compose (refs #68)
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>
2026-07-01 14:05:10 +02:00
notandClaude Opus 4.8 3824f85af6 ci(portal-self-service): Nx frontend lane (lint/test/build) (refs #65)
Add a make frontend target (pnpm install --frozen-lockfile + nx run-many -t lint
test build) and a CI 'frontend' job (pnpm + Node 24, pinned action URLs). Wire
frontend into make ci. The .NET lanes are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:56:59 +02:00
notandClaude Opus 4.8 a07d8277d6 ci(bff): compose wiring, verify-bff live check, mutation baseline (refs #8)
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>
2026-07-01 11:15:05 +02:00
notandClaude Opus 4.8 5a3f28ac6d ci(domain): containerize, wire into compose, and verify end-to-end (refs #6)
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>
2026-06-30 17:31:45 +02:00
notandClaude Opus 4.8 e9a873c152 test(domain): mutation baseline 90 (achieved 97.7%) + CI/Makefile wiring (refs #6)
Stryker.NET config for the domain service (break 90, the repo's ratchet floor),
excluding the OpenZaakJobPump hosted-shell from mutation. Hardened the unit tests
to kill survivors — Basic-credential value, variable types, null/failure response
paths, option defaults, guard clauses, save counts and log output — leaving only
two documented equivalent mutants (Stryker-disabled). make mutation runs the domain
ratchet and CI uploads its report alongside the others.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:24:56 +02:00
notandClaude Opus 4.8 fa8382fc02 ci(infra): run the Event Subscriber + projection-api in compose and verify end-to-end (refs #7)
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>
2026-06-30 15:11:24 +02:00
notandClaude Opus 4.8 d49443353e refactor(ci): one verify-stack stage for all live-stack checks (closes #58) (refs #46 #56)
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
notandClaude Opus 4.8 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
notandClaude Opus 4.8 4474585606 ci(acl): run the ACL integration test in CI inside the compose network (closes #55) (refs #46)
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
notandClaude Opus 4.8 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
notandClaude Opus 4.8 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
notandClaude Opus 4.8 e87113da24 feat(infra): add bind-mount local compose for no-make/Windows dev (refs #30)
CI / lint (pull_request) Successful in 51s
CI / build (pull_request) Successful in 42s
CI / compose-smoke (pull_request) Successful in 3m59s
CI / unit (pull_request) Successful in 51s
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
notandClaude Opus 4.8 dda4c58e1c fix(infra): portable health poll instead of compose --wait (refs #30)
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
notandClaude Opus 4.8 b349dff496 refactor(infra): use upstream images verbatim, seed config via docker cp (refs #30)
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
notandClaude Opus 4.8 a0aa22c80b fix(infra): smoke waits on durable services, not the whole project (refs #30)
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
notandClaude Opus 4.8 12049a0f35 fix(infra): nrc-init runs migrations only, not setup_configuration (refs #30)
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
notandClaude Sonnet 4.6 88de47d1bb fix(infra): harden oz-db healthcheck and raise compose-up timeout (refs #30)
CI / build (pull_request) Successful in 44s
CI / lint (pull_request) Successful in 52s
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
eho 4b2af5c635 feat(acl): ACL skeleton — OpenZaak default-fill (refs #5) (#45)
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
eho 71b76a0ef9 feat(workflow): Flowable + registratie.bpmn external task (closes #4) (#44)
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
eho c904c64597 feat(infra): Keycloak with four mock realms (closes #3) (#43)
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
eho 195a76aaf2 feat(infra): Open Notificaties up + shared network (closes #2) (#42)
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
eho 0409eb42c5 feat(infra): seed BIG catalogus + JWT client for OpenZaak (refs #2) (#41)
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
eho 8c5bbe05a9 feat(infra): OpenZaak + Postgres + Redis up in compose (refs #10) (#40)
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
eho ada2e807a3 chore: contributor workflow — templates, git-cliff, gitea-workflow doc (closes #31) (#38)
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
eho d4a89e6e62 ci: Gitea Actions pipeline + runner runbook (refs #30) (#37)
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