Brainversum · tuxametrics Graph Admin

adr vertraulich owner: matus

ADR-0008: Zwei Capability-Profile — `platform-owner` und `product-owner`

ADR-0008: Zwei Capability-Profile — platform-owner und product-owner

Status

Angenommen (2026-08-17). Ergänzt ADR-0005 (eigene Capability-Authority) und ADR-0007 (erster AGENT-Actor). Löst keine ADR ab.

Kontext

Der Anlass ist ein zweiter Mensch

Dieses Produkt hatte bis heute genau einen menschlichen Actor: praxis01-user-xt5uk6hi, Tenant praxis01, mit allen Rechten als Einzelgrants (scripts/erste-grants-bootstrap.sql, dazu der Demo-Grantsatz aus boot/CapabilityRegistrar). Mit Daniel Matka kommt ein zweiter dazu, in einer anderen Rolle: Product Owner neben Platform Owner. Er existiert bereits als Keycloak-Nutzer daniel.matka im committeten Realm-Export, user_key = praxis01-user-arzt003.

Zwei Actors mit unterschiedlichen Capability-Mengen sind genau der Fall, für den Zwirns Regelwerk ein Werkzeug vorsieht — und ausdrücklich nur eines:

„Eine Capability = eine Funktion. Gebündelt wird über Profile, nie dadurch, dass eine Capability mehrere Aktionen gatet." (governance/security/authorization.md)

Die naheliegende Abkürzung — Daniel bekommt zwölf Einzelgrants — wäre nicht falsch, aber sie wäre der Anfang genau des Zustands, den ein Profil verhindert: eine Rechtemenge, die nirgends benannt ist und sich beim dritten Menschen nur noch durch Abschreiben reproduzieren lässt.

Zwirn zuerst — und was die Prüfung ergeben hat

Der Auftrag war ausdrücklich, erst zu prüfen, ob das Element in Zwirn existiert, und es dort zu schließen, bevor dieses Produkt eine dritte Kopie baut. Ergebnis:

Deshalb wurde zuerst in Zwirn gebaut (Zwirn-ADR-0043, zwirn-platform-service), und dieses Modul hat den fertigen Mechanismus nachgezogen — nicht umgekehrt. Das ist kein Formalismus: der tuxametrics-platform-service ist eine Kopie des zwirn-platform-service („Form eigen", Zwirn-ADR-0027), bis in die Paketstruktur. Eine hier erfundene Variante wäre für jedes weitere Produkt wertlos gewesen.

Der Fund, der den Nachbau von einer Fleißarbeit zu einer Entscheidung macht

Zwirn-ADR-0039 beziffert den Einbau mit „nur ein zusätzlicher Enum-Wert" für actor_type. Das stimmt für dot-platform-service, wo dieses Feld ein String ist. Hier nicht. Dieses Modul typisiert es mit dev.zwirn.security.core.usercontext.ActorType — dem Enum aus zwirn-security-java-core, das genau eine Herkunft hat: ActorType.fromClaim(String), also den actor_type-Claim des Tokens.

Wäre PROFILE dort ein vierter Wert geworden, gäbe es diesen Weg:

Token:  actor_type=PROFILE, user_key=platform-owner
Check:  ActorType.fromClaim("PROFILE") -> PROFILE  (akzeptiert, fromClaim nimmt jeden Enum-Namen)
        Grant-Lookup trifft sauber jeden Grant des Profils

Ein Aufrufer hielte alle Rechte eines Profils, ohne je eine Zuweisung zu besitzen. Der Fund ist generisch und deshalb in Zwirn gelandet (ADR-0043, Regeltext in GG-GOV-SECURITY-0003); hier steht die Folge: model/GrantHolderType (USER, AGENT, SERVICE, PROFILE) auf der Persistenz-Ebene, ActorType unverändert am Token. Spalte, Spaltenname und Spaltenwerte bleiben gleich — kein Schema-Bruch, kein Migrationsschritt für bestehende Grants. Gegenprobe: CapabilityProfileTest.profileIstKeinBehauptbarerActorTyp.

Entscheidung

1. Zwei Profile, additiv, ohne Deny

platform-owner und product-owner. Beide sind Bündel bestehender Capabilities — keine neue Capability, kein neues Recht. Ein Profil kann nur geben; Überschneidung zwischen beiden ist normal und beabsichtigt (beide müssen vieles lesen können).

Bestehende Einzelgrants bleiben unangetastet. Die Profile sind additiv; praxis01-user-xt5uk6hi verliert durch diese ADR keine einzige Capability. Der Abbau der historisch gewachsenen Direktgrants ist eine eigene Entscheidung (siehe „Offen" unten).

2. platform-owner — wer das System verwaltet

Gruppe Keys Warum
Mandanten platform.tenant.create/read/update Eine Praxis aufnehmen ist eine Verwaltungshandlung.
Katalog platform.capability.read …​.create ist [SERVICE]-only und für einen Menschen nicht grantbar.
Rechtevergabe platform.capability-grant.create/read/delete Der Kern der Rolle: wer Rechte vergibt.
Profilvergabe platform.user-profile.create/read/delete Neu mit dieser ADR. Wer Rollen zuweist, gehört auf dieselbe Seite wie wer Rechte erteilt.
Identitäten vid.identity.create/read/update/deactivate Personen, Funktionspostfächer und agentische Identitäten je Praxis.
Betriebssicht txm.process.* (4, lesend) Prozess-Cockpit. Kein Schreibrecht — es gibt keins.
Agenten-Scope autoimprove.agent-module-scope.create/read Was ein Agent sehen darf, ist Governance, keine Produkttriage.
Autoimprove-Katalog autoimprove.module.create/read/update, autoimprove.test-object.read, autoimprove.item.read Die Modul-/Testobjekt-Struktur ist Infrastruktur der Pipeline.
Freigabe-Endstufe autoimprove.item.architecture-approve, .release-for-implementation Die architektonische Abnahme gehört zur Plattformrolle; release-for-implementation zusätzlich, damit die Pipeline nie an einer einzigen Person hängt.

Bewusst NICHT enthalten: jede txm.*-Capability außer den vier lesenden txm.process.*. Das ist die inhaltlich wichtigste Zeile dieser Tabelle, und sie widerspricht dem Ist-Zustand. Der Kopfkommentar von scripts/erste-grants-bootstrap.sql benennt den Grund seit dem 2026-08-11 selbst:

„Wer platform.capability-grant.create hält, kann Rechte vergeben. Das auf denselben Account zu legen, der auch klinisch arbeitet (txm.*), vereint Rechteverwaltung und Fachnutzung in einer Identität. Für einen Prototyp mit einem Menschen ist das unvermeidbar; für einen Pilotbetrieb mit mehreren Therapeuten ist es ein offener Punkt und gehört getrennt."

Mit dem zweiten Menschen ist der Prototyp-Sonderfall vorbei. Die Trennung wird hier zwar noch nicht vollzogen (die Direktgrants bleiben), aber sie wird auch nicht fortgeschrieben: das Profil, das die Rolle künftig definiert, enthält sie nicht. Wer klinisch arbeiten soll, braucht dafür eine eigene Rolle — siehe „Offen".

3. product-owner — wer das Produkt fachlich beurteilt

Gruppe Keys Warum
Gold Path ansehen txm.auswertung.read Ohne diese eine Capability kann ein Product Owner die Frage „ist der Ablauf richtig geschnitten?" nicht beantworten. Mit benannter Bedingung, siehe unten.
Demo-Vorlagen txm.demo.load, txm.demo.cleanup Referenzfälle laden und aufräumen. Berührt ausschließlich synthetische Daten.
Prozess-Cockpit txm.process.* (4, lesend) Dieselbe Betriebssicht wie oben — Überschneidung ist beabsichtigt.
Testfälle zwirn-testmanagement.catalog.read/write, run.read/create/write Der Testfallkatalog ist die tägliche Arbeit dieser Rolle. „Ausführen" ruft POST /demo/referenzfall, also denselben synthetischen Weg.
Verbesserungs-Pipeline autoimprove.item.create/read/update/transition, .release-for-implementation, .domain-approve, autoimprove.test-object.create/read, autoimprove.module.read Melden, triagieren, zur Analyse freigeben, fachlich abnehmen. Das ist Produktarbeit.

Bewusst NICHT enthalten:

Die benannte Bedingung zu txm.auswertung.read: Sie gilt, solange dieses Produkt ausschließlich den synthetischen Referenzfall enthält — der heutige, dokumentierte Zustand (CLAUDE.md: keine klinisch validierten Regeln, tuxametrics.pilot.betriebsmodus = UNGEKLAERT, ADR-0003). Vor dem ersten echten Patientenfall ist dieser eine Eintrag neu zu entscheiden, denn dann liest er Gesundheitsdaten. Das ist dieselbe Bauart von aufgeschobener Entscheidung wie tuxametrics.platform.demo-grants=false — kein Vorbehalt im Fließtext, sondern ein Punkt, der im Skript und in dieser Tabelle steht.

4. Die GM-7-Kette bleibt Einzelgrant an einen namentlich bekannten Menschen

Neu und nicht selbstverständlich: die vier bestehenden GM-7-Verteidigungslinien hätten einen Profil-Grant auf txm.freigabe.grant durchgelassen. Ein Profil wird immer von einem Menschen ausgeübt, der wirksame Actor-Typ ist also USER — die allowedActors=[USER]-Prüfung besteht er. Deshalb gibt es jetzt eine fünfte Linie (Gm7Schutz.istZulaessigerHalter, Gm7SchutzTest.gm7Capabilities_sindNieUeberEinProfilGrantbar).

Der Grund ist fachlich, nicht technisch. GM-7 verlangt die Freigabe durch „eine Person mit benannter Qualifikation". Ein Profil ist keine benannte Person, sondern eine Rolle mit einem Namen, den niemand validiert — es gibt kein Profil-Register (Zwirn-ADR-0039). Wer einem Profil txm.freigabe.grant gäbe, hätte die ärztliche Freigabe an eine Rollenzuweisung gehängt, die ein zweiter Verwaltungsschritt an einem ganz anderen Ort erteilt, und niemand sähe dem Grant noch an, an welchen Menschen er tatsächlich geht.

Das sperrt weiterhin keine klinische Entscheidung. Ein Therapeut mit direktem Grant gibt unverändert frei — die Gegenprobe steht seit 2026-08-10 im Test und ist grün. Gesperrt ist nur der Weg, auf dem die Befugnis unbemerkt entstehen könnte.

Und deshalb wird hier ausdrücklich nicht entschieden, ob Daniel klinisch entscheiden darf. Das ist keine Frage der Rechteverwaltung, sondern der fachlichen Qualifikation, und sie gehört nicht in eine Rollendefinition. Sie steht unter „Offen".

5. Zuweisung vorbereitet, nicht ausgeführt

scripts/capability-profile-bootstrap.sql legt die Profil-Grants und die zwei Zuweisungen an — additiv, idempotent, reversibel, nach dem Muster von scripts/erste-grants-bootstrap.sql. Es wurde nicht ausgeführt. Die Autorisierung eines zweiten echten Menschen ist ein bewusster, separat zu bestätigender Schritt, kein Nebeneffekt eines Commits — dieselbe Haltung, aus der demo-grants standardmäßig aus ist.

Konsequenzen

Offen — bewusst nicht in dieser ADR entschieden

  1. Darf Daniel klinisch entscheiden? Also: hält er die benannte Qualifikation für txm.empfehlung.decide / txm.uebersteuerung.create / txm.freigabe.grant? Das ist eine fachliche und haftungsrechtliche Frage, keine der Rechteverwaltung. Falls ja, wäre die Antwort drei Einzelgrants an praxis01-user-arzt003, nie eine Erweiterung von product-owner — der Weg über ein Profil ist für diese drei Schlüssel geschlossen und soll es bleiben.
  2. Ein drittes Profil pilot-therapeut für die klinisch arbeitende Rolle (txm.* schreibend, ohne die drei GM-7-Schlüssel). Es wäre der Schritt, der die Trennung aus Punkt 2 tatsächlich vollzieht statt sie nur nicht fortzuschreiben. Braucht einen eigenen Anlass: einen dritten Menschen, der klinisch arbeitet.
  3. Abbau der Direktgrants von praxis01-user-xt5uk6hi. Additiv einzuführen war richtig; etwas wegzunehmen ist eine andere Entscheidung und braucht einen Moment, in dem jemand bestätigt, dass nichts kaputtgeht.
  4. praxis01-user-arzt003 folgt der alten, rollenbasierten Key-Konvention statt {tenant}-user-{nanoid8} (gültig seit 2026-08-13). Ein user_key ist der Anker zwischen Keycloak-Claim, VID-Identity und jedem Grant; ihn nebenbei umzubenennen wäre eine Migration über drei Systeme. Eigene Entscheidung, hier nur benannt.
  5. Daniel hat (vermutlich) keine VID-Identity in tuxametrics-virtual-identity-service. Für die Autorisierung ist das folgenlos — die Authority kennt nur user_key —, für die Anzeige „wer ist das" nicht. Nicht angelegt, weil vid kein Pflichtbaustein ist (OF-12).
  6. Kein UI. Profile werden über REST bzw. das Bootstrap-Skript zugewiesen; tuxametrics-ui hat keine Oberfläche dafür. Für zwei Menschen ist das die richtige Menge Bauwerk.

Verweise