adr vertraulich owner: matus
ADR-0007: `zwirn-autoimprove-service` statt eigener Feedback-Triage — und statt `zwirn-feedback-service`
ADR-0007: zwirn-autoimprove-service statt eigener Feedback-Triage — und statt zwirn-feedback-service
Status
Angenommen (2026-08-14). Löst ADR-0006 ab (einen Tag nach dessen Annahme).
Kontext
Was ADR-0006 entschieden hat — und woran es lag
ADR-0006 hat für den Rückkanal aus einer Pilotpraxis ein eigenes Spring/Flowable-Modul gebaut
(tuxametrics-feedback-process, Port 8128, eigene Engine-DB): zwei UserTasks und zwei ServiceTasks,
die den Status einer Meldung im zwirn-feedback-service von OPEN auf RESOLVED treiben. Abschnitt
6 dieser ADR begründet ausführlich, warum das Ergebnis „deutlich kleiner" ausfällt als der Auftrag
(„Autoimprove umsetzen") — und stützt die Begründung auf DOTs
konzepte/agentic-orchestration.md, ein Dokument, das im Quellrepo selbst als „Nicht gebaut …
zurückgestellt" markiert ist, in einem Ordner mit der Überschrift „Frühe Architektur-Vision, nicht
das tatsächlich Gebaute".
Das war die falsche Quelle. „Autoimprove" ist in Zwirn kein Visionspapier, sondern ein fertiger,
betriebener Service: dev.zwirn:zwirn-autoimprove-service (C:\dev\zwirn\feedback\autoimprove-service),
mit eigenem Datenmodell, 16 selbst registrierten Capabilities, Actor-Type-Dual-Gate, MCP-Tools und
einem fertigen Frontend-Paket @zwirn/autoimprove. Er wurde bei der Recherche zu ADR-0006 schlicht
nicht gefunden — gesucht wurde in DOT, gefunden wurde DOTs zurückgestelltes Konzept, und daraus
folgte der Schluss „Autoimprove ist nicht gebaut". Der Schluss war falsch, und mit ihm die
Entscheidung: das Eigenbau-Modul hat mehr Code gekostet als das Anschließen des kanonischen Service,
und es leistet weniger.
Das ist ein Recherchefehler, kein Meinungswechsel. Er steht hier so, weil ADR-0006 sonst als abgewogene Alternative gelesen wird, die sie nicht war.
Der Recherchefehler hatte eine zweite Hälfte: das falsche DOT-Verzeichnis
Auch bei der Arbeit an diesem ADR wurde zunächst C:\dev\dot\dot untersucht und dort „keine
Adoption gefunden". Dieses Verzeichnis ist ein totes Checkout und für DOT nicht maßgeblich. Das
lebende Repo ist C:\dev\dl-controlling\dot — und dort ist der Umstieg vollzogen und
dokumentiert:
C:\dev\dl-controlling\dot\guidelines\adr\0071-autoimprove-service-adoption.md— „ADR-0071: Adoption vonzwirn-autoimprove-servicestatt eigenem Feedback-Modul", angenommen am 2026-08-03.
DOT hat dort exakt denselben Schnitt entschieden, den dieses ADR trifft: eigenes Feedback-Modul
(Data Service und BPMN-Prozessmodul) ersatzlos gelöscht, zwirn-autoimprove-service als
Docker-Service mit eigener DB und eigenem Container übernommen (kein Pooling über Produkte),
@zwirn/autoimprove als file:-Dependency, kein Prozessmodul mehr (die Agenten-Pipeline braucht
keinen BPMN-Statuswechsel), dedizierter Keycloak-Client pro Service, und der Melde-Dialog lokal
nachgebaut, weil das Paket keinen mitbringt.
Damit ist dieses ADR kein Alleingang, sondern der zweite Fall desselben Musters. Das ist der Grund, warum es hier steht: eine Entscheidung, die man für einen Sonderweg hält, wird anders geprüft als eine, die ein Schwesterprodukt vor elf Tagen genauso getroffen hat.
Ausdrücklich nicht als Präzedenzfall herangezogen: evepop. DOTs ADR-0071 nennt dessen
Umstieg zwar als Vorbild, aber evepop hat ihn später zurückgenommen. Eine zurückgerollte Adoption
trägt hier nichts — weder als Argument dafür noch dagegen.
Zwirn bietet zwei Dinge an, nicht eines
Das ist die zweite Sache, die in ADR-0006 fehlt, und sie ist kein Detail:
| Zwirn-Baustein | Was er ist | Zustandsmodell |
|---|---|---|
zwirn-feedback-service + @zwirn/feedback |
Leichtgewichtiger Rückkanal: ein Melde-Widget mit Screenshot, eine Liste | OPEN → IN_PROGRESS → RESOLVED |
zwirn-autoimprove-service + @zwirn/autoimprove |
Claim-basierte Karten-Pipeline mit AGENT-Actor und gestaffelten menschlichen Freigaben | NEU → BEREIT_ZUR_ANALYSE → IN_KONZEPTION → BEREIT_ZUR_UMSETZUNG → IN_UMSETZUNG → UMGESETZT → ABGENOMMEN |
Beide bleiben in Zwirn gültig und nebeneinander bestehen. Sie sind zwei unabhängige Angebote, aus denen jedes Produkt einzeln wählt; dieses ADR sagt über Zwirn nichts und macht keines der beiden obsolet. Was es sagt, gilt nur für tuxametrics — und dort ist die Wahl ein Entweder-oder: beide gleichzeitig zu betreiben hieße, zwei Rückkanäle mit zwei Zustandsmodellen und zwei Oberflächen für dieselbe Sache zu haben, und die erste Frage jedes Melders wäre, in welchen der beiden sein Bug gehört.
Entscheidung
tuxametrics betreibt zwirn-autoimprove-service — und nur ihn. zwirn-feedback-service wird
außer Betrieb genommen, tuxametrics-feedback-process ersatzlos entfernt.
1. Was wegfällt
| Weg | Was damit verschwindet |
|---|---|
tuxametrics-feedback-process (Maven-Modul, ADR-0006) |
Das fünfte Reactor-Modul, feedback-report.bpmn, die Task-API, der Nachzug-Job, die Engine-DB auf 5528, Port 8128, der Keycloak-Client tuxametrics-feedback-process und die sechs feedback-process.*-Capabilities |
zwirn-feedback-service (Fremd-Image) |
Container + DB, die vier feedback.report.*-Grants im Demo-Seed, @zwirn/feedback samt html2canvas, der <FeedbackReportButton/> in shell/TopNav.tsx, die Proxy-Routen /api/feedback/ und /api/fbp/ |
Der Reactor ist damit wieder bei vier Modulen (Authority, VID, Laborauswertung,
Process-Hub-Cockpit), und tuxametrics-process-hub-cockpit ist wieder die eine dokumentierte
Ausnahme vom Quarkus-Standard (Zwirn-ADR-0007), nicht eine von zweien.
2. Ports und DB: der frei gewordene Slot wird wiederverwendet
tuxametrics-autoimprove-service läuft auf 8123, tuxametrics-autoimprove-db auf 5522 —
dieselben Host-Ports, die bis 2026-08-13 zwirn-feedback-service und seine DB belegt haben. Bewusst
wiederverwendet statt neu vergeben: der Slot ist frei geworden, und eine neue Nummer würde nur
suggerieren, dass beides nebeneinander läuft.
Die Volumes sind nicht übernommen (data_tuxametrics_autoimprove_db statt
data_tuxametrics_feedback_db): anderes Schema, andere Entitäten. Vorhandene Feedback-Meldungen
werden nicht migriert. Auf dem Stand vom 2026-08-14 ist das folgenlos — es gibt keine
Pilotpraxis und keine echte Meldung —, aber es ist eine Entscheidung und keine Selbstverständlichkeit.
3. Ein eigener Keycloak-Client für den Service — nicht der geteilte Account
zwirn-autoimprove-service meldet beim Start seine 16 Capabilities selbst an und braucht dafür ein
Client-Credentials-Token. Naheliegend wäre der bestehende geteilte tuxametrics-services gewesen.
Stattdessen ein eigener Client tuxametrics-autoimprove-service — dieselbe Wahl, die DOT an
genau dieser Stelle getroffen hat (ADR-0071, Abschnitt „Dedizierter Keycloak-Client": „konsistent
mit dots bestehendem Muster ‚ein Client pro Service'").
Der geteilte Account ist in keycloak/README.md seit jeher als bewusste DEV-Vereinfachung
benannt, nicht als Zielarchitektur. Dies ist der erste Schritt heraus, und er kostet fast nichts:
| Wert | |
|---|---|
clientId |
tuxametrics-autoimprove-service |
user_key (→ Actor-Key) |
svc-tuxametrics-autoimprove |
actor_type |
SERVICE |
tenant_key |
["platform"] — nicht praxis01 |
| Flows | nur Client-Credentials (standardFlow/directAccessGrants aus) |
Warum kein praxis01: dieses Token wird ausschließlich für die Registrierung im Sentinel-Mandanten
benutzt (capability/ServiceTokenClient im Zwirn-Service hat genau einen Aufrufer, den
CapabilityRegistrar). Der Karten-Verkehr läuft auf dem Token des jeweiligen Aufrufers, nicht auf
diesem. Blast-Radius bei Leak: Capability-Definitionen upserten. Keine Karte, kein Grant, keine
Praxis.
Der Preis: ein zweiter Bootstrap-Grant. DOT braucht keinen — dort ist der Upsert-Endpunkt bewusst
ungegated („Bootstrap-Henne-Ei"), ein neuer Service-Client läuft also einfach. Hier ist er gegated
(platform.capability.create, ADR-0005), also muss boot/CapabilityRegistrar.seedBootstrapGrant()
den neuen Actor namentlich kennen. Aus „ein Actor, eine Capability, der Sentinel-Mandant" wird
„zwei Actors, eine Capability, der Sentinel-Mandant". Die Liste ist damit die Stelle, an der
ablesbar ist, wie viele Service-Identitäten dieses Produkt hat; jede weitere kostet eine Zeile dort
und einen Testfall (PlatformResourceTest#bootstrapGrant_forAutoimproveServiceActor_exists).
4. Der AGENT-Actor: die erste nicht-menschliche, nicht-dienstliche Identität dieses Produkts
autoimprove.item.analyze und .implement sind im Katalog allowedActors=[AGENT] und zusätzlich im
Resource- und MCP-Layer des Service durch einen expliziten ActorType-Check abgesichert. Ein
Mensch und der geteilte Service-Account können sie per Konstruktion nicht halten. tuxametrics braucht
also einen AGENT-Actor — den ersten überhaupt.
Er entsteht an drei Stellen, und alle drei tragen denselben Schlüssel
(praxis01-vid-idn-agent001). Weicht eine ab, läuft jeder Claim in ein 403 ohne erkennbaren
Zusammenhang — dieselbe Falle wie beim service-actor-key:
| Stelle | Datei / Property | Rolle |
|---|---|---|
| Wer bist du | keycloak/realm-export.json, Client tuxametrics-autoimprove-agent |
Stellt das Token aus. Der einzige Client des Realms mit actor_type=AGENT. standardFlow/directAccessGrants aus, nur Client-Credentials |
| Wer ist das in dieser Praxis | tuxametrics-virtual-identity-service, boot/AgentIdentitySeeder + tuxametrics.vid.agent-identity.* |
Trägt die agentische Identität in die Identity-Registry ein — der Ort, den ADR-0005 für „agentische Identitäten" vorgesehen hat und der bis heute leer war |
| Was darfst du | tuxametrics-platform-service, boot/CapabilityRegistrar + tuxametrics.platform.agent-actor-key |
Die drei AGENT-Grants (unten) |
Warum der VID-Seeder existiert. IdentityService.create vergibt einen zufälligen nanoid8 — ein
so erzeugter Schlüssel kann per Konstruktion zu keinem der beiden anderen passen. Ohne den Seeder
wäre „die agentische Identität liegt in der Registry" eine Doku-Behauptung ohne Deckung. Der neue
Pfad IdentityService.seedAgentIfAbsent ist der einzige, auf dem ein identityKey vorgegeben
statt generiert wird; er ist fest auf IdentityType.AGENT verdrahtet (eine Methode, die freien
Schlüssel und freien Typ nimmt, könnte einen Menschen unter einem vorhersagbaren Schlüssel
anlegen) und über HTTP nicht erreichbar.
Warum der Schlüssel hier lesbar sein darf, obwohl keycloak/README.md für user_key ein
Zufallssuffix verlangt: das Argument dort ist der Schutz einer Person. Ein Agent ist keine
Person; es gibt niemanden zu pseudonymisieren. Die Formkonvention
{tenant}-vid-idn-{8 Zeichen} bleibt trotzdem eingehalten.
Beide Seeder sind standardmäßig AUS (tuxametrics.vid.agent-identity.seed=false,
tuxametrics.platform.demo-grants=false). Ein Stack, der beim Hochfahren ungefragt einen handelnden
Actor in eine Praxis einträgt, ist kein Betriebszustand.
5. Capability-Grants: 14 menschliche, 3 agentische
Die 16 Katalogeinträge meldet der Service beim Start selbst an (sein eigener CapabilityRegistrar).
Dieses Repo vergibt nur Grants, und zwar an zwei verschiedene Actors:
An den Menschen (AUTOIMPROVE_MENSCH_CAPABILITIES, 14): item.create/.read/.update,
item.transition (die menschlichen Übergänge _ready-for-analysis, _answer, _accept,
_reject-solution, _reject), die drei gestaffelten Freigabestufen vor der Umsetzung
(.release-for-implementation / .domain-approve / .architecture-approve — welche der Server
verlangt, entscheidet der approvalLevel der Karte), die vier Katalog-Rechte
(test-object.*, module.*) und die zwei agent-module-scope.*.
An den Agenten (AUTOIMPROVE_AGENT_CAPABILITIES, 3): item.read, item.analyze,
item.implement.
Beide Hälften sind nötig: eine Pipeline mit nur menschlichen Rechten bleibt bei NEU stehen, eine
mit nur agentischen kommt gar nicht erst dorthin (das erste Gate _ready-for-analysis ist [USER]).
Bewusst nicht an den Agenten vergeben: item.create (dass ein Agent sich selbst Karten anlegt,
ist eine eigene Entscheidung) und jede .transition-/Freigabe-Capability (die sind [USER] und
gehören dem Menschen).
6. Frontend
@zwirn/autoimprove ersetzt @zwirn/feedback als file:-Dependency; html2canvas entfällt
ersatzlos (das neue Paket bringt bewusst keine schwere eigene Dependency mit) und damit auch sein
Eintrag in zwirn-dedupe.ts. createAutoImproveRoutes() wird ohne Argumente aufgerufen — alle sieben
optionalen Einhängepunkte des Pakets sind in src/App.tsx einzeln begründet nicht verdrahtet.
Die Nav-Sektion „Autoimprove" ist absichtlich nachgebaut statt importiert (der NavSection-Typ des
Pakets trägt drei Felder, die die produktlokale Sidebar seit 2026-08-13 nicht mehr auswertet).
Der Melde-Dialog ist produktlokal nachgebaut (tuxametrics-ui/src/domains/autoimprove/), weil
das Paket keinen exportiert — sein eigener Anlegeweg ist die Inline-Anlage in der NEU-Spalte des
Boards, also erst nach einem Seitenwechsel und ohne Erfassungskontext. Ein Rückkanal, den man suchen
muss, wird nicht benutzt; deshalb hängt der Knopf wie zuvor in shell/TopNav.tsx auf jeder Seite.
Das ist keine Erfindung dieses Repos: DOT stand vor derselben Lücke und hat sie identisch geschlossen
(dot-platform-ui/src/domains/autoimprove/features/report/, ADR-0071 — „Melde-Dialog lokal
nachgebaut"). Der Backend-Vertrag trägt type und den Erfassungskontext (pageUrl, userAgent,
screenWidth/screenHeight) längst; nur das geteilte TS-Modell im Paket noch nicht — daher ein
lokal erweiterter Payload-Typ (model/report.ts) statt einer Änderung am Zwirn-Paket. Sobald das
Paket eine Melde-Komponente mitbringt, ist dieser Slice ersatzlos zu löschen; er ist eine Brücke,
kein Produktbestandteil.
Eine bewusste Abweichung von DOTs Vorlage: dort löst der Klick zuerst einen automatischen
Vollbild-Screenshot aus (html2canvas, dynamisch importiert). Hier nicht — html2canvas ist mit
@zwirn/feedback aus diesem Frontend geflogen, und es allein für eine Bequemlichkeit
zurückzuholen (samt Eintrag in zwirn-dedupe.ts) wäre der falsche Tausch. Der Melder kann ein Bild
einfügen (Strg+V) oder hochladen: derselbe Endpunkt, derselbe Anhang, ein Handgriff mehr.
7. Ein fehlendes Stück Authority, das erst hier auffiel: GET /me/capabilities
Jede handelnde Schaltfläche der Paket-Seiten (Karte anlegen, zur Analyse freigeben, abnehmen,
Umsetzung freigeben, Testobjekt/Modul anlegen) hängt an useCapabilities aus @zwirn/web — und das
ruft GET /api/platform/v1/me/capabilities?tenantKey=…. Diesen Endpunkt hatte
tuxametrics-platform-service nicht. Er war als Lücke bekannt und in nav/navConfig.ts sogar
als Grund vermerkt, warum die Sidebar kein requiredCapability auswertet — solange nur eigener Code
betroffen war, war das eine vertretbare Auslassung.
Mit einem Fremd-Frontend ist sie keine mehr: der Hook wäre nicht fehlgeschlagen, sondern hätte eine
leere Rechtemenge geliefert. Ergebnis: ein Board, auf dem ein vollständig berechtigter Mensch
nichts tun kann und nicht erfährt, warum — also genau der stille Stillstand, den dieses ADR an
ADR-0006 kritisiert. Deshalb ist web/MeResource Teil dieser Entscheidung und nicht eines späteren
Tickets.
Zuschnitt bewusst eng, und die Begründung ist dieselbe wie beim Check-Endpunkt: ungegated, weil
ein Recht darauf, seine eigenen Rechte zu lesen, zirkulär wäre — und weil es nichts zu schützen gibt.
Der Actor kommt aus dem Token, nie aus einem Parameter; ein fremder Actor ist über diesen Pfad
nicht adressierbar. profiles ist dauerhaft leer (dieses Produkt kennt nur Einzel-Grants, ADR-0005),
steht aber im Wire-Vertrag und wird deshalb geliefert.
8. Scope-Grenze: Infrastruktur, kein Betrieb
Es entsteht kein Betreiber der agentischen Rolle. Kein Scheduler, kein Cron, kein Bot, nichts,
das ein Agenten-Token holt und _claim/_complete aufruft. Die Identität, das Token und die Grants
existieren, damit die Rolle bedient werden kann — ob und wodurch sie je bedient wird, ist
eine eigene, spätere Entscheidung und braucht ein eigenes ADR. Das steht so ausdrücklich in der
Beschreibung des Keycloak-Clients, im Javadoc von AgentIdentitySeeder und im Log-Satz beim Seed,
damit es nicht nur hier steht.
9. Keine der fünf Leitplanken ist berührt
Einzeln geprüft:
GM-7: keine klinische Entscheidung berührt. Die drei geschützten Capabilities (txm.empfehlung.decide,txm.uebersteuerung.create,txm.freigabe.grant) bleiben unverändert[USER]und sind an einenAGENTnicht grantbar —util/Gm7Schutzist nicht angefasst, seine Tests laufen unverändert. Dass dieses Produkt jetzt zum ersten Mal überhaupt einen AGENT-Actor besitzt, macht das relevanter, nicht laxer: der Agent arbeitet an Bugs und Ideen über das Produkt, nie an einem Patientenfall.- Die vier unbequemen Elemente: kein Fachdatenmodell angefasst.
E2: kommt nicht vor.- Medikamenten-Interaktionen: nicht berührt.
- Die zwei externen Fragen:
UNGEKLAERT,RegulatorikGuardunverändert.
Keine einzige txm.*-Capability ist angefasst — weder Katalog noch Grants noch
CapabilityRegistrarTest. Es entsteht keine Fachzahl und kein Enum-Wert der Domäne. Die Leitregel
„Korrigiert wird, was v1.2 widerlegt" ist nicht berührt: das ist Infrastruktur um den Ablauf herum,
wie schon Audit und Cockpit.
Konsequenzen
Was besser wird
- Der Rückkanal hat ein Zustandsmodell mit sieben Stufen statt drei, mit gestaffelten menschlichen Freigaben — und er ist kanonisch: gepflegt in Zwirn, nicht in diesem Repo.
- Ein Eigenbau-Modul (BPMN, Task-API, Nachzug-Job, Engine-DB, eigener Keycloak-Client) entfällt
vollständig. Was ADR-0006 an
GG-ARCH-PROCESS-0001-Pflichten erfüllen musste, stellt sich nicht mehr — insbesondere der Registry-Eintraghub.process-services, der in diesem Produkt gar kein Ziel hatte. tuxametrics-virtual-identity-servicehat zum ersten Mal einen belegten Zweck: bis 2026-08-13 stand invid_identityauf der Runtime-VM null Zeilen. Die agentische Identität ist der erste Datensatz, für den deridentityKeytatsächlich deractorKeyist — bei Menschen ist diese Kopplung weiterhin nur Konvention (sieheimplementation-gaps.md).
Was es kostet
| ADR-0006-Stand | jetzt | |
|---|---|---|
| Maven-Module | 5 | 4 |
| Container im Stack | 21 | 19 |
| Postgres-Instanzen | 9 | 8 |
| Spring-Boot-Ausnahmen von Zwirn-ADR-0007 | 2 | 1 |
| Keycloak-Clients | 3 (ui, services, feedback-process) |
4 (ui, services, autoimprove-service, autoimprove-agent) |
Bootstrap-Grants auf platform.capability.create |
1 | 2 |
| Melde-Button auf jeder Seite | ja | ja (produktlokal statt aus dem Paket, §6) |
| Selbstauskunft „was darf ich hier?" | nein | ja (GET /me/capabilities, §7) |
Was offen bleibt
| Punkt | Warum |
|---|---|
| Der Melde-Dialog ist produktlokaler Code | Er gehört ins kanonische Paket; dort steht er (Stand 2026-08-14) nicht. Bis dahin trägt dieses Repo ihn — wie DOT seinen, und mit demselben Auftrag, ihn bei Gelegenheit wieder loszuwerden. Ein zweiter Ort, an dem sich der Payload-Vertrag von AutoImproveItemCreate ändern kann |
| Kein Betreiber der agentischen Rolle | Ausdrückliche Scope-Grenze, §8. Die Karten laufen bis BEREIT_ZUR_ANALYSE und bleiben dort, solange niemand claimt. Das ist ein sichtbarer Stillstand (die Karte steht in einer Spalte), nicht der stille von ADR-0006 |
| Keine Migration der Feedback-Daten | Bewusst nicht gebaut, §2. Folgenlos nur, solange keine echte Meldung existiert — vor einem Pilotbetrieb neu zu bewerten |
| Grants hängen am Demo-Seed | Sowohl die 14 menschlichen als auch die 3 agentischen Grants legt boot/CapabilityRegistrar.seedDemoGrants() an, also nur bei TXM_DEMO_GRANTS=true und nur für den Demo-Mandanten — dieselbe Konstruktion wie beim Testmanagement-Service. Für eine echte Praxis von Hand zu vergeben |
Kein agent-module-scope gesetzt |
Der Service kann einen Agenten auf bestimmte Module beschränken (Blast-Radius-Kontrolle). Die Rechte dafür sind gegrantet, ein Scope ist nicht angelegt — der eine AGENT-Actor darf damit jede Karte claimen. Sobald es einen Betreiber gibt, ist das zuerst zu entscheiden |
IdentityStatusGuard weiterhin nicht verdrahtet |
Eine über _deactivate deaktivierte agentische Identität verliert ihre Grants nicht automatisch. Bekannter, in implementation-gaps.md geführter Punkt — er wird durch den ersten echten AGENT-Actor von theoretisch zu konkret |
| Die 16 Capability-Schlüssel sind in Zwirn noch nicht committet | Der wichtigste offene Punkt, und er liegt außerhalb dieses Repos. Die Grant-Listen oben zielen auf den Stand im Arbeitsbaum von C:\dev\zwirn (16 Schlüssel, phasengetrennt in .analyze/.implement, geprüft am 2026-08-14). Zwirns committeter HEAD registriert dagegen nur 9 und kennt statt der beiden noch die alte, ungeteilte autoimprove.item.claim. Wer das Docker-Image aus Zwirns HEAD statt aus dessen Arbeitsbaum baut, bekommt einen Service, in dem die zwei AGENT-Grants dieses Repos ins Leere zeigen und .claim nicht gegrantet ist — der Agent könnte dann nichts claimen, und zwar ohne erkennbaren Zusammenhang. Der Bau-Befehl in .env.example baut aus dem Arbeitsbaum und ist damit heute korrekt; die Abhängigkeit bleibt trotzdem eine, die jemand in Zwirn schließen muss |
Nachtrag (2026-08-14, am selben Tag): der Modul-Katalog wird einmalig befüllt
Status bleibt Angenommen. Keine Entscheidung dieses ADR ändert sich. Ergänzt wird ein Punkt, den es hier noch gar nicht gab.
Der Anlass
Nach dem Deploy die Frage am laufenden Board: „wo sind die Module?" — im Vergleich mit DOT, das
einen gefüllten Modul-Katalog zeigt. Nachgesehen (GET .../modules/search, gegengeprüft per
Live-Query gegen tuxametrics-autoimprove-db): der Katalog des Mandanten praxis01 enthielt null
Zeilen. Ebenso test_object, autoimprove_item und autoimprove_agent_module_scope — die Instanz
war unbenutzt, nicht defekt.
Das ist kein Fehler des Deploys und keiner des Service. jpa/ModuleEntity sagt in seinem Javadoc
ausdrücklich, der Katalog sei „bewusst kein hartkodiertes, aus service-landscape.md generiertes
Verzeichnis" und wachse „durch Nutzung". Für einen produktneutralen Service ist das richtig — er
kennt kein bestimmtes Produkt-Repo. Für ein frisch adoptierendes Produkt ist es trotzdem ein
Fehlstand: solange der Katalog leer ist, gibt es beim Melden nichts auszuwählen, und keine
Meldung kann je einem Bereich zugeordnet werden. DOT hat exakt dieselbe Beobachtung am 2026-08-12
gemacht („Dazu sehe ich keine Module" — genau ein Eintrag, kein einziges echtes
Bounded-Context-Modul) und sie ebenso durch eine einmalige Erstbefüllung gelöst.
Die Entscheidung
Der Startbestand wird einmalig angelegt, aus genau einer Quelle:
guidelines/project/service-landscape.md. Neun Einträge, jeder
ein real gebauter oder real betriebener Baustein dieses Produkts. Danach wächst der Katalog wieder
durch Nutzung.
| Modul | Warum es eines ist |
|---|---|
tuxametrics-laborauswertung-service |
Der Gold Path — der fachliche Kern |
tuxametrics-platform-service |
Die Capability-Authority |
tuxametrics-virtual-identity-service |
Identity je Praxis |
tuxametrics-keycloak |
Der IdP — siehe unten, das ist der einzige Grenzfall |
tuxametrics-ui |
Das Frontend |
tuxametrics-process-hub-cockpit |
Das Prozess-Cockpit |
zwirn-audit-service |
Betrieben, nicht importiert — eigener Bereich mit eigenen Fehlerbildern |
zwirn-testmanagement-service |
dito |
zwirn-autoimprove-service |
Der Rückkanal selbst |
Warum tuxametrics-keycloak ein eigenes Modul ist und nicht Teil der Authority. Es ist der
einzige Eintrag, der kein Maven-Modul und kein Fremd-Image mit Fachlogik ist, sondern
Konfiguration. Ihn in tuxametrics-platform-service einzufalten wäre bequem und würde genau die
Trennung einebnen, auf der dieses Produkt an mehreren Stellen besteht: wer bist du beantwortet
Keycloak, was darfst du die Authority (GG-GOV-SECURITY-0003). Eine Meldung „Anmeldung geht
nicht" gehört nicht in dasselbe Fach wie „mir fehlt ein Recht".
Warum der Autoimprove-Service selbst dabei ist. Er ist eine eigene Domäne, nicht Teil des Produkts, das er triagiert — dieselbe Begründung, mit der DOT ihm ein eigenes Modul gegeben hat (dot-ADR-0082). Ein Bug im Rückkanal soll nicht im Fach des Gold Path landen.
Bewusst nicht angelegt: die acht Postgres-Instanzen, Redis und OPA. Das ist Infrastruktur unter den Bausteinen, kein Bounded Context — ein Modul „tuxametrics-db" hätte keinen Adressaten.
Drei Felder bleiben leer, und das ist die eigentliche Entscheidung
ModuleCreate trägt nur name und description. ownerActorKey, maturity und docPath setzt
ausschließlich PUT .../{moduleKey}. Alle drei bleiben hier leer, und zwar nicht aus Bequemlichkeit:
ownerActorKey— dieses Produkt hat genau einen menschlichen Actor, der alle 14 menschlichen Capabilities hält. Ihn zum Owner aller neun Module zu machen wäre wahr und informationslos. Dazu kommt, dasssrc/App.tsxrenderActorundsearchIdentitieseinzeln begründet nicht verdrahtet: ein Owner würde als roher Actor-Key gerendert, und einen Owner-Picker gibt es gar nicht. Die Wirkung des Feldes ist ohnehin nur weiche Adressierung einerDOMAIN-Freigabe — jeder Halter der Capability darf freigeben, der Owner ist nur die erwartete Person. Nachtragbar perPUT, sobald es mehr als einen Menschen gibt.maturity— „nicht eingestuft" ist lautModuleMaturity-Javadoc ein eigener, ehrlicher Zustand. Eine Einstufung je Modul wäre hier erfunden; dass das Produkt ein Prototyp ist, steht bereits in CLAUDE.md und macht keine Aussage über einzelne Module.docPath— löst das Frontend überresolveDocUrlzu einer Hosting-URL auf, und dieser Einhängepunkt ist (mit Begründung: dieses Repo liegt in keinem öffentlichen Git-Hosting) nicht verdrahtet. Ein Pfad wäre tote Daten.
Damit stehen in der Tabelle exakt die Spalten, die ModuleService.create auch schreiben würde.
Keine Karte bekommt einen moduleKey — Reihenfolge, nicht Vergesslichkeit
Der Modul-Katalog ist reine Stammdaten und löst nichts aus. Der moduleKey an einer Karte
dagegen aktiviert die Scope-Prüfung (AutoImproveItemService#requireModuleScope): eine getaggte
Karte darf nur ein Agent claimen, für den ein passender AgentModuleScope existiert — fail-closed.
Dieses Repo hat, wie in „Was offen bleibt" oben festgehalten, keinen einzigen Scope-Eintrag.
Eine Karte hier jetzt zu taggen hieße also, sie dauerhaft unclaimbar zu machen, sobald die agentische Rolle je bedient wird — und zwar unsichtbar, weil es erst beim ersten Claim-Versuch auffällt und dann wie ein Rechteproblem aussieht. Reihenfolge: Katalog → Scopes → taggen. Die ersten beiden Schritte gehören zusammen mit der Entscheidung, ob es einen Betreiber gibt (§8) — nicht hierher.
Der Weg: SQL statt REST, und warum das eine benennenswerte Abweichung ist
Der vorgesehene Weg ist POST /api/autoimprove/v1/tenants/praxis01/modules. Er ist mit
autoimprove.module.create gegatet, und den Grant hält genau ein Actor: der menschliche Nutzer.
Ein Token dafür entsteht ausschließlich im Browser — alle vier Clients des Realms haben
directAccessGrantsEnabled: false, ein Password-Grant ist konstruktiv ausgeschlossen (und das ist
Absicht, siehe scripts/erste-grants.py). Die beiden Client-Credentials-Actors halten den Grant
nicht.
Die Alternative wäre gewesen, einem Service-Account den Grant zu geben und ihn danach wieder zu
entziehen — also eine Rechteänderung an laufender Infrastruktur für eine einmalige Datenanlage.
Das ist der größere Eingriff. Gewählt wurde stattdessen ein additives, idempotentes und reversibles
SQL-Skript (scripts/autoimprove-modul-katalog.sql),
das genau die Spalten schreibt, die ModuleService.create schreiben würde, mit
created_by = 'manual-bootstrap' — demselben Marker, den scripts/erste-grants-bootstrap.sql für
„diese Zeile kam nicht durch den Service-Layer" benutzt.
Das bleibt eine Abweichung und keine neue Regel. Sie umgeht CapabilityGuard. Sie ist hier
vertretbar, weil sie keinem Actor irgendein Recht verschafft und kein Gate dauerhaft schwächt —
sobald ein Browser-Token zur Hand ist, ist der REST-Weg der richtige, auch für Nachträge.
Die Schlüssel sind zufällig erzeugt ({tenant}-aim-mod-{nanoid8}, dasselbe Alphabet wie
util/NanoId), nicht sprechend vergeben. DOT hat an genau dieser Stelle einen sprechenden Key
vergeben und ihn am 2026-08-13 samt 7 Karten und 21 Testobjekten zurückbauen müssen.
Was dabei in Zwirn aufgefallen ist
Der Fehlstand ist kein tuxametrics-Problem, sondern eine Dokumentationslücke im geteilten Repo:
zu zwirn-autoimprove-service gab es keine Adoptionsanleitung, Module/AgentModuleScope kamen in
guidelines/ als Konzepte nicht vor, und die Capability-Liste im README des Service war seit
2026-08-06 falsch (sie nannte 6 Schlüssel und die längst entfallene autoimprove.item.claim statt
der aktuellen 16). Beide bisherigen Adoptionen sind über dieselben Stellen gestolpert. Nachgezogen im
Zwirn-Arbeitsbaum, unkommittiert zur Durchsicht — siehe
agentic-engineering/agent-log/2026-08-14.md.
Keine der fünf Leitplanken ist berührt
Es entsteht kein Fachdatensatz, kein Enum-Wert der Domäne und keine Fachzahl. Keine txm.*-Capability
ist angefasst, Gm7Schutz und RegulatorikGuard sind unberührt. Neun Zeilen Stammdaten in der DB
eines betriebenen Fremd-Service, die benennen, welche Bausteine dieses Produkt hat — das steht
wörtlich so bereits in service-landscape.md.
Verweise
- ADR-0006 — abgelöst; enthält die Fehlannahme, die hier korrigiert wird
- ADR-0005 — die Authority und die dort vorgesehenen „agentischen Identitäten", die hier zum ersten Mal eine bekommen
C:\dev\dl-controlling\dot\guidelines\adr\0071-autoimprove-service-adoption.md— der reale Präzedenzfall (2026-08-03): dieselbe Adoption, dieselbe Löschung des Eigenbau-Moduls, derselbe dedizierte Keycloak-Client, derselbe lokal nachgebaute Melde-Dialog. Nicht zu verwechseln mitC:\dev\dot\dot— ein totes Checkout, in dem nichts davon stehtC:\dev\zwirn\feedback\autoimprove-service\README.md— der kanonische Service; die Capability-Liste steht in dessencapability/CapabilityRegistrarC:\dev\zwirn\feedback\autoimprove-service\ADOPTION.md— neu am 2026-08-14 (aus diesem Nachtrag entstanden): was ein Produkt bereitstellen muss, bevor der Service bei ihm läuft. Die Reihenfolge Katalog → Scopes → taggen steht dort als Schritt 5C:\dev\zwirn\guidelines\governance\security\agentic-authorization.md(GG-GOV-SECURITY-0004) — seit 2026-08-14 mit beiden Mustern für eine AGENT-Identität; das statische, das dieses Repo gewählt hat, ist dort als Muster B belegtC:\dev\zwirn\feedback\autoimprove-web\README.md— das Frontend-Paketaudit-feedback-processhub-integration.md— die Entscheidung von 2026-08-09 („erst Feedback sammeln, dann entscheiden"), die hier beantwortet wirdkeycloak/README.md— der AGENT-Client und dieuser_key-Konvention