Brainversum · tuxametrics Graph Admin

konzept vertraulich owner: matus

Autorisierungs-Authority und IdP entscheiden

Autorisierungs-Authority und IdP entscheiden

Status:ENTSCHIEDEN UND UMGESETZT (2026-08-10) — Entscheidung: ADR-0005

Dieses Dokument wird nicht gelöscht. Es beschreibt, warum die Lücke bestand und was fehlte — und genau das ist die Begründung für den Umfang dessen, was jetzt gebaut wurde. Der ursprüngliche Befund steht unverändert darunter; erledigt ist er ab dem Abschnitt „Was am 2026-08-10 gebaut wurde". Muster wie in v12-nachzug-scope.md: Historie bleibt lesbar.


Was am 2026-08-10 gebaut wurde

Auslöser: es sollen Ärztinnen und Ärzte mit echten Patientendaten arbeiten. Damit ist die Frist aus GG-ARCH-SERVICES-0005.5 („fällig vor dem ersten Nutzer, der nicht der Entwickler ist") abgelaufen — ab da ist ein Platzhalter kein „bewusst offen" mehr, sondern ein Regelverstoß.

Was Wo
Eigene Authority (Form „eigen" aus -0005.1) tuxametrics-platform-service, Port 8125, eigene Postgres 5524. Umfang exakt -0005.2, Vorbild prozesswerkstatt-platform-service
IdP Keycloak, Realm digital-labs, Port 8180, eigene Postgres 5526. Zwei Clients, Claim-Mapper für user_key/actor_type/tenant_key (keycloak/README.md)
Virtual Identity tuxametrics-virtual-identity-service, Port 8126, eigene Postgres 5525. Kein Pflichtbaustein — fachlich begründet über OF-12
Registrar im Data-Service (-0005.3) tuxametrics-laborauswertung-service/capability/CapabilityRegistrar + PlatformCapabilityAdminClient + ServiceTokenClient + KeycloakTokenRestClient. Nicht startblockierend, Retry im Daemon-Thread
allowedActors je Capability Alle 22 Schlüssel (18 txm.* + 4 vid.*) tragen eine nicht-leere Liste. Die drei GM-7-Schlüssel sind [USER], durchgesetzt an vier Stellen (util/Gm7Schutz, Gm7SchutzTest)
UI AuthShell — gebautes Artefakt immer OIDC, Dev-Stub nur noch als Abkürzung ohne Infrastruktur

Zwei Dinge, die über den Referenz-Bauplan hinausgehen und begründet sind:

  1. Der Bootstrap-Grant wird angelegt, nicht dokumentiert. prozesswerkstatt-platform-service führt das Henne-Ei-Problem (der Upsert-Endpunkt ist selbst gegatet) als Operator-Vorgang. Hier legt boot/CapabilityRegistrar genau einen Grant an: SERVICE svc-tuxametrics-services darf platform.capability.create im Sentinel-Mandanten platform. Bei Gesundheitsdaten ist „der Operator macht das schon" kein Betriebszustand.
  2. CapabilityRegistrarTest bindet die registrierten Schlüssel maschinell an die tatsächlichen capabilityGuard.require(...)-Aufrufe. Beide Driftrichtungen brechen jetzt den Build.

Korrektur einer Zahl, die dieses Dokument selbst noch falsch hat

Der Absatz unten sagt „14 schreibende Endpunkte" und „die 14 txm.*-Capabilities". Es sind 18 (17 schreibende plus txm.auswertung.read). Die 14 stand seit der Erstanlage in mehreren Dokumenten; korrigiert wurde sie am 2026-08-10 in implementation-gaps.mddieses Dokument war eine der Stellen, die dabei stehen blieben. Der Satz oben schließt das nach.

Neu ist nicht die Zahl, sondern dass sie jetzt nicht mehr driften kann. CapabilityRegistrarTest vergleicht die registrierte Schlüsselmenge maschinell mit den tatsächlichen capabilityGuard.require(...)-Aufrufen im Quelltext. Vorher waren das zwei Listen, die nichts aneinander band außer Disziplin — und genau daran ist die 14 vier Dokumente weit mitgelaufen.

Was davon nicht berührt ist

tuxametrics.regulatorik.einstufung und tuxametrics.pilot.betriebsmodus stehen unverändert auf UNGEKLAERT, RegulatorikGuard greift unverändert beim Start (ADR-0002, ADR-0003). Das sind Fragen an Dritte, keine Infrastrukturfragen — dass die Autorisierung entschieden ist, sagt über sie nichts.


Der ursprüngliche Befund (Stand bis 2026-08-09)

Der Service ruft CapabilityGuard.require("txm.…") an allen 14 schreibenden Endpunkten (korrekt: 17, siehe oben). Der PlatformCapabilityCheckClient zeigt auf quarkus.rest-client.platform-service.url — einen lokalen Platzhalter. Im Test ersetzt AllowAllCapabilityCheckClient den echten Client; geprüft wird der Pfad durch das Gate, nicht der Remote-Check.

Ebenso offen: welcher IdP für eine Pilotpraxis gilt. Der Compose-Stack enthält keinen Keycloak.

Von drei Schritten existiert nur der dritte — es fehlt ein CapabilityRegistrar (2026-08-09)

GG-GOV-SECURITY-0003 nennt die Reihenfolge ausdrücklich bindend: registrieren → granten → erzwingen. Dieses Repo macht nur das Erzwingen. Es gibt keinen CapabilityRegistrar, keinen PlatformCapabilityAdminClient und keinen ServiceTokenClient — die 14 txm.*-Capabilities sind nirgends deklariert, sie existieren ausschließlich als Zeichenketten in require(...)-Aufrufen. Kein Katalog kennt sie, also kann sie auch niemand granten.

Das ist der Unterschied, der beim Vergleich mit einem vollständig aufgesetzten Zwirn-Produkt sofort auffällt (dkfz-prozesswerkstatt/eventmanagement, dl-controlling/dot und Zwirns eigene Docker-Services haben alle drei Klassen). Es ist aber keine übersehene Lücke, sondern eine Folge genau dieser offenen Entscheidung: ein Registrar registriert bei einer Authority, und welche das ist, ist nicht entschieden. Ohne Ziel gibt es nichts zu registrieren.

Mit der Entscheidung fällig, als Paket und nicht einzeln:

Das fehlende Bauteil heißt Capability-Authority — und ist seit 2026-08-09 eine Zwirn-Regel

Der Abschnitt darüber beschreibt richtig, was fehlt (Registrar, Admin-Client, Service-Token-Client), sagt aber nicht, wogegen das registrieren würde. Genau diese Lücke hat Zwirn aus diesem Befund heraus geschlossen: GG-ARCH-SERVICES-0005 (Zwirn-ADR-0027, Herkunft ist ausdrücklich dieser tuxametrics-Befund).

Die Regel verlangt je Produkt genau eine benannte Authority, in einer von zwei Formen:

Form Was zu bauen/tun wäre Vorbild
eigen — ein tuxametrics-platform-service Tenant + Capability + CapabilityGrant, GET /api/platform/v1/capabilities/{key}/check, in-process-Registrar. Ausdrücklich nicht DOTs Umfang (72 Dateien mit Agent-Sessions, Keycloak-Client-Provisioning, Profilen, OPA, Outbox) prozesswerkstatt-platform-service — 34 Java-Dateien, der belegte Minimalbauplan
fremd — die Authority eines Plattform-Repos desselben Kunden konsumieren Kein eigener Service; nur Registrar + Check-Client, dafür ein schriftlicher Check-Vertrag auf der Gegenseite eventmanagement-anmeldung-serviceprozesswerkstatt-platform-service, Vertrag in CAPABILITY_CHECK_CONTRACT.md

Ein dritter Fall — Platzhalter-URL auf einen Dienst, den es nicht gibt — ist keine Form, sondern ein offener Punkt. Genau dafür existiert dieses Dokument.

Für ein Produkt mit Gesundheitsdaten hat die Formwahl eine fachliche Folge, die über Infrastruktur hinausgeht: -0005.2 verlangt allowedActors je Capability im Katalog. Das ist der einzige Ort, an dem sich „Freigabe, Übersteuerung und Therapeutenentscheidung sind USER-only" deklarieren und damit gegen einen AGENT-Grant absichern lässt. Heute ist das nur dadurch garantiert, dass die MCP-Tools ausnahmslos lesend sind — eine Eigenschaft des Codes, keine Zusage. Bei einer fremden Authority bedeutet das: der Katalog dort muss allowedActors führen, sonst ist GM-7 nicht durchsetzbar und die Form scheidet aus. Das gehört in die Entscheidung, nicht danach.

Warum das trotzdem kein Regelverstoß ist: -0005.5 macht die Authority erst vor dem ersten Nutzer, der nicht der Entwickler ist fällig. Bis dahin genügt ein auf das dev-Profil begrenzter Stub plus ein benannter Backlog-Punkt — beides ist gegeben (siehe nächster Abschnitt; dev/dev-auth-stub.mjs existiert im gebauten Artefakt und im Compose-Stack nicht). Mit der ersten Pilotpraxis ist die Frist abgelaufen.

Warum es passiert ist — nicht Nachlässigkeit dieses Repos: Zwirns samples/sample-data-service-quarkus/.../capability/ liefert nur die Konsumentenseite (PlatformCapabilityCheckClient, PlatformCapabilityRestClient) — keinen CapabilityRegistrar, keinen Admin-Client, keinen ServiceTokenClient. Wer das Sample korrekt kopiert, bekommt genau Schritt 3 von dreien und hat keinen Anlass zu merken, dass zwei fehlen. Verstärkt dadurch, dass GG-GOV-SECURITY-0003 durchgehend dot-platform-service beim Namen nennt — das liest sich ohne DOT-Kenntnis wie ein plattformweit vorhandener Dienst statt wie DOTs lokale Implementierung. Zwirn-ADR-0027 führt das Sample-Nachziehen als Folgearbeit mit dem höchsten Hebel.

Was seit dem Frontend-Start daran hängt (2026-08-09)

Damit tuxametrics-ui im Browser bedienbar ist, gibt es jetzt einen lokalen Stub für genau diese beiden offenen Punkte: dev/dev-auth-stub.mjs. Er signiert ein JWT mit dem Claim-Satz aus TestToken.java, veröffentlicht den passenden öffentlichen Schlüssel als JWKS und antwortet auf jeden Capability-Check mit allowed: true.

Er greift ausschließlich im Quarkus-dev-Profil (%dev-Block in application.properties) und im Vite-Dev-Server; im gebauten Artefakt und im Compose-Stack existiert er nicht. Er ist kein Keycloak-Realm und keine Vorentscheidung — er spricht kein OIDC-Protokoll, kennt keinen Realm und kein Rechtemodell.

Was mit dieser Entscheidung zu löschen ist: dev/, der %dev-Block in application.properties, der /dev-auth-Proxy in vite.config.ts und tuxametrics-ui/src/shell/DevAuthProvider.tsx (ersetzt durch AuthProvider + createOidcConfig aus @zwirn/web, eine Zeile in main.tsx).

Nachtrag 2026-08-10 — bewusst anders gemacht als hier vorgesehen. Der Tausch in main.tsx ist tatsächlich eine Zeile geworden (AuthShell), und ein gebautes Artefakt nimmt immer OIDC — genau das verlangt -0005.5. Gelöscht wurde der Stub trotzdem nicht. Begründung: seine Bedeutung hat sich geändert, nicht sein Nutzen. Er war der Ersatz für eine offene Entscheidung; jetzt ist er die Abkürzung für einen Dev-Loop ohne laufende Infrastruktur — der Unterschied zwischen npm run dev in fünf Sekunden und einem Kaltstart von 15 Containern. Das ist ein echter Nutzen, und -0005.5 verbietet ihn nicht: verboten ist der Stub „im gebauten Artefakt oder im Compose-Stack", und dort ist er nach wie vor nicht.

Damit die Abkürzung keine zweite Wahrheit wird, hängt der Default an import.meta.env.PROD und nicht an einer Variable, die jemand vergisst — und npm run dev:oidc fährt denselben Dev-Server gegen die echte Kette. Ebenso neu: %dev.tuxametrics.capability.registrar.aktiv=false, weil der Stub keinen Token-Endpunkt hat und der Registrar sonst zwei Minuten lang vergeblich anklopfen würde.

Warum das nicht nebenbei entschieden werden sollte

Bei einem Produkt mit Gesundheitsdaten ist die Autorisierung keine Infrastrukturfrage. Zwei Punkte aus dem Fachmaterial hängen direkt daran:

  1. OF-12 — ist der Mandant die Praxis oder der Therapeut? Davon hängt ab, ob Therapeut B die Patienten von Therapeut A sieht. Ein Prototyp mit einem Therapeuten umgeht die Frage; die erste Praxis mit zwei Therapeuten nicht.
  2. OF-62 — die Zweckbindung der Kennzahlen muss im Datenmodell erzwungen werden, nicht nur über Rechte. Die Auswertung von Übersteuerungen dient der Regelverbesserung, „nicht der Bewertung einzelner Therapeuten". Eine Capability allein setzt das nicht durch.

Nächster Schritt — erledigt am 2026-08-10

Die drei Punkte, die hier standen, sind abgearbeitet:

  1. ✅ Wahl eigen, mit Namen und Port in service-landscape.md eingetragen — die Zeile „Autorisierungs-Authority: Platzhalter" ist ersetzt, nicht ergänzt.
  2. ✅ Klassen-Paket gebaut (Registrar + Admin-Client + Service-Token-Client) in beiden Konsumenten. Ein Check-Vertrag der Gegenseite entfällt bei eigener Authority — stattdessen ist der eigene Vertrag schriftlich, weil zwei Zwirn-Fremd-Images (zwirn-audit-service, zwirn-feedback-service) ohne Einblick in diesen Code dagegen bauen: CAPABILITY_CHECK_CONTRACT.md.
  3. ⚠️ docker compose build nicht verifiziert — siehe „Was offen bleibt".

Es gibt keine „Zwirn-Plattform-Authority" zum Mitbenutzen — das war eine Fehlannahme einer früheren Fassung dieses Absatzes. Zwirn liefert die Konsumentenseite (zwirn-securitys CapabilityGuard) und das Muster, aber keinen betriebenen Dienst; jede Authority gehört einem Produkt.


Was offen bleibt