From 1daf7a5b592e57540424b5a99cd1d25b2e7e9f32 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Wed, 3 Jun 2026 11:38:28 +0200 Subject: [PATCH] Init --- BACKLOG.md | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 164 ++++++++++++++++++++++++++++++ README.md | 184 ++++++++++++++++++++++++++++++++++ docs/PRD.md | 214 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 845 insertions(+) create mode 100644 BACKLOG.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 docs/PRD.md diff --git a/BACKLOG.md b/BACKLOG.md new file mode 100644 index 0000000..ccbd686 --- /dev/null +++ b/BACKLOG.md @@ -0,0 +1,283 @@ +# 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 ` 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 · Repository skeleton, Gitea Actions CI, contributor workflow + +**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. + +**Touches:** repo layout, Gitea Actions workflows, Dockerfile for placeholder BFF, `docker-compose.yml` skeleton, MkDocs scaffold, Gitea issue/PR templates. + +**Out of scope:** any business logic, frontend, OpenZaak. + +--- + +## 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 + +**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 ~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. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..afa171d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,164 @@ +# CLAUDE.md — working agreements for this repository + +This file is read by Claude Code on every task. It defines how we work, what good looks like, and where to find the source of truth. **Read `PRD.md`, the active Gitea milestone, and the relevant slice issue before starting any task.** + +The primary remote for this repository is the **Respellion Gitea instance**. There is no GitHub remote. Tooling, CI, issue tracking, releases, and the container registry all live in Gitea. Treat any reference to GitHub-only features as a bug to be corrected. + +--- + +## 1. Mission + +Build a reference application that demonstrates Respellion's Common Ground architecture pattern. Quality and architectural clarity beat feature throughput. Every commit must teach. + +## 2. Engineering principles (in order) + +1. **TDD.** Red → green → refactor. The red test commit precedes the implementation commit. Never write production code without a failing test first. +2. **DDD.** The Domain Service owns aggregates, invariants, and the ubiquitous language. Cross-aggregate consistency is eventual. Anti-corruption boundaries are explicit and minimal. +3. **BDD at the use-case level.** Each user-visible flow has a Gherkin scenario in `tests/acceptance/`. Scenarios are written in business language (Dutch terms allowed inline where they're the actual domain term). +4. **Loose coupling over convenience.** Upstream Common Ground modules are reached only via their documented APIs. No direct DB access to OpenZaak, Objecten, or any other peer module. Ever. If you're about to break this rule, stop and propose an ADR. +5. **Walking skeleton first, vertical slices after.** Each slice is independently deployable and demoable. + +## 3. Definition of Done + +A change is done only when **all** of these hold: + +- A linked Gitea issue exists (the slice or bug being addressed). +- Failing test written and committed first. +- Implementation makes the test pass. +- Refactor commit follows if structure improved. +- Conventional Commit messages used, referencing the Gitea issue (see §6). +- All Gitea Actions CI jobs green: lint, unit, integration, mutation (ratchet), e2e, container build + push, compose-up smoke test. +- `docker compose up` from a fresh clone reaches green health checks within 3 minutes. +- Docs touched if behaviour, contracts, or operations changed — versioned docs in `docs/`, operational notes in the Gitea Wiki. +- ADR added in `docs/architecture/` if a non-obvious decision was made. +- Demo note appended to `docs/demo-script.md` if the slice is user-visible. +- The Gitea issue is closed by the merging PR's commit footer (`closes #NN`). + +## 4. TDD discipline — concrete rules + +- **Red commit:** `test(scope): describe the behaviour being added (refs #NN)`. Test must fail with a clear assertion message. +- **Green commit:** `feat(scope): implement X (refs #NN)` — the smallest change that makes the test pass. No unrelated edits. +- **Refactor commit:** `refactor(scope): describe the structural change (refs #NN)`. Tests stay green. +- **No skipped or ignored tests** committed to `main`. If a test is temporarily disabled, the commit is `chore(test): skip X — reason (refs #NN)` and is reverted within the same slice. +- **One assertion concept per test.** Multiple `Assert.*` calls are fine if they verify the same behaviour from different angles; otherwise split. +- **Test names describe behaviour**, not method names: `submitting_without_diploma_keeps_zaak_in_ingediend` not `Test_Submit_1`. + +## 5. Mutation testing + +- Stryker.NET (backend) and Stryker (Angular) run on every PR via Gitea Actions. +- The first slice establishes a **baseline mutation score**. CI fails on regression below baseline (ratchet). +- Goal direction: increase baseline only deliberately, as a slice's stated outcome. Never lower it. + +## 6. Commits, branching, issues, and PRs + +- **Conventional Commits** taxonomy: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `arch` (architectural change with ADR), `perf`, `build`, `ci`. +- **Scope** is the affected service or library: `feat(domain): ...`, `test(acl): ...`, `arch(workflow): ...`. +- **Every commit references its issue:** `(refs #NN)` in the subject. The PR that merges the slice closes the issue via `closes #NN` in the squash-commit body. +- **Trunk-based development.** Short-lived branches off `main`, squash merges. No long-lived feature branches. +- **Branch naming:** `/-`, e.g. `feat/14-acl-default-fill`, `fix/27-projection-idempotency`. +- **Changelog** generated by `git-cliff` from commits, refreshed on tag; published as a Gitea Release. +- **CalVer** versioning: `YYYY.MM.PATCH`. +- **Breaking changes:** footer `BREAKING CHANGE: ...` plus an ADR. + +## 7. Gitea is the system of record + +- **User stories, bugs, and ADR proposals live in Gitea Issues.** The `BACKLOG.md` file is a curated mirror of the active milestone, not the source of truth. +- **Milestones** group slices into iterations (Iteration 0, 1, 2, …). +- **Labels:** `type:slice`, `type:bug`, `type:adr-proposal`, `type:chore`; area labels `area:acl`, `area:domain`, `area:portal-self-service`, `area:portal-openbaar`, `area:portal-behandel`, `area:portal-beheer`, `area:infra`, `area:workflow`, `area:event-subscriber`, `area:projection`, `area:bff`, `area:docs`. +- **Project boards** visualise each milestone with `Todo / In progress / In review / Done`. +- **Issue templates** in `.gitea/ISSUE_TEMPLATE/`: + - `slice.md` — for backlog slices; encodes the Definition of Done as a checklist. + - `bug.md` — for defects. + - `adr-proposal.md` — for decisions that need recording before coding starts. +- **PR template** in `.gitea/PULL_REQUEST_TEMPLATE.md` enforces the DoD checklist. +- **Don't open a PR without a linked issue.** If the work is genuinely throwaway, open the issue first and close it on merge. + +## 8. Architectural rules — non-negotiable + +1. **The ACL is the only code that talks to ZGW APIs.** BFF, Domain Service, and portals never construct ZGW URLs. +2. **The Workflow Client is the only code that talks to Flowable.** BPMN models never embed OpenZaak knowledge — they ask the Workflow Client to execute external tasks. +3. **Portals talk only to the BFF.** Never directly to Domain Service, ACL, OpenZaak, or any other backend. +4. **The Read Projection is rebuildable.** It is a derived artefact and must remain so. No write-only state lives in the projection. +5. **No direct DB access across services.** Each service owns its schema. +6. **Idempotency at every event boundary.** The Event Subscriber must tolerate duplicate and out-of-order NRC events. +7. **No GitHub-only assumptions.** Workflow files live in `.gitea/workflows/`. Marketplace actions are referenced by absolute URL and pinned. CI features that require GitHub APIs (e.g. GitHub-only `actions/upload-artifact` quirks) are replaced with Gitea-compatible alternatives or documented as known gaps in `docs/runbooks/gitea-actions-gotchas.md`. + +## 9. Layering inside .NET services + +- `Domain/` — aggregates, value objects, domain events. No external dependencies. Pure C#. +- `Application/` — use cases (commands, queries, handlers). Depends on Domain. +- `Infrastructure/` — adapters: EF Core, HTTP clients, Flowable, Keycloak. +- `Api/` — minimal API endpoints + DTOs. Depends on Application. +- **Dependency direction:** Api → Application → Domain. Infrastructure implements ports defined in Application. + +## 10. Frontend rules + +- **Nx monorepo** with four apps and shared libs. +- **NL Design System** components first; only roll our own if NL DS doesn't cover it (recorded in `docs/frontend-decisions.md`). +- **Standalone components + signals.** No NgModules in new code. +- **Angular Testing Library** for component tests; **Playwright** for e2e. +- **API client generated** from BFF's OpenAPI spec; never hand-written. +- **No business logic in components.** Logic lives in services and stores; components render. +- **Accessibility (WCAG 2.1 AA)** is part of Definition of Done for portal slices. + +## 11. Tests pyramid for this repo + +- **Unit (fast, in-memory):** dominant. Domain, application handlers, Angular services. +- **Integration (containers via Testcontainers):** ACL ↔ OpenZaak, Event Subscriber ↔ NRC, Workflow Client ↔ Flowable, projection updates. +- **Acceptance (Gherkin via Reqnroll):** one scenario per user-visible flow. Drives the slice. +- **E2E (Playwright):** the walking-skeleton happy path, expanded slice by slice. +- **Mutation:** Stryker on every PR with ratchet. + +## 12. Docs are part of the work — two surfaces + +- **`docs/` (versioned, MkDocs):** PRD, architecture, ADRs, glossary, synthetic-data spec, runbooks, demo-script. Source of truth. PR-reviewed. Published via a Gitea Actions workflow to Gitea Pages. +- **Gitea Wiki (living):** day-to-day runbooks that change often, known-issues, environment URLs, on-call rota. The Wiki points at `docs/` for everything authoritative. +- ADRs follow the Nygard template. One decision per ADR (`docs/architecture/adr-NNNN-title.md`). The first ADR is provided as a template — read it before writing new ones. + +## 13. When in doubt + +- If a task pushes against any of §8's architectural rules, **stop and open an `adr-proposal` issue in Gitea** before coding. +- If a slice issue looks bigger than 1–2 days of focused work, **split it** in Gitea — close it with a comment pointing at the replacement issues — and update `BACKLOG.md` to match. +- If a test is hard to write, **the design is wrong** — refactor the production code to be testable. +- If you're tempted to add a dependency, **justify it in the PR description** with three sentences: what it gives us, what we'd write to replace it, what risk it adds. +- If you can't explain a piece of code in one sentence, it's too clever — rewrite or extract. + +## 14. Definition of an ADR-worthy moment + +You owe an ADR when any of these apply: + +- A new dependency is added. +- A boundary between services is changed. +- A coupling rule from §8 is bent (even slightly). +- A non-obvious technical decision is made that future-you might question. +- A Gitea Actions workflow uses a non-standard mechanism (custom runner, escape hatch, GitHub-only fallback). + +ADRs are short — a page is plenty. Open an `adr-proposal` Gitea issue first; the ADR file lands in the PR that implements the decision. + +## 15. CI behaviour (Gitea Actions) + +- **Workflow files** live in `.gitea/workflows/`. They follow GitHub-Actions-compatible YAML, with these conventions: + - `uses:` references are absolute URLs and pinned to a tag, e.g. `uses: https://github.com/actions/checkout@v4`. + - Self-hosted runner labels (e.g. `runs-on: respellion-linux`) are declared in `docs/runbooks/ci.md`. + - Secrets come from Gitea repository or organisation secrets — never inline. + - Container images push to the Gitea Container Registry: `gitea.respellion.local/big-reference/:`. +- **PRs must be green to merge. No exceptions.** +- **Flaky tests are fixed, not retried.** +- **A red `main` is a P0** — pause feature work until green. +- **The compose-up smoke test runs in CI and gates merges.** + +## 16. Working agreements with Claude Code + +- Read this file, `PRD.md`, the active Gitea milestone, and the relevant slice issue before starting a task. +- Pick the smallest open issue that produces a demoable outcome. Don't batch. +- Always commit a failing test before the implementation that makes it pass. +- Don't create files outside the layout described in `PRD.md` §9 without proposing a change first via an issue. +- Don't add dependencies casually. See §13. +- Reference the Gitea issue in every commit (`refs #NN`) and close it on the merging commit (`closes #NN`). +- If asked to "just make it work," default to the slice's Definition of Done. If that's not what was wanted, ask. +- When a task is ambiguous, propose 2–3 options with trade-offs in the issue and stop. Don't guess. +- Never push directly to `main`. Never bypass the PR review gate. + +--- + +**This file is a living document.** Improve it as the repo matures. Treat changes to `CLAUDE.md` as architecturally significant — open a Gitea issue, then a PR, then get review. diff --git a/README.md b/README.md new file mode 100644 index 0000000..98873fb --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# 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](BACKLOG.md) for the current iteration. + +--- + +## Start here + +| Document | Purpose | +|---|---| +| **[docs/PRD.md](docs/PRD.md)** | What we're building and why. Goals, non-goals, architecture summary, scope. Read once at project start. | +| **[CLAUDE.md](CLAUDE.md)** | How we work. Engineering principles, TDD/DDD/BDD discipline, non-negotiable architectural rules, Gitea conventions. Read every task. | +| **[BACKLOG.md](BACKLOG.md)** | Iteration plan. A curated mirror of the active Gitea milestone — Gitea Issues are the system of record. | +| **[docs/architecture/](docs/architecture/)** | Diagrams (Mermaid sources) and ADRs. Start with `adr-0001-loose-coupling.md`. | +| **[docs/runbooks/](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](docs/PRD.md) and [docs/architecture/](docs/architecture/). + +--- + +## Local quickstart + +**Prerequisites** + +- Docker Engine (or Docker Desktop) with Compose v2 +- ~8 GB free RAM, ~10 GB free disk +- Bash or PowerShell + +**Bring the stack up** + +```bash +git clone https://gitea.respellion.local/respellion/register-reference.git +cd register-reference +cp .env.example .env # edit if you change ports +docker compose -f infra/docker-compose.yml up -d +``` + +Health checks should be green within ~3 minutes on a developer machine. If something fails, see [docs/runbooks/local-startup.md](docs/runbooks/local-startup.md). + +**Default URLs** + +| 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:5000 | +| 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](docs/synthetic-data.md). + +**Re-seed synthetic data** + +```bash +./tools/seed.sh # or pwsh ./tools/seed.ps1 +``` + +--- + +## 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](docs/PRD.md). + +--- + +## 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 `/-` (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](CLAUDE.md). + +**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](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: + +```bash +./tools/test-all.sh +``` + +Run a focused slice (example): + +```bash +dotnet test services/acl +``` + +--- + +## Contributing + +1. Find or open a Gitea issue using one of the templates in `.gitea/ISSUE_TEMPLATE/` (`slice.md`, `bug.md`, `adr-proposal.md`). +2. Assign yourself, move it to "In progress" on the milestone's project board. +3. Branch off `main`, follow the [CLAUDE.md](CLAUDE.md) working agreements (TDD: red commit → green commit → refactor commit). +4. Open a PR using the template, link the issue, ensure the Gitea Actions pipeline is green. +5. Squash-merge once approved. The merging commit closes the issue. + +If a task pushes against any of the architectural rules in [CLAUDE.md §8](CLAUDE.md), **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](LICENSE). 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-proposal` issue. +- Anything sensitive: contact Respellion through the channel in `docs/runbooks/contact.md`. diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..e72b176 --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,214 @@ +# BIG-Register Reference Application — PRD + +**Status:** v1 draft +**Owner:** Respellion +**Primary remote:** Gitea (self-hosted, Respellion) +**Purpose:** A reference application that demonstrates Respellion's Common Ground architecture pattern — using OpenZaak and other Common Ground modules behind a loose-coupling anti-corruption layer, with workflow, scheduling, and data governance as peer modules. It serves as a Foundations playbook artefact and as a credible, runnable demonstrator for Dutch public-sector engagements. + +--- + +## 1. Goals + +1. Demonstrate that a domain-specific case management application (a BIG-style register) can be built **on top of unforked Common Ground modules** by isolating all domain shaping in an Anti-Corruption Layer. +2. Show **four end-user portals** (self-service, public register, back-office, maintenance) sharing one domain layer through a BFF. +3. Show **process orchestration via Flowable** as a peer module, with BPMN + DMN governing the registration workflow. +4. Show **data governance and GDPR lineage** as a separate, integratable module (later iteration). +5. Be **fully runnable locally** with `docker compose up`, including all upstream Common Ground modules, identity, workflow engine, and synthetic data. +6. Encode Respellion craft defaults: TDD, DDD, BDD, mutation testing, trunk-based development, Conventional Commits, ADRs, and CI gates. +7. **Run end-to-end on Gitea infrastructure** — source, issues, CI, container registry, releases, wiki — to demonstrate EU-sovereign tooling. + +## 2. Non-goals + +- Not a certified production system. No real DigiD/eHerkenning/eIDAS — mocked via Keycloak. +- Not a multi-tenant SaaS. One instance, one client persona, per deployment. +- Not feature-complete to the real Dutch BIG-register. Modelled as a plausible analogue, not a copy. +- Not optimised for production scale; correctness and architectural clarity beat throughput. +- Not dependent on GitHub. The repository lives on a Respellion Gitea instance; GitHub mirroring is explicitly out of scope unless added later as an outreach measure. + +## 3. Architecture summary + +Refer to `docs/architecture/` for diagrams. Briefly: + +- **Portals (Angular, NL Design System):** self-service, openbaar register, behandel-portal, beheer-portal — four apps in an Nx monorepo with a shared component library. +- **BFF (.NET):** per-portal aggregation, OIDC token validation, audit, rate-limiting. +- **BIG Domain Service (.NET):** DDD aggregates, registration workflow, eligibility rules, orchestrates ACL and workflow engine. +- **Anti-Corruption Layer (.NET):** the only code that knows about ZGW APIs. Default-fills schema-mandatory fields, maps domain ↔ ZGW vocabulary. +- **Workflow Client (.NET):** REST client to Flowable; acts as external-task job worker so BPMN never reaches into OpenZaak. +- **Event Subscriber (.NET):** consumes NRC webhooks, updates the read projection, correlates events to running Flowable processes. +- **Read Projection Store:** denormalised, public-safe view feeding the openbaar register portal. +- **Scheduler (Quartz.NET):** fleet-wide sweeps (expiry, reminders). +- **Common Ground modules (upstream, unforked):** OpenZaak, Open Notificaties, Open Klant, Objecten + Objecttypen, Autorisaties. +- **Flowable:** BPMN + DMN engines with Postgres-backed jobs. +- **Identity:** Keycloak realms standing in for DigiD, eHerkenning, eIDAS, and a medewerker-IdP. +- **Data Governance (later):** OpenMetadata as a separate module, ingesting from read replicas and via a lineage SDK. + +## 4. User types and primary scenarios + +| User type | Auth | Portal | Primary scenario | +|---|---|---|---| +| Zorgprofessional | DigiD (mock) | Self-Service | Submit registration, upload diploma, track status | +| Werkgever / Onderwijsinstelling | eHerkenning (mock) | Self-Service | Register on behalf of a professional (machtiging) | +| EU-professional | eIDAS (mock) | Self-Service | Foreign diploma registration with extra checks | +| Burger / Werkgever-check | anonymous | Openbaar Register | Lookup a professional's registration status | +| Behandelaar / Jurist | medewerker-IdP | Behandel-Portal | Review cases, request additional documents, decide | +| Beheerder | medewerker-IdP + MFA | Beheer-Portal | Manage catalogus, configure default-fill rules, monitor | + +## 5. Functional scope — flows for v1 + +1. **Happy path registration** — DigiD login, submit, upload diploma, automatic verification passes, behandelaar approves, register entry published. +2. **Document timeout** — registration submitted, no diploma uploaded within 30 days, automatic cancellation. +3. **Withdrawal** — applicant withdraws before decision; case cancelled with audit trail. +4. **Foreign diploma escalation** — eIDAS login, diploma flagged as foreign, additional CBGV-style assessment step. +5. **Beoordeling escalation** — behandelaar does not pick up within 14 days, case escalates to teamlead. + +The five flows form the BDD acceptance backbone (Gherkin scenarios in `tests/acceptance/`). + +## 6. Synthetic data + +- Fixed seed loaded on first start: 5–10 hand-crafted personas covering all five flows, plus ~50 register entries with diverse statuses to populate the openbaar register. +- Dutch-realistic names; **valid test-BSN range** documented in `docs/synthetic-data.md`. +- Diplomas: PDF placeholders with deterministic content, generated by a small data-loader service on first compose-up. +- Re-seeding: a `make seed` / `pwsh ./tools/seed.ps1` target rebuilds from scratch. + +## 7. Non-functional requirements + +- **Local startup:** `docker compose up` brings everything green within 3 minutes on a developer machine. +- **CI gates (Gitea Actions):** lint, unit, integration, mutation (with ratchet), e2e for the current flow, container build pushed to Gitea Container Registry, compose-up smoke test. +- **TDD discipline:** red → green → refactor commits visible in history. +- **Mutation testing:** Stryker.NET (backend) and Stryker (Angular), baseline-ratcheted on `main`. +- **Coverage:** not a target; mutation score is the quality signal. +- **Observability:** structured logs (Serilog), OpenTelemetry traces, Prometheus metrics; a local Grafana with pre-built dashboards. +- **Security:** all secrets in Gitea repository/organisation secrets or local `.env` (gitignored); no secrets in repo. OWASP dependency scanning step in the Gitea Actions pipeline. +- **Conventional Commits + git-cliff** generated changelog. CalVer versioning. + +## 8. Technology choices (locked) + +- **Source control & collaboration:** **Gitea** (Respellion self-hosted) — repository, issues, milestones, labels, projects, releases, container registry, wiki, packages. +- **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. +- **Frontend:** Angular (latest LTS) + TypeScript, standalone components + signals, Nx monorepo, NL Design System component library, Angular Testing Library + Playwright. +- **Workflow:** Flowable (BPMN + DMN) via Docker image; Postgres for engine store. +- **Identity:** Keycloak with pre-seeded realms. +- **Common Ground:** official upstream Docker images for OpenZaak, Open Notificaties, Open Klant, Objecten, Objecttypen, Autorisaties. +- **Datastore:** Postgres for all .NET services; one schema per service. +- **Scheduler:** Quartz.NET inside the Domain Service deployment. +- **Container registry:** Gitea's built-in container registry; image references in compose pin to `gitea.respellion.local/big-reference/:`. +- **Containers:** Docker for all components; compose for local; Helm chart sketch for production aspirations (not deployed as part of v1). +- **Docs:** MkDocs Material **plus** Gitea's built-in wiki for living operational pages. See §10. +- **Languages of artefacts:** code, commits, ADRs, docs in **English**; user-facing portal copy and glossary terms in **Dutch**. + +## 9. Repository structure (high-level) + +``` +/ +├── apps/ # Angular portals (Nx) +│ ├── self-service/ +│ ├── openbaar/ +│ ├── behandel/ +│ └── beheer/ +├── libs/ # shared Angular libs (UI, auth, api-client) +├── services/ # .NET services +│ ├── bff/ +│ ├── domain/ # BIG Domain Service +│ ├── acl/ # standalone ACL library + service +│ ├── event-subscriber/ +│ └── projection-api/ +├── workflows/ # BPMN + DMN sources, exported and version-controlled +├── infra/ +│ ├── docker-compose.yml +│ ├── keycloak/ +│ ├── opennotificaties/ +│ ├── openzaak/ +│ ├── objecten/ +│ ├── flowable/ +│ └── seed/ # data loader +├── tests/ +│ ├── acceptance/ # Gherkin / Reqnroll BDD scenarios +│ └── e2e/ # Playwright +├── docs/ # static docs (MkDocs source) +│ ├── PRD.md +│ ├── architecture/ # diagrams + ADRs +│ ├── runbooks/ +│ ├── synthetic-data.md +│ └── gitea-workflow.md # how we use Gitea for issues/milestones/projects +├── .gitea/ +│ ├── workflows/ # Gitea Actions YAML +│ └── ISSUE_TEMPLATE/ # issue templates (slice, bug, ADR-proposal) +├── CLAUDE.md # working agreements +├── BACKLOG.md # pointer + mirror of the active Gitea milestone backlog +└── README.md +``` + +## 10. Documentation strategy — split between static and living docs + +Two surfaces, each with a clear role: + +**Static, versioned documentation — `docs/` rendered by MkDocs Material.** + +- The PRD, architecture diagrams (Mermaid sources in repo), ADRs, glossary, synthetic-data spec, and runbooks live here. +- Source of truth, versioned with the code, reviewed via PR. +- Published artefact: an MkDocs site built by a Gitea Actions workflow on every push to `main` and exposed via Gitea Pages (or a static-files job uploading to a Respellion-hosted location). +- ADRs use the Nygard template, one decision per file (`adr-NNNN-title.md`). First ADR (`adr-0001-loose-coupling.md`) is provided as a template. + +**Living, operational documentation — Gitea Wiki.** + +- The Gitea project's built-in **Wiki** holds operational pages that change frequently and where in-place editing matters: the day-to-day runbook, "known issues right now," the on-call rota, environment URLs, and the human-readable view of the iteration backlog. +- The Wiki is **not** the source of truth for architecture or ADRs — those stay in `docs/`. The Wiki points at them. +- The Wiki is part of the deliverable and is reviewed alongside the code. + +**Why split:** versioned docs benefit from PR review and ship with the code; operational pages benefit from in-UI editing and don't need a full PR cycle. Treating both as legitimate gives each its proper home. + +## 11. Story and backlog management — Gitea is the system of record + +User stories, defects, technical debt, and ADR proposals are tracked in **Gitea Issues**. The `BACKLOG.md` file in the repo is a curated mirror of the *current* milestone — it exists so that Claude Code and contributors can read the iteration plan without opening the web UI, but the **system of record is Gitea**. + +Concrete conventions: + +- **Each slice in `BACKLOG.md` corresponds 1:1 to a Gitea issue** with the same `S-NN` identifier in the title (e.g. `S-04 · ACL skeleton with one operation: open a zaak`). +- **Milestones** group slices into iterations: `Iteration 0 — Foundations`, `Iteration 1 — Walking Skeleton`, etc. +- **Labels** classify work: `type:slice`, `type:bug`, `type:adr-proposal`, `type:chore`, plus area labels (`area:acl`, `area:domain`, `area:portal-self-service`, `area:infra`, `area:docs`). +- **Projects** (Gitea's kanban-style project boards) visualise an iteration as a board: `Todo / In progress / In review / Done`. +- **Issue templates** live in `.gitea/ISSUE_TEMPLATE/` with three forms: `slice.md`, `bug.md`, `adr-proposal.md`. The slice template encodes the Definition of Done from `CLAUDE.md` §3 as a checklist. +- **Commits and PRs reference issues** with `#NN` (e.g. `feat(acl): default-fill bronorganisatie (refs #14)`). Closing keywords on the merging commit (`closes #14`) move the issue to Done automatically. +- **Pull Requests** are the unit of code review. Trunk-based, squash-merged, with the PR description summarising the slice's acceptance. +- **Releases** are cut on tags via `git-cliff`-generated changelogs and published to Gitea Releases. Container images are tagged matching the release. + +`BACKLOG.md` is regenerated (manually or by a small script) whenever a milestone is opened or closed. Treat it as a snapshot, not the source of truth. + +## 12. Iteration shape — walking skeleton first + +The first iteration is a **walking skeleton** that proves the spine end-to-end with minimal features. Each subsequent iteration is a **vertical slice** that adds one capability, deployable on its own. See `BACKLOG.md` for the slice list. + +The skeleton: zorgprofessional logs in via mock DigiD → self-service portal submits a registration → BFF calls Domain Service → Domain Service starts a Flowable BPMN process and opens a zaak via the ACL → status event flows via NRC to the Event Subscriber → projection updated → openbaar register portal shows the entry. + +Everything in the skeleton is real (no mocks beyond the IdP). Subsequent slices flesh out scenarios, edge cases, and the remaining portals/modules. + +## 13. Definition of Done (per slice) + +A slice is done when: + +- Red → green → refactor commits visible in history (Conventional Commits). +- All CI gates pass on Gitea Actions (lint, unit, integration, mutation baseline, e2e for the slice's scenario, container build + push to Gitea Container Registry, compose-up smoke test). +- `docker compose up` from a fresh clone yields green health checks within 3 minutes. +- The Gherkin scenario(s) that define the slice pass. +- An ADR is added in `docs/architecture/` if a non-obvious decision was made. +- The relevant runbook section is updated (in `docs/runbooks/` or the Wiki, as appropriate per §10). +- A short demo note appended to `docs/demo-script.md`. +- The corresponding Gitea issue is closed via the merging commit's footer (`closes #NN`). + +## 14. Open risks tracked + +- **OpenZaak version drift.** Pin upstream image tags; document the upgrade procedure. +- **Flowable + .NET integration friction.** External-task worker pattern via REST; mitigate by isolating in Workflow Client. +- **NL Design System completeness.** Some components may need polyfills; record gaps in `docs/frontend-decisions.md`. +- **NRC ordering and duplication.** Event Subscriber must be idempotent; verified by acceptance tests that replay events. +- **Gitea Actions ↔ GitHub Marketplace action drift.** Some marketplace actions assume GitHub-specific APIs. Workflows pin actions to known-good versions; gaps are recorded in `docs/runbooks/gitea-actions-gotchas.md`. + +## 15. Out of scope for v1 + +- OpenMetadata data governance module (v3 slice). +- Objecten as the authoritative register record store (v2 slice — v1 uses OpenZaak zaak-eigenschappen as a placeholder). +- Production-grade Helm chart (sketch only). +- Multi-tenancy. +- Real outbound notifications (email/SMS) — logged to console in v1. +- GitHub mirroring or any GitHub-hosted automation.