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
- Betrieb · Art: Scope-Erweiterung, keine Korrektur
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 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-springzwirn-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@zwirn/autoimprove), @zwirn/bpmn |
npm-file:-Pakete |
audit-web, feedback-webautoimprove-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-serviceist 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 DOTskonzepte/agentic-orchestration.md— ein im Quellrepo selbst als „nicht gebaut, zurückgestellt" markiertes Visionspapier — und schloss daraus, „Autoimprove" sei unerprobte Vision. Der kanonischezwirn-autoimprove-serviceexistierte 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-serviceals 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/autoimproveexportiert keinen) — ohne automatischen Screenshot, dafür mit Einfügen/Hochladen, weilhtml2canvasmit@zwirn/feedbackentfallen ist. Und die Authority hat jetztGET /me/capabilities: ohne diesen Endpunkt liefertuseCapabilitiesaus@zwirn/webeine 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-cockpit — die 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 allesallowed) 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.
- 2026-08-12 liegt eine
.bpmnim Cockpit (processes/tuxametrics-auswertung.bpmn20.xml),check-process-definitionssteht auftrue. 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.- 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.