Brainversum · tuxametrics Graph Admin

konzept vertraulich owner: matus

Audit, Feedback und Process-Hub anschließen — was entschieden wurde und was es kostet

Audit, Feedback und Process-Hub anschließen — was entschieden wurde und was es kostet

Status: entschieden (2026-08-09, Nutzerentscheidung nach Diskussion) · Adressat: Product Owner

Bis hierher hat tuxametrics von Zwirn nur Bibliotheken gezogen: zwirn-security-java-core/-quarkus, zwirn-tracing-java-quarkus, zwirn-test, @zwirn/web. Alles davon überquert einen Classpath bzw. einen Bundler und kostet keinen zusätzlichen Betrieb.

Die drei hier angeschlossenen Bausteine sind der dritte Wiederverwendungsmechanismus aus Zwirn-ADR-0006: Docker-Services. Sie werden nicht importiert, sondern betrieben — jeder mit eigener Datenbank, eigenem Container, erreichbar nur über das Netz.

Die Leitregel dieses Repos und warum sie hier nicht greift

„Korrigiert wird, was v1.2 widerlegt. Nicht gebaut wird, was v1.2 neu ermöglicht."

Diese Regel schützt den fachlichen Schnitt (Gold Path E1/E3) vor stiller Ausweitung. Sie ist hier nicht berührt: es entsteht keine neue Fachregel, keine neue Zahl, keine neue Ebene, kein neues Enum im Domänenmodell. Was entsteht, ist Infrastruktur um denselben Ablauf herum.

Trotzdem ist es eine Scope-Erweiterung — nur eben eine betriebliche: aus einem Stack mit 3 Containern werden 10. Deshalb dieses Dokument vor dem ersten Compose-Block.


Was angeschlossen wurde

Baustein Art Herkunft Was tuxametrics davon hat
zwirn-audit-service fertiges Docker-Image ../../../zwirn/audit/audit-service Revisionsfestes Protokoll der drei fachlich bedeutsamen Übergänge (siehe unten) + Live-Ansicht im UI
zwirn-feedback-service betrieben seit 2026-08-14 abgelöst durch zwirn-autoimprove-service, siehe Nachtrag unten fertiges Docker-Image ../../../zwirn/feedback/feedback-service „Bug/Idee zu diesem Screen melden" mit Screenshot — der Rückkanal aus einer Pilotpraxis
zwirn-process-hub-flowable-spring seit 2026-08-15 abgelöst durch zwirn-process-api + zwirn-process-flowable + zwirn-process-hub-router-spring Maven-Artefakte, kein Image ../../../zwirn/process-hub Bis dahin: Flowable-REST-Fassade /process-api/**. Jetzt: der engine-agnostische Prozess-Vertrag plus der Aggregator /api/process-hub/v1/tenants/{t}/..., gegen den @zwirn/bpmn spricht — rein lesend und capability-geprüft (process-hub-wiederanschluss.md)
@zwirn/audit, @zwirn/feedback (seit 2026-08-14 @zwirn/autoimprove), @zwirn/bpmn npm-file:-Pakete audit-web, feedback-web autoimprove-web, process-hub-bpmn-web Die zugehörigen Oberflächen, als TypeScript-Quelle über den Bundler

Nicht angeschlossen: zwirn-autoimprove-service/@zwirn/autoimprove. Autoimprove ist eine claim-basierte Agenten-Pipeline über Feedback-Karten — eine zweite Bearbeitungsstufe für einen Rückkanal, den es hier noch nicht einmal gibt. Erst Feedback sammeln, dann entscheiden, ob es eine Pipeline braucht.

Nachtrag 2026-08-14 — die Frage ist beantwortet, und zwar mit ja.

Der Absatz oben („erst Feedback sammeln, dann entscheiden, ob es eine Pipeline braucht") ist abgearbeitet: tuxametrics betreibt seit heute zwirn-autoimprove-service, und nur ihn (ADR-0007, Port 8123, eigene DB auf 5522). zwirn-feedback-service ist abgeschaltet.

Zwischenschritt, der einen Tag gehalten hat: am 2026-08-13 entstand hier ein Eigenbau-Modul tuxametrics-feedback-process (ADR-0006, BPMN-Triage mit zwei UserTasks, Port 8128). Es ist am 2026-08-14 ersatzlos entfallen. Der Grund ist ein Recherchefehler und steht so in ADR-0007: die damalige Prüfung stützte sich auf DOTs konzepte/agentic-orchestration.md — ein im Quellrepo selbst als „nicht gebaut, zurückgestellt" markiertes Visionspapier — und schloss daraus, „Autoimprove" sei unerprobte Vision. Der kanonische zwirn-autoimprove-service existierte zu dem Zeitpunkt längst, gebaut und capability-gegatet; er wurde bei der Recherche nur nicht gefunden.

Was das für dieses Dokument bedeutet: die Entscheidung von 2026-08-09 war richtig — „erst sammeln, dann entscheiden" — und sie ist jetzt getroffen, nicht übergangen. Was sich als falsch erwiesen hat, ist nicht der Aufschub, sondern die Annahme, eine zweite Stufe müsse selbst gebaut werden.

Kein Widerspruch zur Nicht-Automatik: es entsteht ein AGENT-Actor, aber kein Betreiber. Kein Scheduler, kein Bot, kein LLM-Aufruf. Die Karten bleiben sichtbar stehen, bis ein Mensch oder ein später zu entscheidender Betreiber sie claimt.

Kein Alleingang: DOT (C:\dev\dl-controlling\dot) hat denselben Schnitt am 2026-08-03 in ADR-0071 entschieden — eigenes Feedback-Modul samt Prozessmodul gelöscht, zwirn-autoimprove-service als Docker-Service mit eigener DB übernommen, dedizierter Keycloak-Client, Melde-Dialog lokal nachgebaut. tuxametrics ist der zweite Fall desselben Musters, nicht der erste.

Zwei Nebenwirkungen, die dieses Dokument betreffen: der Melde-Button hängt weiterhin auf jeder Seite, kommt aber jetzt aus tuxametrics-ui/src/domains/autoimprove/ statt aus einem Zwirn-Paket (@zwirn/autoimprove exportiert keinen) — ohne automatischen Screenshot, dafür mit Einfügen/Hochladen, weil html2canvas mit @zwirn/feedback entfallen ist. Und die Authority hat jetzt GET /me/capabilities: ohne diesen Endpunkt liefert useCapabilities aus @zwirn/web eine leere Rechtemenge, und jede handelnde Schaltfläche der Paket-Seiten bliebe für einen vollständig berechtigten Menschen stumm deaktiviert.


Die vier Entscheidungen, die dieses Dokument trifft

Die Zwirn-READMEs lassen bewusst offen, was pro Produkt zu entscheiden ist. Hier steht, wie entschieden wurde — damit es nicht als Kommentarzeile in einer YAML-Datei versickert.

1. Audit-Events gehen über Redis, nicht über REST

zwirn-audit-service hat keinen schreibenden REST-Endpunkt. AuditEventResource bietet ausschließlich GET .../events/search, GET .../events/stream (SSE) und GET .../security-events/search. Der einzige Ingest-Weg ist ein XADD auf den Redis-Stream events mit dem Feld data = JSON nach dev.zwirn.audit.redis.DomainEventDto (RedisStreamConsumer, Consumer-Gruppe audit).

Ein „REST-Client analog PlatformCapabilityCheckClient" wäre also ein Client auf einen Endpunkt, den es nicht gibt. Umgesetzt ist deshalb ein Stream-Publisher (audit/AuditEreignisPublisher) — mit demselben Grundsatz wie beim Capability-Client: der Baustein liegt produktseitig, der Vertrag ist der Envelope, nicht eine Java-Klasse aus dem Service.

Konsequenz: der Stack braucht ein eigenes Redis (tuxametrics-audit-redis). Kein Pooling über Produkte — das verlangt ADR-0006 ausdrücklich, weil tenantKey nur innerhalb eines Produkts eindeutig ist.

2. Der Prozess-Hub braucht einen eigenen Spring-Wrapper — die dokumentierte Quarkus-Ausnahme

zwirn-process-hub-flowable-spring ist ein Dependency-Bündel, kein Deployment. Ohne einen @SpringBootApplication-Wrapper gibt es nichts zu starten. Deshalb entsteht das Modul tuxametrics-process-hub-cockpitdie einzige Nicht-Quarkus-Komponente dieses Repos.

Das ist kein stilles Abweichen von Zwirn-ADR-0007, sondern die dort namentlich genannte Ausnahme: Flowables REST-/Engine-Integration liegt in diesem Org-Kontext nur Spring-nativ vor. Vorbild ist 1:1 samples/sample-process-cockpit/process-hub-cockpit.

Der Wrapper enthält eine Java-Klasse und keinen Fachcode. Entstünde dort Fachlichkeit, gehörte sie in den Laborauswertungs-Service.

3. Der Parent des Wrappers wird aus dem Repository aufgelöst, nicht per relativePath

Das Sample nutzt <relativePath>../../../pom.xml</relativePath> auf zwirn-platform — das geht dort, weil es im Zwirn-Repo liegt. Hier läge der Pfad außerhalb des Repos (../../zwirn/pom.xml) und wäre im Docker-Build-Context nicht vorhanden.

Entschieden: <relativePath/> (leer) — dev.zwirn:zwirn-platform:0.1.0-SNAPSHOT kommt aus dem lokalen .m2. Das ist keine neue Voraussetzung: mvn -f ../zwirn/pom.xml install steht bereits als Schritt 1 in docker-compose.yml und in CLAUDE.md. Nutzen: Spring-Boot-Linie und java.version kommen aus derselben Quelle wie beim Starter selbst — kein Versions-Drift zwischen Starter und Konsument.

4. Auth: die beiden neuen Services erben die offene Authority-Frage — sie beantworten sie nicht

zwirn-feedback-service will beim Start einen Service-Account-Token holen (CapabilityRegistrar, quarkus.rest-client.keycloak.url + zwirn.feedback.service-client-*), zwirn-audit-service will ein JWKS und einen platform-service.

tuxametrics hat keinen entschiedenen IdP und keine entschiedene Authority (capability-authority.md, ADR-0002/0003-Nachbarschaft). Ein Keycloak- Container hier wäre eine stille Vorentscheidung.

Entschieden: die neuen Services zeigen auf dieselben Platzhalter wie der Laborauswertungs-Service (KEYCLOAK_URL/KEYCLOAK_REALM/TXM_CAPABILITY_AUTHORITY_URL aus .env). Damit gilt für sie exakt das, was für den Hauptservice schon gilt: sie starten, aber sie sind ohne gültiges Token nicht bedienbar. Die Capability-Registrierung des Feedback-Service schlägt fehl und wird geloggt; der Service läuft trotzdem an.

Das ist ausdrücklich kein Betriebszustand, sondern der sichtbare Preis einer offenen Frage. Ein sample-capability-stub (nginx, antwortet auf alles allowed) wurde bewusst nicht übernommen: das Zwirn-Sample nennt ihn selbst „darf einen Sample-Stack niemals verlassen". In einem Repo mit Gesundheitsdaten-Anspruch ist ein Allow-All-Autorisierungsbackend die falsche Bequemlichkeit — auch im Prototyp.


Wo Audit-Events entstehen — und wo bewusst nicht

Nicht überall. Drei Übergänge, und zwar genau die, an denen im Fachmaterial eine Verantwortung wechselt:

Ereignis Auslöser objectType / eventType Warum genau hier
Empfehlungen erzeugt AuswertungslaufService.auswerten Auswertung / empfehlungen-erzeugt Der Moment, in dem das System etwas vorschlägt (GM-7, Systemseite)
Informierte Übersteuerung TherapieplanService.uebersteuere Empfehlung / uebersteuert GM-7.2 — der Therapeut weicht bewusst von einer benannten Regel ab. Eine der vier „unbequemen" Zustände aus ADR-0004
Freigabe erteilt TherapieplanService.gibFrei Auswertung / freigegeben GM-7 — hier und nur hier wird aus „vorgeschlagen" final, durch eine Person mit benannter Qualifikation

Nicht protokolliert werden: jeder Lesezugriff, jede einzelne Therapeutenentscheidung, jede Messwertkorrektur, jeder Gesamtplan-Check. Ein Audit-Log, das bei jedem Klick auslöst, wird genauso weggeblättert wie eine Blockade, die immer auslöst — dasselbe Argument, das FACH-079 Z. 1753 für Freigabeblockaden macht („eine Blockade, die häufig ohne erkennbaren Nutzen auslöst, wird zur Gewohnheit").

Zwei Eigenschaften, die nicht verhandelbar sind

a) GM-7: kein Event blockiert irgendetwas. Der Publisher läuft als CDI-Observer in TransactionPhase.AFTER_SUCCESS — er sieht die Transaktion also nach dem Commit und kann sie nicht mehr scheitern lassen. Zusätzlich fängt er jede Exception ab und loggt sie auf WARN. Ist Redis weg, ist der Audit-Eintrag weg — die Freigabe nicht. Genau diese Reihenfolge ist Absicht: ein Protokollierungsproblem darf eine Therapeutenentscheidung nicht verhindern.

b) Keine Gesundheitsdaten im Payload. Der Audit-Service hat eine eigene Datenbank. Was dort landet, hat die Datenhoheitsgrenze des Laborauswertungs-Service verlassen. Deshalb enthalten die Payloads ausschließlich Schlüssel und Zählungen (auswertungKey, empfehlungKey, Anzahl Empfehlungen, Qualifikation der freigebenden Person, Name der berührten Regel) — keine Messwerte, keine Analyt-Werte, keinen Patientenschlüssel, keine Freitext-Begründung. Wer das erweitert, verschiebt personenbezogene Gesundheitsdaten in eine zweite Datenbank und braucht dafür eine eigene Entscheidung.

Der Schalter dafür heißt tuxametrics.audit.aktiv und steht per Default auf false — aktiv nur im %docker-Profil, wo das Redis auch existiert. Test und quarkus:dev brauchen dadurch kein Redis.


Was das Prozess-Cockpit hier zeigt — und was nicht

Ehrlich zuerst: die Engine ist leer. tuxametrics modelliert den Gold Path nicht als BPMN-Prozess. Es gibt kein Prozessmodul, keine .bpmn-Datei, keine deployte Definition. Der Cockpit-Container startet mit einer leeren Flowable-Engine (check-process-definitions: false, async-executor-activate: false — er liest, er deployt nicht).

Stand 2026-08-13: der Absatz oben stimmt so nicht mehr, und zwar aus zwei unabhängigen Gründen. Er bleibt stehen, weil er den Stand vom 2026-08-09 beschreibt; hier steht, was sich geändert hat.

  1. 2026-08-12 liegt eine .bpmn im Cockpit (processes/tuxametrics-auswertung.bpmn20.xml), check-process-definitions steht auf true. Es ist ein Dokumentationsmodell des Gold Path — isExecutable="true" nur, weil Flowables Deployer nichts anderes annimmt; kein Code dieses Repos startet dafür je eine Instanz. Die Aussage „tuxametrics modelliert den Gold Path nicht als BPMN-Prozess" bleibt damit richtig: modelliert ja, ausgeführt nein.
  2. 2026-08-13 gab es einen Tag lang ein zweites, echtes Prozessmodul (tuxametrics-feedback-process, ADR-0006) mit eigener Engine, eigener DB und laufenden Instanzen. Es ist am 2026-08-14 ersatzlos entfallen (ADR-0007). Damit gilt für den Rest des Absatzes oben wieder, was er sagt: es gibt in diesem Produkt genau eine Flowable-Engine, die des Cockpits, mit einer Definition und keiner Instanz.

Unverändert offen ist die eigentliche Frage: ob der klinische Ablauf Patient → Befund → Freigabe als Prozess ausgeführt werden soll. Sie war auch am 2026-08-13 nicht berührt — die Engine trieb damals eine Bug-Meldung, keinen Patientenfall.

Wozu dann? Weil der Anschluss jetzt billig und später teuer ist, und weil das Cockpit den Ereignis-Reiter des Audit-Service mitbringt. Was heute funktioniert:

Seite Backend Zustand
ProcessCockpitPage (Definitionen/Instanzen/Ereignisse) flowableClient + auditClient funktioniert — Prozesslisten leer, Ereignis-Reiter gefüllt
ProcessDefinitionDetailPage flowableClient funktioniert, sobald eine Definition deployt ist
ProcessInstanceDetailPage flowableClient funktioniert, sobald es eine Instanz gibt
ProcessInstancesPage, ProcessTasksPage, TaskDetailPage processClient/api/process-hub/v1/tenants nicht angeschlossen

Die letzten drei brauchen einen Process-Hub-Aggregator, den weder Zwirn noch dieses Repo hat (@zwirn/bpmns README nennt ihn als Erwartung an den Konsumenten). Entschieden: sie werden nicht gemountet. Eine Route, die garantiert 404 liefert, ist schlechter als eine Route, die es nicht gibt — dieselbe Haltung wie bei fehlenden Fachzahlen. Deshalb wird createProcessHubRoutes nicht benutzt, sondern die drei funktionierenden Seiten werden einzeln verdrahtet (das README des Pakets sieht genau das vor). Aus demselben Grund wird processHubSidebar nicht importiert: es enthält „Prozesse" und „Aufgaben".

Offen gelassen, nicht erfunden: ProcessModuleInfo[] bleibt leer. Ein Eintrag dort behauptete ein Prozessmodul mit Owner, Engine und Key-Präfix — für tuxametrics gäbe es dafür keine Fundstelle.


Der Preis, ungeschönt

vorher nachher
Container im Stack 3 (db, opa, service) 10
Postgres-Instanzen 1 4 (je Service eine eigene — ADR-0006, kein Pooling)
Host-Ports 8120, 8181, 5520 + 8122–8124, 5521–5523, 6380
Maven-Module 1 (Quarkus) 2 (Quarkus + Spring)
Voraussetzung vor docker compose up 2 Schritte 3 — zwei Zwirn-Images müssen einmalig gebaut werden
npm-Dependencies @zwirn/web + @zwirn/audit, @zwirn/feedback, @zwirn/bpmn, html2canvas, @xyflow/react

Der Reactor-Build wird länger (Spring Boot 4.1 + Flowable 8 zusätzlich zur Quarkus-Linie). Der tsc --noEmit-Lauf prüft jetzt auch die drei file:-Pakete mit.


Was danach noch offen ist

Punkt Warum offen
IdP + Authority Unverändert die eine blockierende interne Entscheidung — sie blockiert jetzt vier Services statt einem. capability-authority.md
Process-Hub-Aggregator (/api/process-hub/v1/tenants) Existiert nirgends. Ohne ihn keine Aufgaben-/Instanzlisten. Bauen hieße einen neuen Service erfinden
BPMN-Modell des Gold Path Ob der Ablauf Patient → Befund → Freigabe überhaupt als Prozessmodell laufen soll, ist nie entschieden worden. Das Cockpit stellt die Frage, es beantwortet sie nicht
Retention der Audit-Daten zwirn-audit-service schreibt append-only ohne Löschfrist. Bei Gesundheitsdaten-Bezug ist das eine eigene Frage — auch wenn die Payloads hier schlüssel-only sind
Webhooks zwirn-audit-service kann HMAC-signierte Webhooks zustellen. Ungenutzt, kein Ziel registriert
quarkus.hibernate-orm.database.generation Der Audit-Service-Default ist validate und setzt ein extern verwaltetes Schema voraus. Hier steht update — wie im Zwirn-Sample, aus demselben Grund: dieser Stack bringt keine Migration mit. Für einen echten Betrieb ist das zu ändern

Unangetastet geblieben: TUXAMETRICS_REGULATORIK_EINSTUFUNG und TUXAMETRICS_PILOT_BETRIEBSMODUS stehen weiterhin auf UNGEKLAERT; RegulatorikGuard ist nicht berührt. Keine neue Fachzahl, kein neuer Enum-Wert im Domänenmodell, E2 kommt weiterhin nirgends vor.