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>
16 KiB
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
- 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.
- Show four end-user portals (self-service, public register, back-office, maintenance) sharing one domain layer through a BFF.
- Show process orchestration via Flowable as a peer module, with BPMN + DMN governing the registration workflow.
- Show data governance and GDPR lineage as a separate, integratable module (later iteration).
- Be fully runnable locally with
docker compose up, including all upstream Common Ground modules, identity, workflow engine, and synthetic data. - Encode Respellion craft defaults: TDD, DDD, BDD, mutation testing, trunk-based development, Conventional Commits, ADRs, and CI gates.
- 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
- Happy path registration — DigiD login, submit, upload diploma, automatic verification passes, behandelaar approves, register entry published.
- Document timeout — registration submitted, no diploma uploaded within 30 days, automatic cancellation.
- Withdrawal — applicant withdraws before decision; case cancelled with audit trail.
- Foreign diploma escalation — eIDAS login, diploma flagged as foreign, additional CBGV-style assessment step.
- 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.ps1target rebuilds from scratch.
7. Non-functional requirements
- Local startup:
docker compose upbrings 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_runnerinstances. Workflow files live in.gitea/workflows/. Marketplace actions are referenced via absolute URLs (uses: https://github.com/actions/checkout@v4or Gitea-hosted equivalents where available) for reproducibility. - 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.
- 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/<service>:<tag>. - 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
mainand 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.mdcorresponds 1:1 to a Gitea issue with the sameS-NNidentifier 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 fromCLAUDE.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 upfrom 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.