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:
- Die Spezifikation existiert vollständig: Zwirn-ADR-0039 (2026-08-13) und der Abschnitt
„Profile" in
governance/security/authorization.md. - Der Code im
zwirn-platform-serviceexistierte nicht. Zwirn-ADR-0039 Punkt 4 hatte den Bau ausdrücklich vertagt — unter anderem mit der Begründung „die dritte, zwei Tage alte Authority kommt ohne aus". Genau das gilt seit heute nicht mehr.
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.createhä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 drei GM-7-Capabilities (
txm.empfehlung.decide,txm.uebersteuerung.create,txm.freigabe.grant). Sie sind über ein Profil strukturell nicht erreichbar —Gm7Schutzweist einen Profil-Grant darauf mit 422 ab, auf dem HTTP- und auf dem Boot-Pfad. Begründung unter Punkt 4. - Jede schreibende
txm.*-Capability (patient.create,messwert.write,sicherheitsmerkmal.write,medikation.write, …). Das ist klinische Dokumentation. Um den Ablauf zu beurteilen, braucht es sie nicht: dafür gibt es die Demo-Vorlagen und den Testfallkatalog. platform.capability-grant.*undplatform.user-profile.*. Ein Product Owner vergibt keine Rechte und keine Rollen.vid.*undautoimprove.agent-module-scope.*. Identitäts- und Agenten-Verwaltung.autoimprove.item.architecture-approve. Die architektonische Abnahme ist nicht die fachliche.
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
- + Die zwei Rollen sind zum ersten Mal benannt statt implizit. Ein dritter Mensch bekommt eine Zeile, keine zwölf.
- + Die Vermischung „Rechteverwaltung + klinische Arbeit in einer Identität", die das Repo seit dem 2026-08-11 als offenen Punkt führt, wird nicht in die neue Struktur übernommen.
- + GM-7 hat eine fünfte Verteidigungslinie, und sie schließt eine Lücke, die vor den Profilen nicht existierte und mit ihnen entstanden wäre.
- +
ResolvedCapabilities.profilesist nicht mehr dauerhaft leer — das Feld stand im@zwirn/web-Vertrag und wurde bisher nur bedient, um die TypeScript-Seite nicht zu brechen. - −
CapabilityGrantEntity.actorTypewechselt den Java-Typ (ActorType→GrantHolderType). Wire-Format und DB bleiben identisch, aber jeder Aufrufer im Modul war betroffen. Wer diesen Diff liest und nur „Enum umbenannt" sieht, hat den Grund verpasst — er steht oben und inGrantHolderType. - − Ein Tippfehler im Profilnamen bleibt diagnosefrei: es gibt kein Register, gegen das
validiert würde (Zwirn-ADR-0039). Die Folge ist ein Profil ohne Grants, also stillschweigend keine
Rechte — fail-closed, aber ohne Fehlermeldung. Die
@Pattern-Prüfung aufUserProfileAssignmentCreatefängt nur die Form, nicht die Existenz. - − Der Ist-Zustand und der Soll-Zustand fallen ab jetzt auseinander:
praxis01-user-xt5uk6hihält weiterhin alletxm.*direkt, obwohlplatform-ownersie nicht enthält. Das ist bewusst (nichts wird entzogen), aber es ist eine Drift, und sie hat ein Ablaufdatum — siehe unten.
Offen — bewusst nicht in dieser ADR entschieden
- 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 anpraxis01-user-arzt003, nie eine Erweiterung vonproduct-owner— der Weg über ein Profil ist für diese drei Schlüssel geschlossen und soll es bleiben. - Ein drittes Profil
pilot-therapeutfü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. - 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. praxis01-user-arzt003folgt der alten, rollenbasierten Key-Konvention statt{tenant}-user-{nanoid8}(gültig seit 2026-08-13). Einuser_keyist 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.- Daniel hat (vermutlich) keine VID-Identity in
tuxametrics-virtual-identity-service. Für die Autorisierung ist das folgenlos — die Authority kennt nuruser_key—, für die Anzeige „wer ist das" nicht. Nicht angelegt, weilvidkein Pflichtbaustein ist (OF-12). - Kein UI. Profile werden über REST bzw. das Bootstrap-Skript zugewiesen;
tuxametrics-uihat keine Oberfläche dafür. Für zwei Menschen ist das die richtige Menge Bauwerk.
Verweise
- Zwirn-ADR-0039 — spezifiziert den Profil-Entwurf, vertagt in Punkt 4 den Bau.
- Zwirn-ADR-0043 — löst diese Vertagung ein und entscheidet
PROFILE ≠ ActorType. Zuerst dort gebaut, dann hier nachgezogen. - Zwirn-ADR-0027 — Authority-Pflicht je Produkt, Form „eigen".
- ADR-0005 — die Authority selbst,
Gm7Schutz. - ADR-0007 — erster
AGENT-Actor,GET /me/capabilities. scripts/erste-grants-bootstrap.sql— der Vorgänger, und die Fundstelle des offenen Punkts „Rechteverwaltung und Fachnutzung in einer Identität".