Brainversum · tuxametrics Graph Admin

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 von zwirn-autoimprove-service statt 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 kannob 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:

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

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:

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