Brainversum · tuxametrics Graph Admin

adr vertraulich owner: matus

ADR-0006: Feedback-Triage als eigenes Prozessmodul — und ausdrücklich *nicht* „Autoimprove"

ADR-0006: Feedback-Triage als eigenes Prozessmodul — und ausdrücklich nicht „Autoimprove"

Status

Abgelöst durch ADR-0007 (2026-08-14). Angenommen am 2026-08-13, abgelöst am Tag darauf. Das hier beschriebene Modul tuxametrics-feedback-process ist ersatzlos entfernt; zwirn-feedback-service wird in tuxametrics nicht mehr betrieben.

Der Inhalt unten bleibt unverändert stehen — er ist ab „Kontext" nur noch als Beleg lesbar, nicht mehr als Beschreibung des Systems. Der Grund für die Ablösung steht vollständig in ADR-0007 und ist unangenehm: Abschnitt 6 („Was hier bewusst nicht entsteht") begründet den Verzicht auf „Autoimprove" mit DOTs konzepte/agentic-orchestration.md — einem im Quellrepo selbst als „nicht gebaut, zurückgestellt" markierten Visionspapier. Das war die falsche Quelle. Der kanonische zwirn-autoimprove-service existiert in Zwirn seit Längerem, ist gebaut, getestet und capability-gegatet; er wurde bei der Recherche schlicht nicht gefunden. Die Tabelle in Abschnitt 6 ist damit in ihrer Kernaussage falsch: „Autoimprove" war keine unerprobte Vision, sondern ein fertiger Service, den anzuschließen weniger Code gekostet hätte als das hier beschriebene Modul.

Was richtig bleibt und in ADR-0007 fortgeführt wird: die Trennung von Vorgang und Sammelstelle (Kontext), die Tenant-Pfad-Regel für zwirn.security.tenant-path-pattern (§2), und dass keine der fünf Leitplanken aus CLAUDE.md berührt ist (§6, letzter Absatz).

Kontext

Seit dem 2026-08-09 betreibt tuxametrics zwirn-feedback-service als fertiges Docker-Image (Zwirn-ADR-0006): das In-App-Widget „Bug/Idee zu diesem Screen melden" aus @zwirn/feedback, in tuxametrics-ui/src/shell/TopNav.tsx auf jeder Seite eingebunden. Der Service kennt einen Status-Workflow OPEN → IN_PROGRESS → RESOLVED — aber niemand hat ihn getrieben. Jede Meldung blieb auf OPEN stehen, für immer, ohne Aufgabe und ohne Zuständigen. Der Rückkanal aus einer Pilotpraxis war damit eine Sammelstelle, kein Vorgang.

Dieselbe Datei, die das entschieden hat, hat auch benannt, was fehlt: audit-feedback-processhub-integration.md Z. 37–40 — „Nicht angeschlossen: zwirn-autoimprove-service. […] Erst Feedback sammeln, dann entscheiden, ob es eine Pipeline braucht."

Zwei Dinge sind seither dazugekommen, und beide sind der Anlass für dieses ADR:

  1. Es gibt ein belegtes Vorbild für die zweite Stufe, und es ist keine Agenten-Pipeline. DOT-ADR-0030 schneidet für dieselbe Domäne einen Data Service und ein Prozessmodul: Start → UserTask „Feedback sichten" → ServiceTask setzt IN_PROGRESS → UserTask „Feedback bearbeiten" → ServiceTask setzt RESOLVED → Ende, gestartet vom Frontend nach dem erfolgreichen POST, abgesichert durch einen @Scheduled-Nachzug-Job. Gebaut, in Betrieb, nicht Vision.
  2. Die Rahmenregel für neue Prozessmodule wurde einen Tag vor dieser Entscheidung geändert. GG-ARCH-PROCESS-0001 (../../../zwirn/guidelines/architecture/process/microprocess-architecture.md, Abschnitt „Pflicht beim Anlegen eines neuen Process-Moduls") verlangt seit DOT-ADR-0081 (2026-08-12) von jedem neuen Modul zweierlei: die engine-agnostische Prozess-API (ProcessObservationPort aus dev.zwirn:zwirn-process-api) und einen Eintrag in der Endpunkt-Registry der Plattform. Wer jetzt baut, baut danach — oder er baut das nach.

Der Auslöser dieser Arbeit war die Bitte, „Autoimprove mit den aktuellsten Ideen aus DOT" umzusetzen. Die Prüfung dieser Bitte ist Teil der Entscheidung und steht deshalb unten unter „Was hier bewusst nicht entsteht".

Entscheidung

Ein neues, fünftes Maven-Modul: tuxametrics-feedback-process. Spring Boot, eingebettete Flowable-8-Engine, eigene Postgres-Engine-DB, Port 8128.

1. Der Prozess

src/main/resources/processes/feedback-report.bpmn, Prozess-ID io.dl.tuxametrics.feedback.v1.public.feedback-report (GG-ARCH-PROCESS-0002.1), Name „Feedback-Meldung bearbeiten":

● Feedback gemeldet ─▶ (Feedback sichten) ─▶ [Status → IN_PROGRESS]
                     ─▶ (Feedback bearbeiten) ─▶ [Status → RESOLVED] ─▶ ● Meldung erledigt

( ) = userTask, [ ] = serviceTask. Die beiden Entscheidungsschritte sind Aufgaben für Menschen; die beiden Service-Schritte tun genau eine Sache — den Status im Data Service nachziehen, per flowable:expression auf eine plain Spring-Bean (Zwirn-ADR-0033, kein JavaDelegate).

Ohne flowable:candidateGroups. Wer in einer Praxis eine Meldung sichtet, ist eine offene fachliche Rollenfrage (OF-12). GG-ARCH-PROCESS-0003 führt Candidate Groups ausdrücklich als „was das explizit NICHT abdeckt" — alle Aufgaben sind für alle authentifizierten Nutzer eines Mandanten sichtbar. Eine erfundene Gruppe wäre eine Rollenaussage ohne Fundstelle.

2. Prozessstart: der UI-Trigger existiert bereits — es fehlte nur der Empfänger

@zwirn/feedback ruft in FeedbackReportModal.tsx nach dem erfolgreichen POST zusätzlich POST /api/fbp/{tenantKey}/processes/feedback-report/instancesbest effort, jeder Fehler wird verschluckt (startFeedbackProcess(...).catch(() => {})). In tuxametrics lief dieser Aufruf bis heute ins Leere: es gab nichts dahinter und keine Proxy-Route dorthin.

Entschieden ist deshalb kein neuer Trigger, sondern das Anschließen des vorhandenen: eine location-Regel in tuxametrics-ui/nginx-locations.conf (und die gleichlautende im Vite-Dev-Proxy). /api/fbp ist die Pfadkonvention des npm-Pakets; das Modul serviert unter /api/v1/tenants/{tenantKey}/…, weil nur diese Form zwirn.security.tenant-path-pattern (/tenants/([^/]+)) trifft. Umgeschrieben wird am Rand, nicht im Backend — dieselbe Regel, die für /flowable-api schon gilt.

Das ist kein Stilthema. Ein Pfad ohne /tenants/-Segment lässt Zwirns UserContextFilter still ins Leere laufen (die Mismatch-Prüfung beginnt mit pathTenantKey != null) — exakt die Lücke, die dieses Repo für Flowables rohe /process-api/** bereits dokumentiert hat und wegen der der Cockpit-Container auf Loopback gebunden wurde. Ein neues Modul reißt sie nicht ein zweites Mal auf.

Ein Data Service ruft nie ein Prozessmodul (GG-ARCH-PROCESS-0001). Hier ist das nicht einmal theoretisch möglich: der Feedback-Service ist ein fertiges Fremd-Image, das dieses Produkt nicht kennt.

3. Nachzug statt Hoffnung: FeedbackProcessReconciliationScheduler

Alle zwei Minuten, je Mandant: offene Reports holen, für jeden ohne Prozessinstanz eine starten. Das ist die Gegenmaßnahme zum verschluckten Fehler oben — ohne sie wäre eine verlorene Meldung unsichtbar verloren. Die Leserichtung Prozessmodul → Data Service ist die erlaubte.

Der Start ist idempotent auf dem businessKey (FeedbackProcessStarter.startIfAbsent), geprüft gegen die historische Instanzabfrage (laufende und beendete). UI-Trigger und Nachzug können sich sonst überschneiden — ein Report bleibt OPEN, bis der erste UserTask abgeschlossen ist, also im Regelfall tagelang. DOT hat diese Prüfung nur im Scheduler; hier liegt sie an einer Stelle, die beide benutzen.

4. Die zwei Pflichten aus GG-ARCH-PROCESS-0001 — eine erfüllt, eine ehrlich offen

Pflicht Stand
ProcessObservationPort implementieren (Dependency zwirn-process-flowable, OwnedProcessDefinitions-Bean, die drei zwirn.process.*-Properties) Erfüllt. config/OwnedFeedbackProcessDefinitions, zwirn.process.module-key=feedback-process. ProcessDeploymentSmokeTest prüft, dass der Port tatsächlich entsteht — und dass die deklarierte Menge der deployten entspricht (der Abgleich, den das Javadoc von OwnedProcessDefinitions verlangt)
Eintrag in der Endpunkt-Registry der Plattform (hub.process-services: Routing-Key → Basis-URL) Nicht möglich — es gibt keine. Der aggregierende Hub ist ein DOT-Modul (dot-process-hub); tuxametrics betreibt mit tuxametrics-process-hub-cockpit Flowables rohe REST-Fassade, keinen Router mit Registry. service-landscape.md führt „Process-Hub-Aggregator: existiert nirgends" seit 2026-08-09 als offenen Punkt

Der Routing-Schlüssel steht trotzdem gesetzt in application.yaml. Er ist die Zusage, unter der das Modul gefunden wird, sobald es einen Router gibt — nicht eine Behauptung, dass es ihn gibt.

Praktische Folge, ungeschönt: es gibt in diesem Produkt heute keine Oberfläche, in der jemand die Aufgaben sieht. Die drei @zwirn/bpmn-Seiten, die das könnten (ProcessInstancesPage, ProcessTasksPage, TaskDetailPage), sind bewusst nicht gemountet — sie brauchen genau diesen Aggregator.

Nicht zu verwechseln mit „unbedienbar" — dieser Fehler wäre beim ersten Entwurf beinahe passiert. Die generische Prozess-API aus zwirn-process-api ist ausdrücklich rein lesend; das Cockpit auf 8124 hat eine eigene Engine und eigene DB und sieht diese Aufgaben nicht; und Flowables rohe REST-API bringt dieses Modul bewusst nicht mit. Ohne einen eigenen Abschluss-Weg wäre der Prozess gestartet, aber von niemandem weiterzutreiben gewesen — die Meldung bliebe trotz laufender Instanz auf OPEN, und das ganze Modul wäre Dekoration.

4a. Deshalb Teil dieser Entscheidung: der Aufgaben-Lebenszyklus

controller/ProcessTaskController + service/ProcessTaskService implementieren die Task-API, die GG-ARCH-PROCESS-0003 vom besitzenden Prozessmodul erwartet:

GET  /api/v1/tenants/{tenantKey}/process-tasks
POST /api/v1/tenants/{tenantKey}/process-tasks/{taskId}/claim
POST /api/v1/tenants/{tenantKey}/process-tasks/{taskId}/unclaim
POST /api/v1/tenants/{tenantKey}/process-tasks/{taskId}/_complete

Drei Zusagen, jede einzeln getestet: der assignee kommt immer aus dem Token und nie aus einem Body (das ausdrückliche „Falsch"-Beispiel der Guideline); jede Abfrage ist doppelt eingeschränkt (nativer taskTenantId und OwnedProcessDefinitions); und die drei Zustände unclaimed / claimed von mir / claimed von anderem verhalten sich wie in der Guideline (409 ohne Claim, 403 bei fremder Zuweisung, 404 statt 403 für eine fremde Praxis — ob dort eine Aufgabe existiert, geht den Aufrufer nichts an).

In DOT übernimmt diese Vermittlung dot-process-hub, weil nur er den End-User-JWT hält. Den Hub gibt es hier nicht — dafür liegt der echte JWT ohnehin direkt an diesem Modul an, weil das Frontend über denselben nginx spricht. Dieselbe Fläche ist zugleich genau das, was ein künftiger Hub erwartet: „eigene generische Task-API implementieren + Eintrag in hub.process-services", mehr verlangt die Guideline nicht.

Bedienbar ist die Triage damit heute per REST-Client, nicht per Klick. Das ist ein benannter Zustand, kein Versehen — und ein anderer als „läuft, aber steht für immer".

5. Sicherheit: eigener Service-Account, kein txm.*

6. Was hier bewusst nicht entsteht — und warum der Name „Autoimprove" nicht fällt

Der Auftrag lautete, „Autoimprove" umzusetzen. Das Ergebnis ist deutlich kleiner, und das ist eine Entscheidung, keine Auslassung:

Gefordert/erwartet Gebaut Warum nicht mehr
zwirn-autoimprove-service anschließen Nein, unverändert nicht angeschlossen Die Entscheidung von 2026-08-09 („erst Feedback sammeln") ist damit nicht aufgehoben, sondern erst jetzt begehbar: bis heute gab es keinen Vorgang, den eine zweite Stufe hätte verarbeiten können. Der Service bleibt außen vor
Agenten-Pipeline über Feedback-Karten (DOT konzepte/agentic-orchestration.md) Nein Im Quellrepo selbst als „Nicht gebaut … zurückgestellt" markiert, in einem Ordner, der als „Frühe Architektur-Vision, nicht das tatsächlich Gebaute" überschrieben ist. Eine unerprobte Vision in ein Produkt mit Gesundheitsdaten-Anspruch zu übernehmen, wäre genau die Art von Übernahme, die dieses Repo an anderer Stelle verweigert
LLM-gestützte Bewertung/Priorisierung von Meldungen Nein Kein LLM, kein Agent, keine automatische Bewertung. Die zwei Entscheidungsschritte sind UserTasks

Dieses Modul heißt deshalb nicht „autoimprove" und darf auch nicht so genannt werden. Es ist eine Triage: zwei menschliche Schritte und zwei Statuszeilen. Wer es „Autoimprove" nennt, verspricht eine Pipeline, die es nicht gibt — dieselbe Sorte Etikett wie „das Cockpit zeigt Prozesse", solange die Engine leer ist.

Keine der fünf Leitplanken aus CLAUDE.md ist berührt. Geprüft, einzeln: GM-7 (kein klinischer Entscheidungspfad, keine der drei geschützten Capabilities, Gm7Schutz unverändert), die vier unbequemen Elemente (kein Fachdatenmodell angefasst), E2 (kommt nicht vor), Medikamenten-Interaktionen (nicht berührt), die zwei externen Fragen (UNGEKLAERT, RegulatorikGuard unverändert). Es entsteht keine neue Fachzahl und kein neuer Enum-Wert der Domäne. Die Leitregel „Korrigiert wird, was v1.2 widerlegt" ist nicht berührt: das hier ist Infrastruktur um den Ablauf herum, wie schon Audit/Feedback/Cockpit — eine betriebliche Scope-Erweiterung.

Konsequenzen

Was besser wird

Was es kostet

vorher nachher
Maven-Module 4 5
Container im Stack 15 17 (Modul + eigene Postgres)
Postgres-Instanzen 6 7 — eigene Engine-DB ist Pflicht (GG-ARCH-PROCESS-0001)
Host-Ports … 8127, 5527 + 8128 (nur Loopback), 5528
Keycloak-Clients 2 3
Spring-Boot-Module (Ausnahme von Zwirn-ADR-0007) 1 2 — dieselbe benannte Ausnahme, nicht eine zweite Begründung

Was offen bleibt

Punkt Warum
Keine Aufgaben-Oberfläche Braucht den Process-Hub-Aggregator, den es weder in Zwirn noch hier gibt. Die Task-API existiert (§4a) — nur kein Bildschirm davor. Bedienbar per REST-Client, nicht per Klick
Kein Registry-Eintrag (hub.process-services) Es gibt keine Registry. Pflicht 2 aus GG-ARCH-PROCESS-0001 ist damit in diesem Produkt nicht erfüllbar, nicht „übersprungen"
Statische Mandantenliste im Nachzug-Job Eine neue Praxis braucht einen Eintrag in TXM_FEEDBACK_PROCESS_TENANT_KEYS, sonst läuft für sie nur der UI-Trigger — ohne Absicherung. Bewusst kein Rundruf bei der Authority im Minutentakt für einen Nachhol-Job
Der set-status-Grant hängt am Demo-Seed Er wird von boot/CapabilityRegistrar.seedDemoGrants() gelegt, also nur bei TXM_DEMO_GRANTS=true und nur für den Demo-Mandanten — dieselbe Konstruktion wie beim SERVICE-Grant des Testmanagement-Service. Für eine echte Praxis ist er von Hand zu vergeben. Fehlt er, bleibt der Report auf OPEN und der ServiceTask scheitert sichtbar; nichts geht still verloren
flowable.database-schema-update: true Die Engine legt ihr Schema selbst an. Für einen echten Betrieb durch eine verwaltete Migration ersetzen — derselbe offene Punkt wie beim Audit-Service
Kein nativer Bau Bewusst JVM, benannte Ausnahme von GG-DEVOPS-0003 — dieselbe Begründung wie beim Cockpit (Flowable/Spring, Reflection)
Kein Prozess-Steckbrief ({prozessname}-metadata.yaml, GG-ARCH-PROCESS-0004) Kein Big-Bang-Rollout vorgesehen; der Prozess reicht die drei BUSINESS_OBJECT_*-Variablen nach dem Altverhalten durch

Verweise