The walking skeleton has only INGEDIEND and no approval/status-transition path, so the Openbaar portal (demoable against the existing public-safe BFF endpoint) and the approve→visible flow are separate slices. See #75 for the approval flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
318 lines
16 KiB
Markdown
318 lines
16 KiB
Markdown
# 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 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.
|
||
|
||
### 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
|
||
|
||
> **S-08 was split** (CLAUDE.md §13; issue #9 closed) into the sub-slices below — it bundled the
|
||
> Nx bootstrap, the generated client, the NL DS + DigiD form, and a full-stack Playwright e2e, well
|
||
> past 1–2 days. Each sub-slice is independently demoable and CI-green.
|
||
|
||
- **S-08a (#65)** · Nx monorepo + Angular tooling + CI Node lane. Placeholder `self-service` app; `nx lint/test/build` green in a new CI Node lane.
|
||
- **S-08b (#66)** · Generated api-client lib from `services/bff/openapi.json` (never hand-written, §10) + a mocked-BFF unit test.
|
||
- **S-08c (#67)** · Self-service submit form — NL Design System `libs/ui`, DigiD OIDC `libs/auth`, component tests (Angular Testing Library), axe WCAG 2.1 AA on the submit page.
|
||
- **S-08d (#68)** · Playwright happy-path e2e (login → submit → success) against the full stack + compose serving + CI e2e lane.
|
||
|
||
**Out of scope (whole of S-08):** document upload, status tracking page.
|
||
|
||
### S-09 · Openbaar Register portal — public lookup *(#10)*
|
||
|
||
**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. Shows the public-visibility half of the walking skeleton.
|
||
|
||
_Split from the original S-09 — scoped to the portal only; the approval flow is **S-09b (#75)**._
|
||
|
||
**Acceptance:**
|
||
|
||
- E2E test: after a zorgprofessional registers via self-service (S-08), the openbaar register shows the entry (as `INGEDIEND`).
|
||
- Public-safe field whitelist enforced and tested (already in the BFF; add a portal component test + a11y check).
|
||
|
||
**Touches:** `apps/openbaar/`, compose serving, e2e, docs.
|
||
|
||
**Out of scope:** approval/status transition (S-09b), advanced search filters, sorting.
|
||
|
||
### S-09b · Approval flow — temp admin endpoint + status transition to projection *(#75)*
|
||
|
||
**Outcome:** A behandelaar approves a submitted registration via a temporary admin endpoint (no behandel-portal yet — S-12). The approval transitions the zaak status through the ACL → NRC → event-subscriber → projection, and the openbaar register then shows the entry as approved.
|
||
|
||
**Acceptance:**
|
||
|
||
- A new terminal/approved status (e.g. `INGESCHREVEN`) exists and is projected.
|
||
- Temporary admin approve endpoint transitions a registration via a real ZGW status set (behind the ACL, §8).
|
||
- E2E: register (S-08) → approve → openbaar shows the entry as approved.
|
||
|
||
**Touches:** `services/domain`, `services/acl`, `services/event-subscriber`, `services/projection-api`, e2e.
|
||
|
||
**Out of scope:** behandel-portal UI (S-12), assessment logic (S-13), escalation (S-15).
|
||
|
||
**End of walking skeleton** (S-09 + S-09b). 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 ~1–2 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.
|