Repository navigation
v2.0.0: iOS App Klarbefund, EHDS compliance, new Graph Visualization
The EHDS Integration Hub turns the European Health Data Space Regulation into software you can run. This release is the point at which it stopped being a graph you explore and became a platform each participant logs into and uses.
382 commits since v1.5.1, across 1167 files, over five months.
Every persona lands on its own overview
The graph explorer left the navigation. A patient, an access body, a data holder and a researcher now each sign in to a view built from their own data, not a shared canvas with their name on it (#271).
- Patient: record reads, who looked and when, opt-out state, the date and time of the last EHR sync, and Art. 77 completeness.
- Access body: the permit queue and the decisions behind it.
- Data holder: what is offered, under which policy, and who has taken it.
- Researcher: the cohorts a permit actually reaches.
Each overview is a 3D scene with keyboard control and a 30 second auto-orbit, sized to fill the viewport rather than sit in a panel. The data behind each one is seeded and has its own derivation library and test cases, so the numbers on screen are derivable rather than decorative (#272).
The access body does its whole job
The Health Data Access Body now carries its full task set under Chapter IV, M1 through M7 (#206): permit applications, approvals and refusals, the prohibited-purpose refusal among them, each visible in /compliance with the reasoning attached.
The compliance suites can fail
Before this release they could not, which is a harder problem than a failing test.
ADR-031 is the rule that came out of it: a check that cannot fail is not a check. Acting on it found real defects that had been reported as passes for as long as they existed (#338, #344, #375):
- Requests that passed on an empty answer, because
res.status: lt 500was the only assertion. - An issuer service bound where nobody looked, answering 502 on the demo and 200 everywhere else (#373).
/api/debug/phase26answering anonymous callers, filed in the collection as an operator route (#377)./api/patientanswering without a session while the role matrix said otherwise (#357).- A CI stack whose CFM Tenant Manager has never once run, hidden because the tenant list silently backfilled from Neo4j (#380).
scripts/compliance-baseline.json records what each suite scores, so a regression fails the build instead of passing quietly.
An API collection organised by who sends the request
bruno/MVHDv2 is now 187 requests across 16 folders, one per role in Regulation (EU) 2025/327, plus the two-sided procedures of Chapter IV, the role boundaries, and the protocol surfaces (#349, ADR-032). Every request asserts the status it expects and something about the body. A request whose subject cannot exist on the stack it is run against skips loudly with the reason, rather than passing in silence.
scripts/check-bruno-coverage.py and check-bruno-strength.py run in pre-commit and the PR gate: a new route with no request, a request whose route is gone, a request that cannot fail, or a credential in a .bru file all fail the build.
A companion iOS app
A lab report scanner that reads a PDF or a photo and turns it into structured, coded results: analytes ordered by how far each sits from its range, published reference ranges quoted from named sources, microbiome organisms named by their NCBI Taxonomy id, and values a chart plots but never labels reported as estimates and marked as such. Everything it holds can be handed over or deleted for good.
Supply chain and platform
- Image platform verification (#380).
scripts/check-image-platforms.pyasks each registry which platforms an image really serves, without a Docker daemon and without pulling, so an image that cannot run on its host is caught before it is started rather than after it silently fails. - CFM images buildable from source for any platform, with OCI source and revision labels (#318, #391).
- Participant contexts seeded onto the Azure control plane (#316), and activation understood as asynchronous rather than broken (#328).
- A file-backed Vault, so a container restart no longer takes every participant's signing key while the identity checks keep passing.
- CycloneDX SBOMs on every CI run, licence allowlist, Trivy, gitleaks, Kubescape and a Lighthouse budget.
Recognition
The platform received the Open Solution Award in the 2026 HL7 AI Challenge. The showcase deck, with recordings of the patient and researcher journeys, is served from the platform itself (#252).
Where it stands
| ADRs | 40 |
| API routes | 67 |
| Unit tests | 2212, across 161 files |
| E2E journey specs | 48 files |
| API collection | 187 requests, 16 persona folders |
| JAD stack | 23 services |
Try it. Static demo: https://ma3u.github.io/MinimumViableHealthDataspacev2/ · Live stack: https://ehds.mabu.red (Mon-Fri 07:00-20:00 Europe/Berlin) · docker compose up -d for the local stack.
All clinical data is synthetic (Synthea). All organisations are fictional. No real patient data is involved anywhere in this project.
Known open
CFM provisioning agents are not yet deployed on Azure, so a participant registered through /onboarding still stalls with three pending activities (#318). The images can now be built for the right platform; publishing them and deploying the agents is the remaining work.
Full commit log, 382 commits since v1.5.1
Changes
- chore(release): 2.0.0 (#392) (7cefea1)
- feat(cfm): build the six CFM images from source, for any platform (#318, #380) (#391) (150cb74)
- fix(ci,azure): the CFM images are arm64 only, and nothing said so (#380, #318) (#390) (5b263af)
- fix(azure): poll for participant activation instead of re-reading once (#328) (#389) (24a0f65)
- fix(auth): gate the operator debug view, and say why the other one stays open (#377) (#388) (deb193e)
- fix(ci): make Demo Smoke carry signal again, and fix the issuer it kept reporting (#373) (#387) (6c486a5)
- fix(api-tests): three Access Body checks are empty on a fresh stack, not broken (#375) (#383) (8be63c9)
- test(api): a successful request must not pass on an empty answer (#375) (#382) (a0eacf4)
- fix(api): let the credentials 502 carry its cause (#380) (#381) (7db4d0a)
- fix(ci): raise the Bruno floor to 147/2, and say why the credentials check is red on CI (#379) (163f92d)
- fix(ci): say why the issuer never came back, not just that it did not (#374) (b74a41c)
- fix(api): make the OpenAPI spec describe what the hub actually answers (#338) (#378) (801914a)
- fix(auth): gate the patient index (#357) (#376) (6ee7590)
- fix(ci): run the compliance suite when its subject changes, and raise the floor (#371) (#372) (63e37ac)
- fix(api-tests): the credentials check was green on an empty answer (#369) (#370) (b6d5ab5)
- fix(api): one shape from /api/admin/policies, whichever backend answers (#367) (#368) (a2252f7)
- docs(api): record that /api/patient is public on purpose, and assert it (#357) (#366) (172ebbe)
- fix(ci): make a workflow re-run when the scripts it runs change (#364) (#365) (c784aee)
- fix(exchange): say which listed rows the detail routes can actually open (#358) (#363) (5e89d9f)
- fix(identity): give the control plane the STS client secret the IdentityHub already holds (#349) (#362) (f20f3b4)
- fix(seeds): wait on the issuer's own healthcheck, not a URL that was never served (#349) (#361) (1dd2d15)
- fix(ci): the seed steps fail the build now that they have been seen to work (#349) (#360) (af3feeb)
- fix(ci): keep raw runner reports out of the suite directory, and raise the floor to 141/8 (#349) (#356) (f1982e4)
- feat(ci): give the live demo its own compliance floor (#349) (#355) (6db57e3)
- fix(ci): seed what the API collection actually needs, so the floor measures the platform (#349) (#354) (4fb41fd)
- fix(bruno): absolutise the report dir so the CI baseline gate sees the results (#349) (#353) (a1348c0)
- fix: build the Neo4j proxy from source, and 404 a consent for a non-existent study (#349) (#352) (3e8be9c)
- fix(azure): stop hardcoding NEXTAUTH_SECRET to the published default (#349) (#351) (ab4aeab)
- feat(api-collection): organise the Bruno collection by EHDS persona (#349) (#350) (65dc92c)
- fix(ci): seed CI's identity layer and issue credentials over DCP; Vault keeps its keys; the DSP floor is honest (#345) (#347) (5dc26ac)
- feat(ci): record the compliance baseline, and two of the three floors are known bad (#338, #345) (#346) (7661404)
- feat(ci): Phase 0 — the compliance workflow can fail now, and ADR-031 is Accepted (#338) (#344) (22a9d25)
- docs(gotchas): the STS both EDC services run, on unpublished ports (#338) (#343) (1e46b8b)
- refactor(compliance): name the two suites for what they test (#338) (#342) (23e37d1)
- fix(dcp-tests): give the 10 checks that cannot fail a reachable failure (#338) (#341) (f2a536a)
- feat(ci): fail the build when a route is not in the OpenAPI spec (#338) (#340) (40e2152)
- docs(sdlc): §6.3 published a DSP figure that was one pass too high (#110) (#339) (ff85615)
- docs: three specification claims that no longer match the code, and a stub that passes our DSP suite (#110) (#337) (b41831a)
- fix(compliance): the call sites were discarding the diagnostic too (#180) (#336) (3524349)
- fix(dcp-tests): reject EDC error envelopes there too, from one shared place (#335) (be07515)
- fix(jad): stop printing eight endpoints that have never resolved (#190) (#334) (bb5c8ab)
- fix(dsp-tck): stop reporting EDC error envelopes as passes (#333) (a6ca362)
- fix(jad): assert a JetStream round trip during bring-up (#191) (#332) (1b9f4ab)
- fix(dsp): send the profile id the connector registers, not the DSP version (#180) (#331) (4830222)
- fix(ui): the live participant list has been bundled mock data (#317) (#330) (97949a9)
- docs(sdlc): the local target produces DSP and DCP verdicts too (#316) (#329) (f61db9d)
- fix(azure): the activation in #325 does not activate anything, so stop claiming it does (#316) (#327) (7850605)
- docs(sdlc): DSP and DCP produce a verdict on Azure for the first time (#316) (#326) (f779cdf)
- fix(ci): seed the ephemeral stack, and fix a readiness probe that never worked (#316) (#323) (616f717)
- fix(azure): activate the seeded participant contexts (#316) (#325) (2522913)
- fix(azure): the participant seed needs the provisioner role, not admin (#316) (#324) (0f1d415)
- feat(azure): seed participant contexts onto the control plane (#316) (#322) (793ac34)
- fix(compliance): mgmt_request died under set -e before it could report anything (#321) (7e90f62)
- docs(sdlc): the Protocol Compliance tier is green because it cannot fail (#320) (dca74f4)
- docs: record what the Azure compliance target now produces, and why (#307, #203) (#319) (21966e2)
- fix(onboarding): stop promising a DID that no agent is going to issue (#203) (#315) (edc243d)
- fix(compliance): the Azure suites were calling a port and a version that do not exist (#307) (#314) (3336b1a)
- fix(ci): reset-demo's dirty check now actually asks Neo4j (#304) (#313) (34654df)
- fix(azure): the realm restore could not read the admin password in CI (#312) (f431374)
- fix(ci): reset-demo must not wipe the edcv realm without being asked (#311) (17f44af)
- fix(ci): reset-demo could not log in to Keycloak, so the realm was never re-imported (#310) (9a1adfc)
- docs(sdlc): §4.1 describes the ruleset as applied, not as planned (#309) (dcd432c)
- chore(ci): one always-on PR gate, so
maincan actually be protected (#308) (bfdd868) - fix(azure): the compliance runner addressed the EDC services on local compose ports (#306) (90f27aa)
- fix(azure): the compliance-runner job could never be created (#205) (#305) (9dcbb11)
- fix(azure): the last two shell callers of Neo4j's port 7474, and two claims the audit page could not back (#205) (#303) (642caf3)
- fix(hl7): the deck's scenes sit further right, the close one larger, and nothing else is drawn first (#252) (#302) (e4c155c)
- feat(hl7): the person's journey gets its own recorded backup, and its graph box opens the overview (#252) (#301) (03c8d7e)
- feat(hl7): the deck opens on the patient's overview and closes on the researcher's (#252) (#300) (7cc07a6)
- test(e2e): journey 46 waits longer for the live stack; two verification traps in gotchas (#206) (#299) (105dc0c)
- feat(hdab): the access body's remaining tasks, M1 to M7 of the plan (#206) (#298) (4247fe4)
- docs(hl7): the researcher animation re-recorded on the platform as it is now (#252) (#297) (cae7820)
- fix(ui): the last EHR sync line waits for the record and stays off other roles' pages (#271) (#296) (2fad2a7)
- test(e2e): J982 and J983 check the last EHR sync line and the stamp flow (#271) (#295) (6b99c62)
- feat(patient): show the date and time of the last EHR sync (#271) (#294) (4834583)
- fix(api): the own-record patient list carries the timeline the page renders (#271) (#293) (5028a68)
- fix(data): the patient1 login owns Maria Schmidt with her visits and timeline (#271) (#292) (02c7acb)
- fix(ui): say whose decision is late, on what, and what it blocks (#271) (#291) (2eecbf4)
- feat(ui): every login lands on the overview; the graph explorer leaves the navigation (#271) (#290) (ac85804)
- feat(ui): keyboard control of the overview scene and a 30 s auto-orbit (#271) (#289) (32a9be5)
- feat(ui): compact left-aligned header, the scene fills the rest of the viewport (#271) (#288) (83225a1)
- feat(ui): a login lands on the persona overview (#271) (#287) (2fe5666)
- feat(ui): the overview uses the full width and the viewport height (#271) (#286) (7ca1946)
- fix(ui): no stale Forbidden banner over a loaded overview, and the camera keeps the discs in view (#271) (#285) (8cbac2e)
- fix(ui): the patient login opens its overview and the scene no longer goes black on Azure (#271) (#284) (58a5c89)
- feat(ui): the data behind the views: record reads, opt-out, Art. 77 completeness (#271 M6) (#283) (0a38fc7)
- docs: persona overview knowledge page, ADR-040, static export refresh and J975 (#271 M5) (#282) (4c4602c)
- feat(ui): researcher overview, the fourth persona view of #271 (M4) (#281) (b6b950e)
- test(e2e): J972 accepts the partly uncovered flow the Azure log gives Limburg (#271) (#280) (579ef91)
- feat(ui): holder overview, the third persona view of #271 (M3) (#279) (3179555)
- fix(ui): a refused applicant that still attempts shows its refusals per month (#271) (#278) (64eb4f2)
- feat(ui): access body overview, the second persona view of #271 (M2) (#277) (6515d95)
- fix(ui): HbA1c leads the patient overview whatever order the graph returns (#271) (#276) (f0bde95)
- feat(ui): patient overview, the first persona view of #271 (M1) (#274) (e370414)
- revert: take four unrelated local files back out of #272 (#273) (b14990e)
- feat(data): seed, derivation library and test cases for the persona overview (#271) (#272) (5c7fe70)
- feat(ui): researcher, access body and holder prototypes carry monthly series with trend, chart and description (#270) (bc9b872)
- fix(ui): the prototype camera stays put while a node is selected (#269) (9fd9a84)
- feat(ui): patient prototype unfolds a parameter into its measurements, with trend and description on the right (#268) (6813aeb)
- docs: screenshots of today's patient and researcher graph views for discussion #265 (#267) (571522a)
- feat(ui): persona-first 3D graph prototypes for patient, researcher, access body and holder (#266) (deede47)
- fix(ui): swimlane boxes link through a click handler, not an SVG anchor (#264) (fd9a9ce)
- fix(ui): the deck's 3D graph drawn in the slide, and layout and animations waiting for the fonts (#263) (1f8eb28)
- Update blog-article.md (aed7297)
- Update blog-article.md (016138a)
- fix(ui): the narration player stays quiet when a slide cuts the previous file off (#262) (6222a21)
- feat(ui): the deck narrated in the presenter's own voice, and the graph's labels inside their bands (#261) (972ffa8)
- fix(ui): the phone stands the full viewport height on the app slides (#260) (c0677d3)
- feat(ui): full-height animated phones on every app slide, a brighter graph on the close, and a narration player (#259) (23e0d5a)
- docs: the app at full height on the right, an app example per ask, a structured close, and the article with links and animations (#258) (97341f0)
- feat(ui): the HL7 deck cut to twelve minutes, opening with the story and a 3D graph (#257) (670188c)
- feat(ui): the two demo flows, an animated knowledge graph and deep links in the HL7 deck (#256) (9f30ca1)
- fix(ui): serve a presentation's folder URL on the live platform (#255) (6f1c6fc)
- feat(ui): the HL7 showcase deck served from the platform, with recordings of both demos (#254) (c6eeb3d)
- ops: keep the stack up on 14 October for the HL7 webinar, and draft the blog article (#253) (7178448)
- docs(ios): Klarbefund screen by screen, with screenshots from the fictional dataset (#251) (cf14a8b)
- feat(ios): name every microbiome organism by its NCBI Taxonomy id, verified against NCBI (#250) (61752b4)
- fix(ios): one card per measurement on the reference screen, however many units it is published in (#249) (fe72d38)
- feat(ios): filter the reference values by category, and enter a run of past measurements at once (#248) (bbbc9d3)
- feat(ios): a published range for 57 more analytes, each quoted from a named source (#247) (4fc2a08)
- feat(ios): hand over everything the app holds, delete it for good, read a report from Photos (#246) (1a14d9d)
- fix(ios): a value typed into the profile stays typed, and says it was entered by you (#239) (9f69fb9)
- feat(ios): order a hundred analytes by what is furthest from its range, filter them, and work out what a sheet did not print (#238) (55dde5a)
- fix(ios): recognise a microbiome panel instead of leaving all of it unmatched (#237) (7ae542e)
- feat(ios): split a table that a PDF flattened onto one line (#236) (2910f3b)
- feat(ios): refuse an article instead of mining the numbers inside its sentences (#235) (5880f2e)
- feat(ios): read an aminogram, and stop a font's own icons hiding values (#234) (057972a)
- fix(ios): read a chart's axis again when it is printed too small, and stop the reader inventing points (#233) (f9d16bb)
- feat(ios): measure the points a scale draws but does not label, and say they are estimates (#232) (88c632d)
- feat(ios): read the year a scale plots, not only the value it names, and never store one twice (#231) (69a7a2f)
- build(ios): build 2 on TestFlight, with What to Test (#230) (0a0c644)
- feat(ios): a test database of seven reports across three years, in developer mode (#229) (0e6a3db)
- test(ios): drive the app in a simulator, and give every measurement a real target (#228) (41a4475)
- build(ios): build for a generic device, so an absent phone costs an install and not a rebuild (#227) (ca45e4f)
- fix(ios): a tapped point stays tapped, and a missing LOINC code stops printing as Optional (#226) (91348cc)
- feat(ios): tap a point to see the measurement, tap the card to open its report (#225) (80adf84)
- fix(ios): the waist is saved, each measurement keeps its own date, and an old reading is found again (#224) (4319ffc)
- build(ios): declare export compliance once, instead of per upload (#223) (6e3cb51)
- build(ios): upload to TestFlight with the Apple ID in Xcode, no issuer id needed (#222) (d2cffec)
- build(ios): version 1.0 to match the App Store record, and an upload-only path (#221) (071c319)
- feat(ios): the texts a TestFlight tester reads, and a description that matches the app (#220) (c70140b)
- feat(ios): the values a scale reports that LOINC has no code for, and a profile you can edit (#219) (22a309a)
- feat(ios): the laboratory's own range becomes the band, and every point says where it came from (#218) (154c266)
- feat(ios): read a body-composition scale's screen from a photograph (8293d3b)
- feat(ios): a profile, body measurements, and colour on the published ranges (b11e5d2)
- feat(ios): OMOP export, and the fixes an audit of seven real reports found (2c9b334)
- feat(ios): the scan is kept, the sheet's own dates are read, and published ranges sit beside them (022aa14)
- feat(hdab): the activity report of the access body, generated from the graph (Art. 59) (fe06f6a)
- chore(seed): drop the DataTransfer index that clashed with the schema constraint name (4518435)
- feat(hdab): the data permit gates the secure processing environment (Art. 61(1), 73) (89025f4)
- chore(seed): log in again after the long wait in the seed workflow (a646003)
- chore(schema): HealthDataRequest and the permit index, mirrored in the schema doc (4686385)
- chore(seed): a demo-only fast path for the governance and audit seeds (492000b)
- feat(hdab): health data requests answered in anonymised statistical format (Art. 69) (3038afd)
- feat(audit): access events carry provider, dataset, permit and bytes, and open into their record (7583f50)
- feat(hdab): the public permits register and revocation of a data permit (24996ad)
- test(hdab): journey 40 applies for a dataset the consumer holds no permit for (6315dca)
- test(hdab): journey 40 asserts on the criteria legend, the one 'Art. 68' the panel shows once (f6d8f5a)
- feat(hdab): the access body decides on applications, and the permit gates the transfer (e03e465)
- test(audit): journey 39 waits for the proxy's cold start on Azure (3225a62)
- feat(audit): the Access Logs tab shows the access events the proxy records (18e2ece)
- fix(audit): the Audit & Provenance page reaches Neo4j over Bolt, so it works on Azure (d62e910)
- docs(azure): the short-lived job log loss is intermittent, not total (9dfc004)
- fix(azure): the CFM seed and createdb jobs linger so their output is collected (7a6cb9c)
- fix(azure): the CFM seed workflow shows this execution's logs, not an older one's (7c7b5da)
- fix(azure): the CFM seed can now reach the managers, and says so when it cannot (92f9818)
- fix(azure): stop the YAML round-trip from wiping the config secret (528ab37)
- fix(azure): the CFM managers never got the config file they read (8fd4afe)
- fix(azure): reach the CFM managers on their target port, and wait for the logs (87bc26d)
- fix(azure): create the cfm database from inside the environment, not through exec (df3a8ee)
- feat(azure): seed the CFM cell and dataspace profile that onboarding reads (0ba2f41)
- fix(onboarding): Register Participant no longer dies on a bare error string (2e0941f)
- fix(negotiate): a demo negotiation now reaches the transfer step (0b2e56b)
- fix(negotiate): Step 2 no longer ends the walkthrough on a 502 (fe054c5)
- test(e2e): guard that Discover Offers returns offers, not a connector error (a0f0838)
- fix(negotiate): Discover Offers sent a demo participant id to the live connector (40b485a)
- docs(deck): slide four carries the Catena-X lessons, not a layer diagram (133db43)
- docs(diagram): cut the slide overview to three tiers and one question (c4704be)
- docs(diagram): the slide version of the operating model is an overview, not the document (3844538)
- docs(deck): the five-slide deck is about secondary use, at the larger size (8de9787)
- docs(deck): point the five-slide deck at primary use, and make the art readable (a5b0b7a)
- docs(deck): the whole pitch in five slides, as its own file (6cc918e)
- docs: how to actually move the secure processing environment to Contrast, and five closing slides (eb8a258)
- docs(deck): six closing slides on who would operate the health dataspace (7f2e43a)
- docs: what an operating company for the EHDS would actually be allowed to do (c20d633)
- docs: the realm vanishes because Postgres has no storage attached (5d2a4c2)
- fix(test): federated-nlq depended on another file's mock leaking into it (779f069)
- fix(e2e): a login suite pointed at the wrong Keycloak must fail, not skip (76043bf)
- fix(azure): a realm that cannot be restored must not stop the rest of the start (9e25505)
- fix(azure): re-import the Keycloak realm on every start, because it does not survive one (2750d08)
- feat(spe): make the secure processing environment an honest model, and plan the real one (c807a41)
- feat(demo): a second access body, a corrected diagram, and both demos per step (823350a)
- fix(azure): restore the edcv realm, which had vanished and broke every login (b133de0)
- feat(ui): make the secondary-use journey a set of deep links, and test it (6eb0cce)
- feat: rename the app to Klarbefund, in both stores and both languages (10ec565)
- feat(clients/ios): the App Store listing, and two languages that were only one (f57a82e)
- feat(clients/ios): export the report as a PDF and a FHIR bundle, entirely on device (2300aec)
- feat(clients/ios): analyse on device by default, so the app works when nothing else does (54ae890)
- docs(clients/ios): TestFlight links, what exists, and the two blockers (a4af014)
- feat(clients/ios): the App Review requirements external testing needs (4db3390)
- feat(clients/ios): bring your own provider, English and German, and stop the weekly wipe (b5e9166)
- security: move the Keycloak admin password to Key Vault, and two ADRs (#186) (efd448a)
- feat(claude-federation): Azure EU by default, with a daily cap on operator-paid use (#186) (f871ef5)
- feat(azure): provision the user-facing OIDC half, and three bugs it exposed (#186) (340d896)
- fix(claude-federation): boot half-configured, and report the claims for setup (770c95f)
- docs: setup runbook for both OIDC sides, and resolve JWKS by discovery (#186) (ed3c5c9)
- feat(clients/ios): ask Claude about selected values, behind a per-call consent (#186) (096c37e)
- feat(claude-federation): reach the Claude API by workload identity, from a backend (#186) (a7dc34e)
- fix(clients/ios): read lab tables by column role, not by position (#186) (2d1e351)
- feat: MeinBefund iPhone app + Apple Health trend summary (#188) (c960259)
- fix(jad): restore CFM participant provisioning (#181) (#189) (35714ab)
- docs(eudi): pitch deck — Consent Wallet for the EHDS (use-case track, #72) (#73) (a3fb280)
- chore: OKF v0.2 migration + Copilot pack and Claude Code stack refresh (#111) (7373ec7)
- feat(epa-ingest): lab report to FHIR R4 with provenance (#185) (5b14134)
- fix(azure): default AOAI deployments to the EU data zone, audit existing ones (#187) (33d54a4)
- docs: re-scope to the ePA ingest path; defer the GP and community journeys (#184) (979d1f5)
- docs: actor journeys for the #72 consent demo, and the plan that builds it (#183) (9e3be13)
- fix(ci): reusable test workflow cancelled its own callers (#179) (4bac7a1)
- ci: block the catalog-* deploys on their own test suites (#178) (df99408)
- fix(jad): my #171 failure counter never incremented — it ran in a subshell (#177) (293eb94)
- fix(jad): compose could not start the controlplane it pins (#176) (ecaef88)
- fix(jad): detect EDC store schema drift, which autocreate cannot (#174) (d4a6d41)
- ci: run the Python service test suites that nothing was running (#170) (#173) (6e56288)
- docs(adr): ADR-031 — checks must assert their outcome and exit non-zero (#172) (ec18fe4)
- fix(jad): seed script reported success on a wholly failed run (#169) (#171) (d945192)
- docs(planning): plan the five #5 pentest items that need a harness (#168) (c8e79cb)
- fix(security): automate the issue #5 pentest checklist, and fix what it found (#167) (b62568e)
- fix(azure): pin all 15 deployed apps, and gate on it (#116) (#166) (426e6a2)
- fix(graph): return a connected subgraph instead of unrelated top-N slices (#165) (e846e2e)
- fix(ci): reset must verify demo logins work, not just that the realm exists (#164) (d7a03d8)
- fix(ci): smoke tests must target the custom domain, not the raw ACA FQDN (#163) (ce662e3)
- fix(azure): pin the image SOURCES, and make the JAD/CFM pin expressible (#116) (#159) (0c7a048)
- chore(deps): bump the python-patch-minor group across 2 directories with 4 updates (#151) (8394e0e)
- chore(deps): bump zaproxy/action-baseline (#154) (155db07)
- fix(deps): stop the actions and pip groups sweeping majors into one PR (#143) (c573082)
- fix(ci): reset-demo skipped the realm repair because it only checked Neo4j (#113) (8ba394c)
- chore(deps-dev): bump respx in /services/catalog-crawler (#124) (92a1e98)
- chore(deps): bump httpx in /services/catalog-crawler (#127) (570f3bf)
- chore(deps): bump alpine in /scripts/azure/compliance-runner (#117) (4b928eb)
- chore(deps): bump the ui-patch-minor group in /ui with 12 updates (#134) (8e12c62)
- chore(deps): bump the proxy-patch-minor group (#125) (4d403e1)
- fix(e2e): authenticate the smoke specs and repair their stale assertions (#115) (#142) (1288b42)
- fix(keycloak): derive SSO provisioning from the realm file, not a hardcoded list (#141) (cc01c63)
- chore(security): SBOM + automated CVE scanning, and three gaps it uncovered (#114) (2f52d9a)
- Create SECURITY.md for security policy and reporting (272ccd0)
- Fix newline at end of dependabot.yml (7a600d1)
- Update issue-26-healthdcat-ap-validation.md (a879f42)
- fix(ci): reset-demo referenced phantom ACA job names — Neo4j was never re-seeded (#109) (cf90b03)
- docs(planning): issue #26 — inventory of existing quality/validation building blocks (#108) (eb715c7)
- fix(ci): retry Keycloak admin token in reset-demo; plan issue #26 SHACL/DQV validation (#107) (d58ae61)
- docs(runbook): ACA Postgres is ephemeral — recovery runbook + migration-runbook correction (#106) (2f31fb8)
- docs(deps): issue #97 Phase C — Postgres migration runbook + ADR-030 Neo4j spike (#105) (1579f1a)
- chore(keycloak): 26.6.0 → 26.6.4 patch bump (issue #97 Phase C, ADR-029 cadence) (#104) (0f1632e)
- chore(node): Node 20 → 22 LTS across CI, Dockerfiles, engines (issue #97 Phase C) (#103) (9d235aa)
- feat(edc)!: promote EDC 0.18 JAD launchers to the default stack (issue #97 Phase B) (#102) (94230de)
- feat(deps): issue #97 Phase B — EDC v0.18 analysis + opt-in upstream JAD overlay (#101) (c4bc726)
- chore(deps): issue #97 Phase A — pin all image versions, ADR-029 (#100) (3db81e0)
- docs(planning): widen issue-97 item — Metaform tier + OSS baseline (#99) (695f7b3)
- docs(planning): track issue #97 (EDC v0.18.0 upgrade) as future item (#98) (bb31ccc)
- chore(claude): repo-grounded control stack — settings guard, skills, agents, ADR/OKF/planning scaffolding (#96) (90d4dcd)
- feat: Phase 26 federated discovery (#8) + production auth fixes + realm guard tests (#95) (a6516c4)
- fix(a11y): drive WCAG color-contrast violations to zero on patient + docs pages (390f705)
- fix(ci): aca-schedule scale-to-zero — max-replicas 0 was rejected, apps billed 24/7 (#92) (#93) (f7e4dad)
- fix(ci): stop pages.yml E2E steps from orphaning the dev server on :3000 (0921158)
- feat(patient): nutrition weekly goals — 30 plants/week, fibre & protein (#91) (ca02606)
- feat(patient): mark ePA events on the answer trend graphs (#90) (99d184f)
- feat(patient): NLQ 3 questions w/ ePA events in answers + query-on-top + fast Pages deploys (#89) (f8687e0)
- feat(patient): NLQ over ePA events — infections, surgeries, vaccinations (#87) (26e970c)
- feat(patient): NLQ free-text input — answer + trend appear after Send (#86) (e08e6fd)
- fix(journey): remove the Blood Test Oracle brand image + name from the labs card (#85) (ea0e6fd)
- feat(patient): personal NLQ research page + fix demo sign-out clearing patient data (#84) (45f5512)
- feat(patient): interactive EUDI approval, Maria record, trend dashboards, iPhone-17 dialog (#83) (a329b56)
- feat(journey): real ePA+fitness+lab sources on donate slide + journey E2E (DEMO_TK) (#82) (96b7791)
- feat(journey): EUDI wallet flows (register/login/TK ePA transfer) + homepage CTA (#80) (#81) (55f5596)
- feat(journey): simulated EUDI Wallet flow next to the QR (#79) (a2e932c)
- feat(home): hero CTA to the patient journey + live EUDI login (#78) (b2266d1)
- fix(journey): prefix basePath on slide images for static export (#77) (bc4011f)
- feat(journey): Maria's EHDS Patient Wallet journey — 4-step presentation (#76) (6df6256)
- fix(eudi): use verifier-backend.eudiw.dev (frontend host 405s on the API) (#75) (9d5bc3e)
- feat(auth): patient QR login via EUDI Wallet (OpenID4VP) — ADR-028 (#72) (#74) (77b4b3f)
- docs(planning): EUDI wallet runs in iPhone Simulator — run steps + status (#22) (#71) (993c953)
- feat(keycloak): OID4VCI PatientIdentityCredential config (EUDI hackathon, #22) (#70) (b125807)
- docs(planning): EUDI Wallet Hackathon 2026 plan — patient identity (#22) (#69) (023b95e)
- docs(submission): add demo screenshots (graph, governance, AI) to HL7 PDF (#66) (#68) (b12948e)
- docs(submission): add HL7 AI Challenge 2026 PDF + source (#66) (#67) (c4fa3f4)
- docs(agentic): clarity pass — per-section 'why it matters', tool/concept links, top-down diagrams (#55) (#64) (d2db6fb)
- docs(sdlc): rewrite SDLC for clarity — diagram, acronyms, per-step explanations, tool & report links (#55) (#63) (d804878)
- docs: hyperlink in-repo file references to GitHub in SDLC & Agentic docs (#55) (#62) (f0074f6)
- docs: add plain-language guides for SDLC & Agentic AI (#55) (#61) (d46f95c)
- docs(readme): link SDLC and Agentic-AI-with-Claude-Code guides (#55) (#60) (430c0d6)
- Fix formatting in AGENTIC-DEVELOPMENT-WITH-CLAUDE-CODE.md (0e89daf)
- Separate maintainer information from last updated date (a506e6e)
- Update formatting in SDLC documentation (47fc107)
- docs(claude): correct max-warnings note — pre-commit hook enforces it, not CI (#55) (#59) (6444e0e)
- docs(sdlc): correct CI/pre-commit lint nuance, spec count, settings excerpt (#55) (#58) (c0da986)
- docs(sdlc): add SDLC + deterministic agentic-AI guidelines (#55) (#57) (4b8955f)
- fix(finops): scale the EDC stack off-hours — close ADR-024/023 cost gap (#54) (3e139e8)
- fix(auth): clear Keycloak SSO session on sign-out (#52) (#53) (7b8afba)
- docs(adr): ADR-026 — token-efficient planning & ADR structure (9973c6d)
- docs(planning): split 72K planning doc into slim index + phase archives (525e6e6)
- feat(neo4j+ui): add 9th tenant — Health Dataspace Operations (EDC_ADMIN slot) (363d5c0)
- feat(neo4j+ui): seed :ParticipantProfile + :VPA so Azure dashboard shows non-zero (230fb04)
- fix(ui): tenants page RBAC + Access Level + profile counts (#50) (018cbd3)
- fix(cp+vault): set EDC_IAM_OAUTH2_ISSUER + bootstrap participant keys (#49) (af5c0c5)
- feat(workflow): edc-probe-cp - check controlplane participant view (#48) (0ff1a24)
- fix(seed): use publicKeyJwk Ed25519 instead of keyGeneratorParams (#47) (0a7d694)
- fix(workflow): keypairs endpoint uses PUT not POST + makeDefault=true (#46) (75bc6f5)
- feat(workflow): edc-add-keypairs — backfill keypairs for seeded participants (#45) (d2db88c)
- feat(workflow): edc-probe-keypairs — verify TCK DCP-2.x readiness (#44) (fc6e93c)
- fix(workflow): vault-bootstrap-aes — use HTTPS via ACA internal ingress (#43) (7549c48)
- fix(workflow): vault-bootstrap-aes — use vault CLI image, longer timeout (#42) (923b8db)
- fix(ih): set AES key alias + Vault bootstrap workflow (issue #25) (#41) (2148c72)
- fix(seed): use keyGeneratorParams in KeyDescriptor (issue #25) (#40) (422507e)
- fix(seed): use participantContextId for IH v0.13.x (#25 Option C) (#39) (891ccee)
- fix(workflow): edc-schema-reset — also recreate controlplane + dataplane DBs (#38) (809d38c)
- fix(workflow): provision-aca-env-logging — wait for OperationalInsights registration (#37) (75fe0e8)
- feat(workflow): provision Log Analytics on the ACA env (Path 1 of #25) (#36) (38e3943)
- fix(workflow): edc-seed v4 — dump appLogsConfiguration + sub-wide LA + full job props (#35) (6408fde)
- fix(workflow): edc-seed v3 — pre-flight Keycloak check + bash -x trace + LA fallback (#34) (3b29016)
- fix(workflow): edc-seed — use azure-cli base image + LA log query (#33) (6d279c9)
- feat(azure): seed 5 EHDS ParticipantContexts on Azure (issue #25) (#32) (304a50a)
- docs(demos): refresh Spanish Ministry deck with the hand-enhanced version (b229af2)
- chore(deps): pin fast-uri ^3.1.2 to clear the HIGH CVE blocking PR merges (#31) (003126e)
- docs(adr): ADR-025 — record the three commits that complete the work (7d8d3c7)
- feat(auth): Phase 3 cleanup of issue #28 — bake auth.ehds.mabu.red into the deploy + docs (#30) (1fc68ed)
- Phase 1 + 2 of #28: keycloak-custom-domain (test + workflow) (#29) (f5dc973)
- fix(workflow): re-import Keycloak edcv realm during weekly demo reset (d724c27)
- chore(bruno): commit Azure-Dev sessionToken (demo env, weekly wipe) (3ae4165)
- docs(bruno): add 'What success looks like' screenshot and verification table (fc58379)
- fix(workflow): bruno-smoke — add checks:write so JUnit publish can land (c4921e5)
- fix(workflow): bruno-smoke — absolute report paths, continue-on-error, step summary (c190c58)
- docs(bruno): add gh CLI commands for secret + workflow dispatch (cd11b90)
- docs(bruno): document auth + add CI smoke automation (7046515)
- feat(scripts): forge NextAuth session cookie for Bruno API testing (72f4e2c)
- docs(demos): add 12-slide ministry briefing deck for Spanish health system (c9957c2)
- docs(adr): supersede ADR-022 with ADR-024, provision full EDC stack per participant on Azure (57207d7)
- feat(workflow): seed 5 EHDS participants after schema reset (issue #25) (fb7edc8)
- fix(edc): add EDC_SQL_SCHEMA_AUTOCREATE to IH+Issuer; add schema-reset workflow (8756122)
- fix(rbac): allow HDAB_AUTHORITY on /admin/policies and /admin/audit (d340100)
- docs(demos): drop em-dashes and parenthetical hyphens from HDAB guide (cfd56f8)
- docs(demos): drop time markers, add HDAB-tasks coverage matrix (2f36d89)
- docs(demos): add 30-min HDAB regulator demo guide (issue #27) (0db7dff)
- fix(ci): scope Azure E2E to a public-page smoke (a54902c)
- fix(layout): skip headers() in static-export builds (9e5423e)
- docs(adr): accept ADR-022 (Option C) — formalise EDC scope split for issue #25 (4d4cd62)
- fix(azure): multi-port controlplane + NATS JetStream — issue #25 partial (3ee18be)
- chore(azure): cost optimisation pass — connector ADR + postgres shrink + off-hours (5e6974e)
- feat(nlq): finish issue #19 — canonical pharmacovigilance scenario green (dd812e9)
- test: cover server-cache, ComponentActions, diagnosis & restart routes (0a6a417)
- ci(test.yml): unblock pipeline — WCAG ratchet + gitleaks allowlist (9cd5071)
- fix(proxy): source dataspace participants from IdentityHub, not Controlplane (6a70f89)
- fix(admin): move Restart/Why-broken from cost panel to topology cards (c39dd49)
- fix(tck): make skipped tests visually distinct from failed (ee946d5)
- feat(tck): visible loading spinner + skip-explainer banner (4bd2035)
- feat(admin): cache + ACA-shared metrics + computed storage/egress (5e33d85)
- fix(api): use Next 15 Promise signature on new admin routes (69c8e0e)
- feat(tck,admin): skip-when-unreachable + per-component restart/diagnosis (issue #25) (970d21b)
- docs(planning): add Phase 27 EUDI Wallet Sandbox + GesundheitsID OIDC RP (issues #22, #24) (094dc83)
- test(leitlinien): expand Layer 6 loader coverage (issue #20) (14e72b4)
- feat: update leitlinien ingestion and NLQ integration work (6f00f24)
- feat(nlq): graph-layer breadcrumb + cohort DQ + CODED_BY backfill (issue #19) (4d15af1)
- Rename leitlinien-ingestion package from scratch placeholder. (2d253e3)
- Phase 1 of issue #20 — AWMF Leitlinien ingestion pipeline (Docling). (d8fcd02)
- refactor(nlq): swap canonical demo to cipro+tendon rupture (issue #19) (4a0afed)
- feat(nlq): pharmacovigilance template + interpretation panel (issue #19) (09760a2)
- fix(admin/components): per-container cards respect metricsShared (c928d24)
- fix(azure): mvhd-nats needs --transport tcp — crawler+enricher NATS wiring (e71e573)
- chore(ci): diagnose workflow also inspects crawler + NATS (e4fda71)
- chore(ci): one-off enricher diagnose workflow (dispatch only) (377b9de)
- fix(seed): Phase 26 fast-path + dispatch workflow (2430539)
- feat(debug): /api/debug/phase26 — read-only loop diagnostic (2135e4c)
- fix(ci): catalog-enricher — ingress flag isn't valid for background worker (2c41bbf)
- feat(enricher): Phase 26c — deploy workflow + core NATS subscribe (7e7c4e1)
- chore(seed): wire Phase 26 glossary + participant-source-init into seed.sh (f3216df)
- feat(nlq): Phase 26 A+B — mock DSP catalog + federated NLQ templates (4a7c573)
- fix(ci): catalog-crawler — ACA Schedule-trigger Job needs --replica-retry-limit (b020d73)
- feat(nlq): Phase 26b — catalog-crawler service + ACA deploy (48bb09a)
- fix(ci): pages.yml — unblock static build of /opengraph-image (95d08dd)
- fix(admin): cost panel also accepts topology.metricsShared as Azure signal (f7da5c6)
- fix(ci): release.yml — use --notes-file so backticks in commit messages don't get interpreted as shell subcommands (440e2f1)
Full changelog: v1.5.1...v2.0.0