The PRD pinned ".NET 9 (LTS at iteration time)", but .NET 9 is STS and
reaches end-of-life around now; the actual LTS at this iteration is .NET
10. The BFF (and the services to follow) target net10.0, pinned via
global.json. Correct the PRD to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register ASP.NET Core health checks and map GET /health. The endpoint
returns 200 with a "Healthy" body by default, making the red test pass.
Smallest change to go green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the BFF Api/Tests skeleton (.NET 10, ASP.NET Core minimal API, xUnit
+ WebApplicationFactory) and a failing test asserting GET /health returns
200 with a Healthy body. The endpoint does not exist yet — the test fails
with NotFound, establishing red before the implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:35:22 +02:00
13 changed files with 145 additions and 485 deletions
- **CI/CD:** **Gitea Actions** running on Respellion-hosted `act_runner` instances. Workflow files live in `.gitea/workflows/`. Marketplace actions are referenced via absolute URLs (`uses: https://github.com/actions/checkout@v4` or Gitea-hosted equivalents where available) for reproducibility.
- **Backend:** .NET 9 (LTS at iteration time), C#, minimal APIs for BFF, MediatR for in-process messaging within Domain Service, EF Core for the projection store and domain DB.
- **Backend:** .NET 10 (LTS at iteration time), C#, minimal APIs for BFF, MediatR for in-process messaging within Domain Service, EF Core for the projection store and domain DB.
**Outcome:** A fresh `git clone` from the Respellion Gitea remote, followed by `docker compose up`, produces a green "hello world" health endpoint from a placeholder BFF. Gitea Actions runs lint, build, unit tests, and the compose-up smoke test, all green. Issue templates, PR template, milestones, labels, and the first project board exist in Gitea.
**Acceptance:**
- New developer follows `README.md` and reaches a green local environment in under 10 minutes.
- Gitea Actions pipeline green on `main`.
- `git-cliff` produces an empty `CHANGELOG.md`.
- `docs/PRD.md`, `CLAUDE.md`, `BACKLOG.md`, `docs/architecture/adr-0001-loose-coupling.md`, `docs/gitea-workflow.md` all in repo.
- `.gitea/workflows/ci.yaml`, `.gitea/ISSUE_TEMPLATE/{slice,bug,adr-proposal}.md`, `.gitea/PULL_REQUEST_TEMPLATE.md` all in repo.
- Gitea milestone `Iteration 1 — Walking Skeleton` exists, populated with issues S-01 through S-09.
**Outcome:** Local `docker compose up` brings up OpenZaak, Open Notificaties, their dependencies, and a seeded ZTC catalogus called `BIG`. A health check confirms all reachable.
**Acceptance:**
- `curl` to OpenZaak `/zaken/api/v1/` returns 401 (auth working).
- A test client with a generated JWT can list zaaktypen in the `BIG` catalogus.
- The seeded catalogus contains one lean `BIG-registratie` zaaktype with only the schema-mandatory fields plus `bsn` as an eigenschap.
**Touches:** `infra/openzaak/`, `infra/opennotificaties/`, `infra/seed/`, ADR for catalogus design.
**Out of scope:** any portal, BFF, Flowable, ACL code.
**Outcome:** Keycloak runs locally with four realms pre-seeded. Each realm has 1–2 test users with known credentials documented in `docs/synthetic-data.md`.
**Acceptance:**
- Browser-based OIDC login flow works for each realm against a placeholder client.
- Mock DigiD realm returns a BSN claim; eHerkenning returns a KvK; eIDAS returns a foreign identifier; medewerker returns role claims.
**Touches:** `infra/keycloak/`, seed scripts.
**Out of scope:** real federation, MFA.
EOF
)"
create_issue "S-03 · Flowable up with a minimal BPMN: \"Registratie ontvangen\""\
**Outcome:** Flowable runs locally with Postgres. A single BPMN model (`registratie.bpmn`) deployed with one start event, one external task `OpenZaakAanmaken`, one end event.
**Acceptance:**
- BPMN model deployed via Flowable's REST API on container start.
- An HTTP call can start a process instance and observe it waiting on the external task.
**Outcome:** A .NET library + service that exposes one method: `OpenZaak(domainPayload) → zaakUrl`. It default-fills `bronorganisatie`, `verantwoordelijkeOrganisatie`, `startdatum`, `vertrouwelijkheidaanduiding`, and posts to OpenZaak. **Strict TDD throughout.**
**Acceptance:**
- BDD scenario: "Given a domain registration payload, when I call the ACL, then a zaak exists in OpenZaak with the default-filled fields."
- Mutation score baseline captured and enforced by the Gitea Actions pipeline.
- Integration test using Testcontainers against real OpenZaak passes.
**Touches:** `services/acl/`, tests, ADR for default-fill strategy.
**Out of scope:** all other ZGW operations, status transitions, documents.
EOF
)"
create_issue "S-05 · BIG Domain Service skeleton with the Registration aggregate"\
**Outcome:** A .NET service exposing a single endpoint `POST /registrations`. The Registration aggregate has a state machine with at minimum `INGEDIEND`. The service orchestrates: start a Flowable process → external task callback executes the ACL `OpenZaak` → zaak URL stored on the aggregate.
**Acceptance:**
- BDD scenario: "Given a zorgprofessional submits a registration, when the domain service receives it, then a Flowable process is started and a zaak is opened in OpenZaak."
- Integration test exercises the full path (no real frontend yet).
- The Workflow Client is the only code that calls Flowable.
**Touches:** `services/domain/`, `services/acl/` (consumed), tests, ADR for external-task job-worker pattern.
**Out of scope:** any other use case, documents, decisions.
**Outcome:** An NRC webhook consumer that, on `zaak.gecreeerd`, writes a row to a `register_projection` table with `id`, `bsn`, `naam_placeholder`, `status`. Idempotent. Rebuildable.
**Acceptance:**
- BDD scenario: "Given a zaak is created in OpenZaak, when the NRC event is delivered, then the projection contains a row with status INGEDIEND."
- Replaying the same event twice does not create duplicates.
- A `projection rebuild` admin command repopulates from OpenZaak.
**Outcome:** A .NET BFF exposing four endpoint groups (one per portal). Validates tokens issued by Keycloak. Implements the minimum needed for the walking skeleton: `POST /self-service/registrations`, `GET /openbaar/register?q=...`.
**Acceptance:**
- BDD scenarios cover the two endpoints with valid and invalid tokens.
**Outcome:** The self-service Angular app, in the Nx monorepo, lets a zorgprofessional log in via mock DigiD and submit a registration. NL Design System styling. Generated API client.
**Acceptance:**
- E2E test (Playwright): full happy path, login → submit → success page.
- Component tests (Testing Library) for the form.
- Accessibility audit (axe-core) passes WCAG 2.1 AA on the submit page.
**Outcome:** The openbaar Angular app shows a search box. Anonymous. Queries the BFF's `/openbaar/register` which reads only the projection's **public-safe** fields. Confirms the walking skeleton end-to-end.
**Acceptance:**
- E2E test: zorgprofessional registers via self-service (S-08), behandelaar approves via a temporary admin endpoint (no behandel-portal yet), openbaar register shows the entry.
- Public-safe field whitelist enforced and tested.
**Out of scope:** advanced search filters, sorting.
_End of walking skeleton. Demo: submit → process → projection → public visibility. All CI gates green on Gitea Actions. Cut release `vYYYY.MM.0` and publish via Gitea Releases._
**Outcome:** BPMN extended with a "wacht op documenten" user task with a 30-day boundary timer. Self-service portal supports diploma upload. On timeout the case is cancelled.
**Acceptance:** BDD scenarios for both branches; integration tests for the timer firing.
**Outcome:** Self-service portal has a "trek aanvraag in" action. Domain service issues a withdraw command; BPMN message event correlates; case cancels with audit trail.
**Outcome:** Behandel portal with login (medewerker realm), werkbak listing INGEDIEND/IN_BEHANDELING cases, claim and complete user tasks via Flowable, decision endpoint via Domain Service.
**Acceptance:** BDD scenarios for claim, complete, request additional document, decide.
**Outcome:** A DMN decision table evaluated by the Domain Service via Workflow Client. Foreign diplomas route to an extra "CBGV-advies" user task in BPMN.
**Acceptance:** BDD scenarios for domestic and foreign diploma paths; DMN evaluated separately is unit-tested.
**Outcome:** Beheer portal lets an admin view ZTC catalogi (read-only first), and manage the ACL's default-fill configuration via a CRUD UI. MFA on the medewerker realm enforced.
"Iteration 5 — Data Governance""type:slice,area:infra""$(cat <<'EOF'
**Outcome:** OpenMetadata stack runs as a separate compose file (`infra/governance/`). Seed bundle loaded: glossary, classification taxonomy, roles, default DQ tests.
EOF
)"
create_issue "S-21 · Read-replica ingestion + API ingestion"\
"Iteration 5 — Data Governance""type:slice,area:infra""$(cat <<'EOF'
**Outcome:** Postgres read replicas of domain, Flowable, projection. OpenMetadata ingestion connectors discover schemas. API connector ingests OpenZaak and Objecten via OpenAPI.
EOF
)"
create_issue "S-22 · Lineage SDK (.NET) + lineage assertions across the personal-data path"\
"Iteration 5 — Data Governance""type:slice,area:acl,area:event-subscriber,area:domain""$(cat <<'EOF'
**Outcome:** A thin .NET package wrapping OpenMetadata's lineage API, published to the **Gitea Packages** registry. ACL, Event Subscriber, and Domain Service call it as personal data flows. Each lineage edge carries purpose and legal basis.
**ADR required:** "Lineage as a property of code, not docs."
EOF
)"
create_issue "S-23 · GDPR reporting cookbook"\
"Iteration 5 — Data Governance""type:slice,area:docs""$(cat <<'EOF'
**Outcome:** `docs/gdpr-reporting.md` showing how to answer specific AVG questions using OpenMetadata (data subject request, processing register, lineage trace).
create_issue "S-24 · Helm chart (sketch) + Kubernetes manifests for the platform"\
"Iteration 6 — Production Posture""type:slice,area:infra""$(cat <<'EOF'
**Outcome:** A non-deployed-but-reviewable Helm chart and accompanying ADR on production posture. Documents HA, secrets, backup, observability, identity wiring.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.