Files
register-referentie/BACKLOG.md
Edwin van den Houdt cd227af244 docs(backlog): split S-00 into S-00-a..e (refs #1)
Mirror the approved S-00 split into the backlog: replace the single S-00
slice with sub-slices S-00-a (BFF + health), S-00-b (Docker + compose +
smoke), S-00-c (Gitea Actions CI), S-00-d (contributor workflow), and
S-00-e (docs scaffold). Gitea remains the system of record; the issues
are managed with the tea CLI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:28:13 +02:00

303 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BACKLOG.md — iteration plan (curated mirror of the active Gitea milestone)
> **Source of truth: Gitea Issues + Milestones.** This file is a human-readable mirror so contributors and Claude Code can see the iteration plan at a glance. When the two disagree, **Gitea wins** and this file is regenerated.
>
> Regenerate this file when a milestone is opened, closed, or significantly reshaped. A commit message of `docs(backlog): sync with milestone <name>` is appropriate.
Each slice has a unique ID (`S-NN`) matching the title of its Gitea issue. Work is pulled from the **active milestone**'s project board in Gitea, not from this file.
Each slice is **independently demoable** and meets the Definition of Done in `CLAUDE.md` §3. Pull only one slice at a time.
---
## Iteration 0 — Foundations *(milestone: `Iteration 0 — Foundations`)*
> **S-00 was split** (CLAUDE.md §13) into the sub-slices below. The original
> outcome — a fresh clone + `docker compose up` reaching a green BFF health
> endpoint, with CI green and the contributor scaffolding in place — is the sum
> of S-00-a…e. The Gitea milestones, labels, and slice issues already exist —
> they are managed directly with the `tea` CLI.
### S-00-a · Placeholder BFF + health endpoint
**Outcome:** A minimal .NET BFF exposing `GET /health` returning green; runnable with `dotnet run`. TDD anchor for the slice.
**Touches:** `services/bff/`, tests. **Out of scope:** Docker, CI, OIDC.
### S-00-b · Dockerfile + compose skeleton + compose-up smoke
**Outcome:** BFF containerized; `infra/docker-compose.yml` brings it up; health green within 3 minutes from a fresh clone.
**Touches:** `services/bff/Dockerfile`, `infra/docker-compose.yml`, smoke script. **Out of scope:** other services, CI.
### S-00-c · Gitea Actions CI (lint, build, unit, compose-up smoke)
**Outcome:** `.gitea/workflows/ci.yaml` green on PRs and `main`: lint, build, unit, compose-up smoke. Actions pinned by absolute URL (§8.7/§15).
**Touches:** `.gitea/workflows/ci.yaml`, `docs/runbooks/ci.md`. **Out of scope:** mutation, e2e, container push.
### S-00-d · Contributor workflow: issue/PR templates, git-cliff, CHANGELOG
**Outcome:** Issue templates (`slice`/`bug`/`adr-proposal`), PR template, `git-cliff` → empty `CHANGELOG.md`, `docs/gitea-workflow.md`.
**Touches:** `.gitea/ISSUE_TEMPLATE/`, `.gitea/PULL_REQUEST_TEMPLATE.md`, `cliff.toml`, `CHANGELOG.md`, `docs/gitea-workflow.md`. **Out of scope:** app code.
### S-00-e · Docs scaffold: MkDocs + ADR-0001 + README quickstart
**Outcome:** MkDocs builds the `docs/` site; `docs/architecture/adr-0001-loose-coupling.md` exists; README has a sub-10-minute quickstart.
**Touches:** `mkdocs.yml`, `docs/` nav, `docs/architecture/adr-0001-loose-coupling.md`, `README.md`. **Out of scope:** Gitea Pages publish.
---
## Iteration 1 — Walking skeleton *(milestone: `Iteration 1 — Walking Skeleton`)*
The skeleton proves the spine end-to-end: a registration, a workflow, a zaak in OpenZaak, an event back, a public projection. Minimum viable but real.
### S-01 · OpenZaak + Open Notificaties + Postgres come up in compose
**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.
### S-02 · Keycloak with mock DigiD, eHerkenning, eIDAS, medewerker realms
**Outcome:** Keycloak runs locally with four realms pre-seeded. Each realm has 12 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.
### 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.
**Touches:** `infra/flowable/`, `workflows/registratie.bpmn`.
**Out of scope:** DMN, boundary timers, second model.
### S-04 · ACL skeleton with one operation: open a zaak
**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.
### 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.
### S-06 · Event Subscriber + Read Projection (minimal)
**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.
**Touches:** `services/event-subscriber/`, `services/projection-api/`, tests.
**Out of scope:** decision events, multiple projections, public-safe field filtering (will tighten in S-09).
### S-07 · BFF with one endpoint per portal + OIDC validation
**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.
- OpenAPI spec generated and committed.
**Touches:** `services/bff/`, OpenAPI spec, tests.
**Out of scope:** behandelaar and beheer endpoints (later slices).
### S-08 · Self-Service portal (Angular, NL DS) — submit a registration
**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.
**Touches:** `apps/self-service/`, `libs/ui/`, `libs/auth/`, `libs/api-client/`, tests.
**Out of scope:** document upload, status tracking page.
### S-09 · Openbaar Register portal — public lookup
**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.
**Touches:** `apps/openbaar/`, projection-api hardening, tests.
**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.
---
## Iteration 2 — Flow completeness and exception handling *(milestone: `Iteration 2 — Flow Completeness`)*
### S-10 · Document upload + boundary timer for document timeout (Flow 2)
**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.
### S-11 · Withdrawal (Flow 3)
**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.
### S-12 · Behandel-portal — werkbak + beoordeling
**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.
### S-13 · DMN decision: diploma eligibility (Flow 4)
**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.
### S-14 · Beoordeling escalation (Flow 5)
**Outcome:** Boundary timer on beoordeling user task — 14 days. On timeout, reassigns to a teamlead role.
---
## Iteration 3 — Maintenance portal and observability *(milestone: `Iteration 3 — Beheer & Observability`)*
### S-15 · Beheer-portal — catalogus & default-fill rules
**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.
### S-16 · OpenTelemetry traces + Grafana dashboard
**Outcome:** Traces span portal → BFF → Domain → ACL → OpenZaak and portal → BFF → Domain → Flowable. Grafana dashboards pre-built for golden signals.
### S-17 · Quartz.NET scheduler — herregistratie reminder sweep
**Outcome:** Nightly job that finds entries within 90 days of expiry and emits a domain event. (No outbound notification in v1 — logged.)
---
## Iteration 4 — Objecten and the authoritative register *(milestone: `Iteration 4 — Objecten`)*
### S-18 · Objecten + Objecttypen up in compose; Register objecttype defined
**Outcome:** Objecten and Objecttypen running. A `RegisterRecord` objecttype defined with the public-safe schema.
### S-19 · ACL extension: write register-record to Objecten on approval
**Outcome:** Approval path writes the canonical register record to Objecten, not OpenZaak eigenschappen. Projection now sourced from Objecten events.
**ADR required:** "Why Objecten holds the register, OpenZaak holds the process."
---
## Iteration 5 — Data governance module *(milestone: `Iteration 5 — Data Governance`)*
### S-20 · OpenMetadata module + seed bundle deployed alongside
**Outcome:** OpenMetadata stack runs as a separate compose file (`infra/governance/`). Seed bundle loaded: glossary, classification taxonomy, roles, default DQ tests.
### S-21 · Read-replica ingestion + API ingestion
**Outcome:** Postgres read replicas of domain, Flowable, projection. OpenMetadata ingestion connectors discover schemas. API connector ingests OpenZaak and Objecten via OpenAPI.
### S-22 · Lineage SDK (.NET) + lineage assertions across the personal-data path
**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."
### S-23 · GDPR reporting cookbook
**Outcome:** `docs/gdpr-reporting.md` showing how to answer specific AVG questions using OpenMetadata (data subject request, processing register, lineage trace).
---
## Iteration 6 — Production aspirations (sketch only) *(milestone: `Iteration 6 — Production Posture`)*
### S-24 · Helm chart (sketch) + Kubernetes manifests for the platform
**Outcome:** A non-deployed-but-reviewable Helm chart and accompanying ADR on production posture. Documents HA, secrets, backup, observability, identity wiring.
### S-25 · Runbook completeness review
**Outcome:** All runbooks complete: startup, seed, common failures, upgrade upstream modules, restore from backup, rotate secrets, Gitea Actions gotchas.
---
## How to add a new slice
1. **Open a Gitea issue** using the `slice.md` template. Title format: `S-NN · One-line outcome`.
2. Assign it to the appropriate milestone and apply `type:slice` plus area labels.
3. Fill in: outcome (one sentence, user-visible if possible), acceptance (Gherkin or testable assertions), touches (services and folders), out of scope (explicit).
4. Add it to the milestone's project board in the `Todo` column.
5. Mirror it into this file under the relevant iteration heading.
6. Open a PR against `BACKLOG.md` for review **before** starting the work.
## How to split a slice
If a slice issue grows beyond ~12 days of focused work:
1. Identify the natural seam (often between backend and frontend, or between happy path and exception path).
2. **In Gitea,** close the original issue with a comment listing the replacement issues. Use `S-NN-a`, `S-NN-b`, etc., or just allocate fresh `S-NN` numbers.
3. Each split must still meet the Definition of Done independently.
4. Update `BACKLOG.md` in a `docs(backlog): split S-NN (refs #NN, closes #NN)` commit.
## Cross-references
- **System of record:** Gitea Issues (this repo's Issues tab).
- **Active board:** the Gitea project board on the current milestone.
- **Working agreements:** `CLAUDE.md`.
- **What we're building and why:** `PRD.md`.
- **Architecture diagrams + ADRs:** `docs/architecture/`.
- **Daily-changing operational notes:** Gitea Wiki.