Developer Guide
Last updated
Technical documentation for developing, testing, and deploying the Health Dataspace v2 platform, an EHDS regulation reference implementation built on Eclipse Dataspace Components.
Onboarding
New to the project? Follow this quick-start path to get productive within your first day.
1. Get Running
2. Explore the Platform
Working on the Project
How a change reaches the live hub, how it is tested through the APIs, and what each part of the stack is. Each page is published from the docs folder of the repository.
Development life cycle
Discussions and issues, branches and pull requests, every hook with its fix, the PR Gate and the other workflows, the deploys and the releases.
Integration tests through the APIs
The Bruno collection and the dataspace and identity checks: how to run them, and how current they are.
The stack and the development environment
EDC-V, JAD, CFM, DSP, DCP and the hub's own services in one glossary; the local stacks, the seed phases, the ports and the iPhone app.
Contributing
Fork, branch, pull request: the short guide for outside contributors, under the Apache License 2.0, with fictional organisations only.
JAD Stack Architecture
The full JAD (Java Application Deployment) stack runs 27 Docker Compose services orchestrated via docker-compose.yml + docker-compose.jad.yml. Services are grouped into five layers:
| Service | Port | Traefik | Purpose | Depends On |
|---|---|---|---|---|
| Traefik | :80 / :8090 | traefik.localhost | API gateway & reverse proxy | — |
| PostgreSQL 17 | :5432 | — | Runtime store (11 databases) | — |
| Vault | :8200 | vault.localhost | Secrets, file storage; vault-unseal unseals it after a restart | — |
| Keycloak | :8080 | keycloak.localhost | OIDC SSO (realm: edcv, 7 users) | PostgreSQL |
| NATS | :4222 / :8222 | — | Async event mesh (JetStream) | — |
| Control Plane | :11003 | cp.localhost | DSP protocol + management API | PG, Vault, NATS, KC |
| Data Plane FHIR | :11002 | dp-fhir.localhost | FHIR PUSH transfer type | PG, Vault, CP |
| Data Plane OMOP | :11012 | dp-omop.localhost | OMOP PULL transfer type | PG, Vault, CP |
| Identity Hub | :11005 | ih.localhost | DCP v1.0: DID + VC store | PG, Vault, KC |
| Issuer Service | :10013 | issuer.localhost | VC issuance + DID:web | PG, Vault, KC |
| Tenant Manager | :11006 | tm.localhost | CFM tenant lifecycle | PG, KC |
| Provision Manager | :11007 | pm.localhost | CFM resource provisioning | PG, KC, CP |
| Neo4j 5 | :7474 / :7687 | — | Knowledge graph (APOC + n10s) | — |
| Neo4j Proxy | :9090 | proxy.localhost | Express bridge: UI ↔ Neo4j | CP |
| Next.js UI | :3000 / :3003 | — | Application frontend | Neo4j |
Plus 4 background CFM agents (keycloak, edcv, registration, onboarding) and 1 one-shot seed container. Vault-bootstrap runs as a sidecar.
Prerequisites
Required
- Node.js 20+: runtime for UI and proxy
- Docker Desktop: with Docker Compose V2
- 8 GB Docker RAM: required for full JAD stack
- Git: with pre-commit hooks enabled
Optional
- Python 3.11+: for Synthea FHIR data loading
- gitleaks: local secret scanning (
brew install gitleaks) - lychee: broken link checker (
brew install lychee) - Playwright browsers: for E2E tests (
npx playwright install)
Port Requirements
The JAD stack requires these ports to be free: 80, 3000, 3003, 4222, 5432, 7474, 7687, 8080, 8090, 8200, 8222, 9090, 10013, 11002, 11003, 11005, 11006, 11007, 11012
Quick Start: Minimal Stack
Run Neo4j + Next.js UI with synthetic data. No JAD services needed.
1. Start Neo4j & load schema
docker compose up -d # Initialize schema (idempotent — safe to re-run) cat neo4j/init-schema.cypher | \ docker exec -i health-dataspace-neo4j \ cypher-shell -u neo4j -p healthdataspace # Load synthetic data (127 patients, 5300+ nodes) cat neo4j/insert-synthetic-schema-data.cypher | \ docker exec -i health-dataspace-neo4j \ cypher-shell -u neo4j -p healthdataspace
2. Start the UI
cd ui npm install npm run dev # → http://localhost:3000 npm test # Run the Vitest unit suite npm run lint # ESLint (max 55 warnings)
Quick Start: Full JAD Stack
The bootstrap script starts all 27 services with health checks, initializes Vault secrets, imports the Keycloak realm, and runs the 7-phase seed pipeline.
1. Bootstrap everything
# Full stack — takes ~3-5 min on first run ./scripts/bootstrap-jad.sh # Check status & endpoints ./scripts/bootstrap-jad.sh --status
2. Seed the dataspace
# Run all 7 seed phases (sequential, strict order) ./jad/seed-all.sh # Resume from a specific phase ./jad/seed-all.sh --from 3 # Run only one phase ./jad/seed-all.sh --only 5
3. Access the platform
# Live UI (production build) open http://localhost:3003 # Keycloak Admin Console open http://keycloak.localhost # admin / admin # Neo4j Browser open http://localhost:7474 # neo4j / healthdataspace # Traefik Dashboard open http://traefik.localhost
Common operations
./scripts/bootstrap-jad.sh --ui-only # Rebuild UI only (fast) ./scripts/bootstrap-jad.sh --seed # Re-run seed pipeline ./scripts/bootstrap-jad.sh --pull # Pull latest images ./scripts/bootstrap-jad.sh --down # Stop all services ./scripts/bootstrap-jad.sh --reset # Stop + remove volumes
Data Seeding Pipeline
The 7-phase seed pipeline populates the dataspace with tenants, credentials, policies, assets, and contracts. Phases must run in strict order; each depends on the previous.
| Phase | Script | Target Service | What It Does |
|---|---|---|---|
| 1 | seed-health-tenants.sh | Tenant Manager | Create 5 participant tenants via CFM |
| 2 | seed-ehds-credentials.sh | Issuer Service | Register EHDS credential types |
| 3 | seed-ehds-policies.sh | Control Plane | Create ODRL policies for all participants |
| 4 | seed-data-assets.sh | Control Plane | Register data assets + contracts |
| 5 | seed-contract-negotiation.sh | Control Plane | PharmaCo ↔ AlphaKlinik negotiations + data planes |
| 6 | seed-federated-catalog.sh | Control Plane | MedReg ↔ LMC federated catalog negotiation |
| 7 | seed-data-transfer.sh | Data Plane | Verify EDR tokens and data plane transfers |
Important: Vault keeps its secrets on disk (volumes vault_data and vault_keys; the vault-unseal sidecar unseals it on every start), so a restart or a docker compose down keeps every secret. Only removing the volumes (docker compose down -v or ./scripts/bootstrap-jad.sh --reset) deletes them; bootstrap and seed the stack again after that. If IdentityHub logs Private key ... not found, check that health-dataspace-vault-unseal reported ready.
Project Structure
├── .github/workflows/ # CI/CD (pr-gate, test, compliance, security-scan, deploy-azure, pages, ...) ├── connector/ # EDC-V connector (Gradle multi-module) │ ├── controlplane/ # DSP + Management API │ ├── dataplane/ # FHIR + OMOP data planes │ └── identityhub/ # DCP Identity Hub ├── docs/ # Architecture docs, journeys, ADRs, reports ├── jad/ # JAD infrastructure configs │ ├── keycloak-realm.json # Keycloak realm (edcv, 7 users, 6 roles) │ ├── edcv-assets/ # Contract definitions & ODRL policies │ ├── seed-*.sh # 7-phase seed scripts │ └── openapi/ # OpenAPI specifications ├── k8s/ # Kubernetes / OrbStack manifests ├── neo4j/ # Cypher scripts & data │ ├── init-schema.cypher # Constraints, indexes, vector indexes │ ├── insert-synthetic-schema-data.cypher │ └── fhir-to-omop-transform.cypher ├── scripts/ # Automation (bootstrap, synthea, compliance) ├── services/neo4j-proxy/ # Express bridge (Neo4j ↔ UI) ├── ui/ # Next.js 15 application │ ├── src/app/ # 64 pages, 84 API routes │ ├── src/components/ # Shared React components │ ├── src/lib/ # auth.ts, api.ts, graph-constants.ts │ ├── __tests__/unit/ # Vitest unit tests │ ├── __tests__/e2e/ # Playwright specs (journeys/) │ └── public/mock/ # 38 JSON fixtures for static export ├── docker-compose.yml # Minimal stack (Neo4j + UI) └── docker-compose.jad.yml # Full JAD stack (24 services)
Neo4j Graph Schema
The 5-layer knowledge graph spans 27 node labels with 70+ indexes and 3 vector indexes for GraphRAG. Schema defined in neo4j/init-schema.cypher (idempotent, safe to re-run).
5 Semantic Layers
- L1 Marketplace: Participant, DataProduct, Contract, HDABApproval, OdrlPolicy
- L2 HealthDCAT-AP: Catalogue, HealthDataset, Distribution, DataService
- L3 FHIR R4: Patient, Encounter, Condition, Observation, MedicationRequest
- L4 OMOP CDM: OMOPPerson, ConditionOccurrence, DrugExposure, Measurement
- L5 Ontology: SnomedConcept, ICD10Code, RxNormConcept, LoincCode
Key Conventions
- Labels:
PascalCase - Relationships:
UPPER_SNAKE_CASE - Properties:
camelCase - Always
MERGE, neverCREATE - Constraints use
IF NOT EXISTS - 3 fulltext indexes (clinical, catalog, ontology search)
- 3 vector indexes (384-dim, cosine, for GraphRAG)
PostgreSQL Schema
PostgreSQL serves as the runtime store for all JAD services: EDC-V state machines, Keycloak identity, and CFM tenant metadata. Neo4j holds the health knowledge graph. This split follows ADR-1.
| Database | Service | Contents |
|---|---|---|
| controlplane | EDC-V Control Plane | Contract negotiations, transfer processes, asset definitions, policy store |
| dataplane | DCore FHIR | FHIR data plane state, EDR tokens, transfer tracking |
| dataplane_omop | DCore OMOP | OMOP data plane state, EDR tokens, transfer tracking |
| identityhub | Identity Hub | DID documents, verifiable credential store, key pairs |
| issuerservice | Issuer Service | Credential definitions, attestation records, issued VCs |
| keycloak | Keycloak | Users, roles, realm config, sessions, client scopes |
| cfm | Tenant Manager, Provision Manager, CFM agents | Tenant records, VPAs (Virtual Participant Agents), provisioning tasks |
| redlinedb | — | Created by jad/init-postgres.sql; no service in either compose stack uses it today |
| taskdb | Neo4j proxy | Persistent task list behind the proxy's /tasks endpoints (phase 13d) |
Neo4j vs PostgreSQL Split
- Neo4j: Health knowledge graph (FHIR, OMOP, ontologies), graph traversal queries, semantic search, GraphRAG vectors
- PostgreSQL: EDC-V runtime state machines, OIDC sessions, tenant metadata, credential storage; transactional ACID workloads
- Rationale: Graph queries for clinical relationships are orders of magnitude faster in Neo4j; EDC-V requires PostgreSQL for its state machine persistence
Integration Flows
Two canonical flows cover 90% of real-world EHDS integrations. Pick the one that matches your role, then use the Scalar API Reference to try each endpoint from the browser.
Data Consumer flow
Researcher / pharma / HTA body discovers and requests cross-border health data.
- Discover →
GET /api/catalogreturns HealthDCAT-AP datasets - Inspect →
GET /api/assetsfor access policies (ODRL) - Negotiate →
POST /api/negotiationsopens a DSP 2025-1 contract - Attest →
POST /api/credentials/presentproves role via DCP VC - Transfer →
GET /api/transfers/:idmonitors the data plane - Analyse →
GET /api/analyticsorPOST /api/nlq
DATA_USER · Persona: Dr. Petra Lang (PharmaCo Research)Data Provider flow
Hospital / clinic / registry publishes datasets for secondary use.
- Register →
POST /api/participantscreates a did:web identity - Publish →
POST /api/catalogadds a HealthDCAT-AP dataset - Policy →
POST /api/admin/policiescreates an ODRL policy (EDC_ADMIN) - HDAB approval →
GET /api/compliancetracks approval state - Accept →
GET /api/negotiationsshows incoming contract offers - Deliver → data plane pushes FHIR/OMOP bundles to the consumer
DATA_HOLDER · Persona: Dr. Klaus Weber (AlphaKlinik Berlin)API Reference
84 Next.js API routes (111 operations) proxy to Neo4j and the EDC-V services; the table is generated from ui/src/app/api/. Every route needs a session (ADR-044) except the sign-in flows, the Keycloak address for the sign-in banner, the health probe, and the Klarbefund app's account creation, which answers only a verified Apple App Attest attestation (ADR-054). Routes are disabled in the static export; mock data is served from ui/public/mock/*.json.
| Area | Routes and methods | What it is for |
|---|---|---|
| /api/activity-report | / GET | The access body's activity report, Regulation (EU) 2025/327 Art. 59, generated from the graph |
| /api/admin | /audit/retention GET POST /audit GET /components/[name]/diagnosis GET /components/[name]/restart POST /components GET /components/topology GET /participants GET POST DELETE /policies GET POST PUT DELETE /spe/power POST /spe GET /tenants GET | Operator tools: components and their health, tenants, participants, ODRL policies, audit log and retention. EDC_ADMIN; policies and audit also HDAB_AUTHORITY |
| /api/analytics | / GET | OMOP cohort analytics |
| /api/app-accounts | /challenge POST / POST | The Klarbefund app creates a sandbox account for itself, without a session but only with an Apple App Attest attestation (ADR-054) |
| /api/assets | / GET POST | EDC asset registry |
| /api/auth | /[...nextauth] GET POST /eudi/register POST /eudi/start POST /eudi/status GET | Sign-in: NextAuth with Keycloak, and the EUDI Wallet start and status calls. No session needed |
| /api/catalog | /jsonld GET / GET POST DELETE | HealthDCAT-AP dataset catalogue: list, publish, remove |
| /api/compliance | /applications/clock POST /applications/complete POST /applications GET POST /findings/close POST /findings/respond POST /findings GET POST /information-requests/answer POST /information-requests GET POST /permits/revoke POST /permits POST /requests/decide POST /requests GET POST /results GET POST / GET /tck GET | Access body workflows: permit applications and their clock, statistical requests, permits and revocation, findings, information requests, results, and the DSP TCK results |
| /api/credentials | /[id] DELETE /definitions GET /request POST / GET | Verifiable credentials: list, request, revoke, and the issuer's definitions |
| /api/debug | /phase26 GET | Read-only check of the federated discovery plumbing. EDC_ADMIN |
| /api/eehrxf | / GET | EEHRxF profile alignment |
| /api/federated | / GET | Federated query across the participants' graphs |
| /api/graph | /expand GET /node GET / GET /validate GET | Knowledge graph: nodes, expansion, schema validation |
| /api/health | / GET | Liveness and readiness probe. No session needed |
| /api/information | / GET | What the access body tells the public about secondary use, Art. 58(1) |
| /api/jurisdictions | / GET | How six member states implement the EHDS: access body, national law, opt-out, fees, dates (#782) |
| /api/keycloak-config | / GET | Where Keycloak is, for the sign-in banner. No session needed |
| /api/mock-dsp | /[participant]/catalog/request GET POST | Demo DSP catalogue endpoint the catalog crawler polls. A session or the crawler's token (ADR-020) |
| /api/negotiations | /[id] GET / GET POST | DSP contract negotiations |
| /api/nlq | /backend GET / GET POST | Natural language queries through the proxy, and which NLP backend answers them |
| /api/odrl | /scope GET | The caller's effective ODRL scope: permissions, prohibitions, datasets, time limits |
| /api/overview | / GET | The data behind the persona overview: layers, nodes, links and signals |
| /api/participants | /[id]/credentials GET /[id] PATCH /me GET / GET POST | Participant registry, profiles and their credentials |
| /api/patient | /app-devices/[deviceId] DELETE /app-devices GET POST /app-pairing/[pairingId] GET /app-pairing POST /app/account DELETE /app/record GET POST /app/wearables POST /ehr-sync POST /insights GET /observations GET /opt-out GET POST DELETE /profile/interests PUT /profile GET /research GET POST DELETE / GET /wallet-link GET DELETE | The patient's record, profile, insights, research consent, EHR sync, and the Klarbefund app pairing and devices |
| /api/permits | / GET | The access body's register of applications and permits, Art. 57 |
| /api/tasks | / GET | Negotiations and transfers across all participant contexts, as one task list |
| /api/transfers | /[id] GET / GET POST | Data transfers |
| /api/trust-center | / GET | Trust centers with their governance chain and statistics |
Testing
Unit Tests (Vitest)
- UI and Neo4j proxy suites, the UI's mirroring
ui/src/underui/__tests__/unit/ - v8 coverage, published with the test report
- Testing Library for components; a test that needs the network stubs
fetchitself, and Neo4j is mocked at@/lib/neo4j(MSW was removed in #404) - View Test Report
npm test # Run once npm run test:watch # Watch mode npm run test:coverage # With v8 coverage
E2E Tests (Playwright)
- Journey specs in
ui/__tests__/e2e/journeys/(IDs J001 onward) - WCAG 2.2 AA accessibility audit (
27-wcag-accessibility) - OWASP/BSI security & pentest (
28-security-pentest) - Playwright Report
npm run test:e2e # Headless (chromium) npm run test:e2e:ui # Interactive UI # Against JAD stack PLAYWRIGHT_BASE_URL=http://localhost:3003 \ npm run test:e2e
DSP 2025-1 TCK
Validates EDC connector implements Dataspace Protocol correctly: catalog queries, contract negotiations, transfer processes.
./scripts/run-dsp-tck.sh
DCP v1.0
Verifies Decentralized Claims Protocol: DID resolution, credential presentation, trust framework.
./scripts/run-dcp-tests.sh
EHDS Domain
Health domain compliance: FHIR R4 bundles, OMOP transformation, HDAB approval chains, patient rights.
./scripts/run-ehds-tests.sh
EHDS User Journey
The full 8-step EHDS secondary-use journey with sequence diagrams, persona mappings, and E2E test coverage:
View Full User JourneyQuality Gates
Four-stage quality pipeline aligned with BSI C5, OWASP Top 10, EHDS regulation, and WCAG 2.2 AA.
CI/CD Pipeline
test.yml: 8 jobs on every push
- Changed paths
- UI Tests (Vitest)
- Neo4j Proxy Tests (Vitest)
- SPE Loader Tests (Vitest)
- Trustee Policy Tests (opa test)
- ePA Ingest Tests (Vitest)
- iOS Analyte Parity (Swift)
- Supply-chain and posture scans
pages.yml: Deploy to GitHub Pages
- Seed Neo4j and refresh the mock fixtures from the live API
- Disable API routes (
mv src/app/api /tmp/api_disabled) - Build static export (
NEXT_PUBLIC_STATIC_EXPORT=true) - Copy test reports to output
- Deploy to GitHub Pages
nightly.yml: once a night when main changed
Playwright E2E with the WCAG 2.2 AA and OWASP/BSI checks against a seeded Neo4j, the Lighthouse budget, and on the full JAD stack: Image platforms, DSP 2025-1 TCK, DCP v1.0 Compliance, EHDS Health Domain, EHDS API Collection (Bruno), Azure Demo Compliance (ACA Job), Compliance Report. Each compliance suite is held to the floor in scripts/compliance-baseline.json. Skipped when main has no commit since the last green run; a red night opens an issue.
deploy-azure.yml: Azure Deployment
Deploys the 21 Container Apps and their jobs to Azure via OIDC federation. Includes E2E smoke tests against the live Azure environment.
reset-demo.yml: Demo Reset (manual)
Restarts stateful services, re-bootstraps Vault and Keycloak, reseeds data and runs smoke tests. Manual dispatch only since 2026-09-13: the Keycloak realm now holds the Klarbefund client and real accounts, which a scheduled reset would delete.
aca-schedule.yml: Off-Hours Stop and Start
Stops every Container App except the UI, then the PostgreSQL Flexible Server, daily at 18:00 UTC; starts them in dependency order Monday to Friday at 05:00 UTC, skipping Berlin public holidays (ADR-053). See Live Stack Off Hours.
Live Stack Off Hours
Outside office hours the Azure stack is stopped, database included, and ehds.mabu.red answers every page with an offline notice and every data route with 503. Local development is not affected: docker compose and npm run dev know nothing of the schedule. To see the notice locally, start the UI with LIVE_DEMO_OFFLINE=true.
Working on the live stack at night or at the weekend
# 1. Hold it: no scheduled stop before this instant (UTC) gh variable set LIVE_DEMO_HOLD_UNTIL --body "2026-10-10T22:00Z" # 2. Start it (any day, about ten minutes, green only if a login works) gh workflow run aca-schedule.yml -f action=start # 3. Optional: one catalog crawl, it runs in office hours only az containerapp job start --name mvhd-catalog-crawler --resource-group rg-mvhd-dev # 4. Done: release the hold and stop it again gh variable delete LIVE_DEMO_HOLD_UNTIL gh workflow run aca-schedule.yml -f action=stop
- Start the stack before merging at night; a deploy expects the apps to be running.
- A forgotten hold ends by itself: the first scheduled stop after it expires stops the stack.
- Troubleshooting, the state check and the full procedure are in the off-hours runbook.
Latest Reports
Test Coverage Report
Vitest coverage report with per-file breakdown
Playwright E2E Report
E2E test results with screenshots and traces
CI Workflow Runs
GitHub Actions test suite history
Compliance Runs
DSP TCK, DCP, EHDS compliance results
Security Advisories
Trivy SARIF findings and dependency alerts
GitHub Pages Deploy
Static export build and deployment history
Security and Data Protection
Tested by the project itself with tools anyone can run on the repository; every finding is fixed or named as open. The test of 2026-10-15 found ten findings, F1 to F10: after the fixes nine are closed and one is reviewed and accepted (F3, the published development default key of the local compose stack). None of this is an independent penetration test or a certification. The reports are published under /docs/security.
Security test report, 2026-10-15
CodeQL, Semgrep, grype, npm audit, trivy, gitleaks, hadolint, shellcheck, the app's posture tests and the anonymous black-box checks of the live hub; findings F1 to F10 with their status after the fixes, and what an independent test would add.
Threat model: the EHDS Integration Hub
STRIDE for the hub on Azure: the data flows and trust boundaries, the four attackers, one exchange with every threat at its step, and each mitigation naming the code, the test or the decision that shows it; what the demo accepts and what real data needs, and how to verify every row.
Threat model: Klarbefund, the iPhone app
STRIDE analysis of the iPhone app; each mitigation names the code or test that shows it.
Klarbefund: self-assessment
Data protection (GDPR), BSI TR-03161 objective by objective, the EU AI Act label and the MDR status of the app, each line pointing to the code or test that shows it.
Klarbefund: evidence
The accuracy benchmark, the dictionary parity check, the run record, and what is still missing.
Klarbefund: evidence report for health insurers
The three questions an insurer asks, is the extraction right, is the data safe, is it a medical device, answered with the evidence, plus the white-label and consulting offer.
Klarbefund: running the evidence on a Mac
How to reproduce every number in the evidence on a Mac with Xcode.
BSI C5 Gap Analysis — EHDS Integration Hub
The hub against the BSI C5:2020 criteria catalogue, for EHDS Art. 50(6).
Software bills of materials
One CycloneDX bill of materials per deployable part, byte-stable; the --check fails when a bill no longer matches its lockfile.
Report for health insurers, on one page
The same report as one page for a steering meeting, served by the hub
Security scans in CI
grype over the bills and trivy over the images on every push; CodeQL for TypeScript and Swift in their own workflows
Run it again: scripts/security/generate-sbom.sh --check, scripts/security/scan-repo.sh, scripts/security/check-hub-blackbox.sh and swift test --filter SecurityPostureTests in clients/ios. Decisions: ADR-011 (testing strategy) and ADR-060 (the app lock; why pinning and jailbreak detection were declined).
Writing the Documentation
The documentation is part of the repository and the hub publishes it from there. Every page under /docs gets an index of its headings at the right (a disclosure at the top on a phone), and every diagram or screenshot opens full screen with zoom on click.
Choose the kind of page by its reader. A report, guide or runbook read from start to end is Markdown under docs/, which the hub publishes; a page that shows live facts, diagrams and links into the hub is a hand-written React page. Both get numbered headings and the index at the right without further work.
Published documents
- Write the Markdown under
docs/, linking other files by relative path. - Add it to DOCS in scripts/gen-docs-content.py with a section, a slug and a one-line summary. The sections are those of the docs menu: EHDS Regulation, Operations, Security and Data Protection, Klarbefund and Developer Guide.
- Run
python3 scripts/gen-docs-content.pyand stageui/src/content/published-docs.json; pre-commit runs its--check, so the copy never drifts from the source.
Example: docs/developer/api-integration-tests.md is the page /docs/developer/api-integration-tests. A link to another published document becomes its route, any other relative link a GitHub link; embedded images are copied, and a mermaid block is drawn, as in the SDLC's lifecycle.
Hand-written pages
A page under ui/src/app/docs/ is a React component, like this guide, developer/page.tsx. Its texts go through useT() and the dictionaries in ui/src/messages/, one per language; i18n-messages.test.ts fails on a text without an entry.
DocsToc numbers every h2 and h3 and lists it in the index at the right; a heading inside a nav or a data-toc-skip block stays out, and one that shows its own number keeps it.
Diagrams in ArchiMate and UML
- Write the PlantUML source in
docs/<area>/diagrams/with the ArchiMate library (ArchiMate 3.2) for structure, or as a sequence diagram where the order of messages matters. Example: cicd-pipeline.puml. - Render it with
scripts/render-plantuml.sh(needsbrew install plantuml) to img/cicd-pipeline.svg; translate its labels in cicd-pipeline.labels.json and runpython3 scripts/render-plantuml-i18n.pyfor the German, French, Dutch and Spanish variants. - Embed it with
DocFigureinside aDocFigureViewer, as the CI/CD section of this page does; a click opens it full screen with zoom.
Two things the ArchiMate library rejects: a ;line: colour suffix on a band, and relations that point downward across layers, which flips the band order. Use the _Up, _Right and _Left variants of each relation.
Screenshots
The user guide's previews are taken from the public demo by capture-guide-screenshots.spec.ts (CAPTURE_GUIDE=1, one WebP per page, SCREENSHOT_LANG=de for a language) and shown through LocalizedImage; gen-localized-images.py lists the translated variants.
Klarbefund's come from a simulator through clients/ios/Scripts/capture-screenshots.sh with the fictional dev dataset (-MBDevData -MBShot <screen>), at half resolution under docs/klarbefund/img/, as in the Klarbefund guides. No real report, value or organisation may ever appear in one.
Facts and dates
Numbers on these pages (routes, services, hooks, versions) come from ui/src/app/docs/docs-facts.json, written by scripts/gen-docs-facts.py from the repository; the same script keeps each page's last-updated date, and its check fails a commit whose page changed without it. Run python3 scripts/gen-docs-facts.py after editing a docs page.
Example: the job list under CI/CD Pipeline is facts.workflowJobs, so a new job in test.yml appears there by itself.
Translations
Every page follows the language picker (ADR-071). A published document's translation is docs/i18n/<lang>/<path>, stamped with the English file's hash by gen-docs-content.py --stamp, for example the German article numbering; --report-i18n lists what is missing or stale. A page's texts live in the dictionaries, a diagram's in its .labels.json, a screenshot's variant in a <lang>/ folder next to it.
Data Flow
Release Notes
The version is the one in ui/package.json; when a merge to main changes it, release.yml tags it and publishes the release. How releases and the TestFlight builds of the iPhone app work
Klarbefund iPhone app, an overview per persona, the access body's tasks, compliance suites that can fail
GraphRAG for natural-language queries (GDS, FastRP, reranking), releases published automatically
Azure Container Apps deployment, SIMPL-Open gap analysis, nightly demo reset, 14 ADRs, quality gates documentation
WCAG 2.2 AA zero violations, ODRL policy enforcement, OWASP/BSI security pentest, 778 Playwright assertions
Initial release: 5-layer Neo4j knowledge graph, 127 synthetic patients, DSP 2025-1 + DCP v1.0, 7 personas, GitHub Pages demo
Planning & Issues
Planning Document
Project roadmap, phase tracking, cross-cutting concerns, and ADR index
GitHub Issues
Feature requests, bug reports, and task tracking
CI/CD Workflows
test.yml, pages.yml, compliance.yml, deploy-azure.yml, reset-demo.yml
SIMPL-Open Gap Analysis
Simpl-Open, the adopted EHDS and the hub compared: identity, catalogue, permits, fees, opt-out, DOME
Onboarding Guide
New developer onboarding: first day checklist, key concepts, common tasks
Conventions
- Commit messages: Conventional Commits format (feat:, fix:, docs:, chore:)
- Branch strategy: Feature branches → PR to main
- Pre-commit: 34 hooks, listed on the quality gates page: Prettier, ESLint, TypeScript, Semgrep, gitleaks, the API collection check and more
- Pre-push: Full Vitest suite (--bail), npm audit (HIGH+), and CodeQL on the iPhone app when it changed
- Cypher: UPPER_SNAKE_CASE relationships, PascalCase labels, camelCase properties, always MERGE
- TypeScript: Strict mode, no any, @/* path alias → ui/src/*
- Fictional orgs only: AlphaKlinik Berlin, PharmaCo Research AG, MedReg DE, Limburg Medical Centre, Institut de Recherche Santé