## 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
register-reference
A reference application demonstrating how to build a Dutch government register (a BIG-style professional register) on top of unforked Common Ground modules — OpenZaak, Open Notificaties, Objecten, Open Klant — with loose coupling, modern workflow tooling, and data-governance ready integration points.
This repository is the runnable companion to Respellion's Foundations playbook entry on Common Ground architecture for non-municipal contexts (CIBG, DUO, RVO, and similar uitvoeringsorganisaties).
Status: under active development. See BACKLOG.md for the current iteration.
Start here
| Document | Purpose |
|---|---|
| docs/PRD.md | What we're building and why. Goals, non-goals, architecture summary, scope. Read once at project start. |
| CLAUDE.md | How we work. Engineering principles, TDD/DDD/BDD discipline, non-negotiable architectural rules, Gitea conventions. Read every task. |
| BACKLOG.md | Iteration plan. A curated mirror of the active Gitea milestone — Gitea Issues are the system of record. |
| docs/architecture/ | Diagrams (Mermaid sources) and ADRs. Start with adr-0001-loose-coupling.md. |
| docs/runbooks/ | Operational guides: local startup, seeding, common failures, CI debugging. |
The day-to-day operational pages — environment URLs, known issues right now, on-call notes — live in the Gitea Wiki for this repository. The wiki points at docs/ for anything authoritative.
What this application demonstrates
- A BIG-style professional register modelled on Dutch public-sector patterns, with four end-user portals: self-service, openbaar register, behandel-portal, beheer-portal.
- Common Ground modules as upstream peers — never forked, reached only via documented APIs (ZGW, NRC events).
- An Anti-Corruption Layer that confines all ZGW knowledge to one place, so the rest of the codebase stays domain-shaped rather than municipality-shaped.
- BPMN + DMN workflows via Flowable as a separate, swappable module — using the external-task job-worker pattern so BPMN models never reach into OpenZaak.
- A read projection as the public-facing data path, decoupled from the authoritative modules.
- Synthetic data and mock identity (Keycloak realms standing in for DigiD, eHerkenning, eIDAS, and a medewerker IdP) so the whole system runs locally without external dependencies.
- TDD, DDD, BDD, mutation testing, ADRs, and Conventional Commits as enforced defaults — encoded in CI.
- Gitea-native delivery: source, issues, milestones, project boards, releases, container registry, packages, wiki, and Actions.
For the architecture rationale, see docs/PRD.md §3 and docs/architecture/.
Local quickstart
Prerequisites
- .NET 10 SDK (for
make lint/build/unit) - A container engine with Compose v2 — Docker, or rootless Podman (see docs/runbooks/ci.md for the Podman + Compose-provider setup)
make,curl,git- ~4 GB free RAM, ~5 GB free disk (grows as services land)
Clone
git clone git@git.labs.respellion.tech:eho/register-referentie.git
cd register-referentie
Wired today (Iteration 0): only the placeholder BFF exists so far. Get to green in under 10 minutes — run the full check gate, or just the running service:
make ci # lint + build + unit + container smoke — the CI gate
docker compose -f infra/docker-compose.yml up -d --build --wait
curl http://localhost:8080/health # -> Healthy
--wait exits non-zero unless the container reports healthy, so it doubles as the compose-up smoke test. The remaining services and the URLs below land in later slices.
Target service URLs (most land in later slices)
| Service | URL |
|---|---|
| Self-Service portal | http://localhost:4200 |
| Openbaar register | http://localhost:4201 |
| Behandel-portal | http://localhost:4202 |
| Beheer-portal | http://localhost:4203 |
| BFF | http://localhost:8080 |
| OpenZaak | http://localhost:8000 |
| Open Notificaties | http://localhost:8001 |
| Flowable | http://localhost:8080 |
| Keycloak | http://localhost:8180 |
| MkDocs site (after build) | http://localhost:8000/docs/ |
Test credentials, BSNs, and personas: see docs/synthetic-data.md.
Build the docs site
python3 -m venv .venv && .venv/bin/pip install mkdocs-material
.venv/bin/mkdocs serve # live preview at http://localhost:8000
.venv/bin/mkdocs build # static site in ./site
Repository layout
register-reference/
├── apps/ # Angular portals (Nx monorepo)
│ ├── self-service/
│ ├── openbaar/
│ ├── behandel/
│ └── beheer/
├── libs/ # shared Angular libs (UI, auth, generated API client)
├── services/ # .NET services
│ ├── bff/
│ ├── domain/ # BIG Domain Service
│ ├── acl/ # Anti-Corruption Layer (the only code that knows ZGW)
│ ├── event-subscriber/
│ └── projection-api/
├── workflows/ # BPMN + DMN sources
├── infra/ # docker-compose, Keycloak, OpenZaak, Flowable, seed
├── tests/
│ ├── acceptance/ # Gherkin / Reqnroll BDD scenarios
│ └── e2e/ # Playwright
├── docs/ # versioned documentation (MkDocs source)
├── .gitea/ # Gitea Actions workflows, issue/PR templates
├── CLAUDE.md # working agreements
├── BACKLOG.md # iteration plan (mirror of active milestone)
└── README.md
Full description in docs/PRD.md §9.
Working in this repository
Source of truth for work: Gitea Issues + Milestones for this repository. BACKLOG.md is a mirror.
Branching: trunk-based. Short-lived branches off main, named <type>/<issue-number>-<short-slug> (e.g. feat/14-acl-default-fill).
Commits: Conventional Commits, referencing the Gitea issue:
feat(acl): default-fill bronorganisatie (refs #14)
The merging PR closes the issue via closes #14 in the squash-commit body.
Pull Requests: the unit of review. Squash-merged. PR template enforces the Definition of Done checklist from CLAUDE.md §3.
Releases: CalVer (YYYY.MM.PATCH), tagged on main, changelog generated by git-cliff, published as a Gitea Release with container images in the Gitea Container Registry.
See CLAUDE.md for the full working agreements, the architectural non-negotiables, and the rules Claude Code follows on every task.
Testing
- Unit tests — dominant. .NET (xUnit) and Angular (Vitest / Testing Library).
- Integration tests — Testcontainers-driven, exercising real OpenZaak, Flowable, NRC.
- Acceptance tests — Gherkin scenarios in
tests/acceptance/, one per user-visible flow. - End-to-end — Playwright, expanding slice by slice from the walking-skeleton happy path.
- Mutation testing — Stryker.NET and Stryker, baseline-ratcheted on
main.
Run everything:
./tools/test-all.sh
Run a focused slice (example):
dotnet test services/acl
Contributing
- Find or open a Gitea issue using one of the templates in
.gitea/ISSUE_TEMPLATE/(slice.md,bug.md,adr-proposal.md). - Assign yourself, move it to "In progress" on the milestone's project board.
- Branch off
main, follow the CLAUDE.md working agreements (TDD: red commit → green commit → refactor commit). - Open a PR using the template, link the issue, ensure the Gitea Actions pipeline is green.
- Squash-merge once approved. The merging commit closes the issue.
If a task pushes against any of the architectural rules in CLAUDE.md §8, stop and open an adr-proposal issue first. That conversation is more important than the code.
License and attribution
Respellion-authored code is licensed under EUPL-1.2. Upstream Common Ground modules retain their own licences (typically EUPL-1.2 or MIT — see each module's repository).
This reference application is not an official product of CIBG, DUO, VNG Realisatie, or any government body. It is a Respellion playbook artefact illustrating an architectural pattern.
Contact
- Issues, questions, proposals: open a Gitea issue on this repository.
- Architectural discussion: start with an
adr-proposalissue. - Anything sensitive: contact Respellion through the channel in
docs/runbooks/contact.md.