Files
Deploymentcenter BotandClaude Opus 5 8274a6c0ac feat(2.8.0): Pruefen duerfen, ohne beheben zu duerfen
Der Unterschied zwischen "ich habe es behoben" und "der Fix eines anderen
traegt" ist das Rueckgrat des Pruefverfahrens aus 2.6.1. Er stand bisher nur in
der Anleitung - im Datenbestand und in den Rechten kam beides gleich an.

RECHTE

bugtracker:manage war ein einziger Schreib-Scope: Wer eine Nachpruefung
bestaetigen durfte, durfte auch resolve, update, bulk_update und die Roadmap
umschreiben. Fuer einen Pruefstand ist das zu viel - und wer einen Fix schreibt
und ihn selbst abnimmt, prueft gegen die Erwartung, die er beim Schreiben hatte.

bugtracker:verify deckt jetzt ab, was ein Pruefer braucht: next, claim, release,
comment, confirm, report. Nicht mehr. manage schliesst es ein (IMPLIED_SCOPES),
vorhandene Token verlieren nichts.

"status" bleibt bewusst bei manage. Darueber liesse sich sonst jeder Zustand
setzen, auch "resolved" - der Umweg um genau diese Trennung.

DATENBESTAND

Eine bestaetigte Nachpruefung war nicht von "jemand hat das Item zugemacht" zu
unterscheiden: dc-testkit confirm setzte status=closed und schrieb den Rest in
einen Kommentar. Damit war die eine Frage, fuer die das Verfahren gebaut ist,
nicht beantwortbar - wurde dieses Item je gegen ein ausgeliefertes Paket
geprueft? Ein Kommentartext laesst sich nicht zaehlen und nicht filtern.

Migration 017 legt verified_at, verified_in_build und verified_by an. Kein
eigener Status daneben: Ein bestaetigtes Item IST geschlossen, die Bestaetigung
ist eine Eigenschaft dieses Abschlusses. Ein zweiter Wert waere dieselbe Falle,
die 016 beim is_roadmap-Flag vermieden hat.

verified_in_build steht neben resolved_in_build, weil beide verschiedene Fragen
beantworten und auseinandergehen koennen - genau das ist der interessante Fall.
Das eine ist die Behauptung des Behebenden, das andere die Beobachtung des
Pruefenden.

confirm laesst sich nur auf Items anwenden, die jemand als behoben ausgegeben
hat (resolved, awaiting_verification, in_verification, fix_insufficient). Ein
offenes Item zu "bestaetigen" waere ein Schliessen mit schmueckendem Beiwort -
und stuende hinterher als geprueft in einer Statistik, die genau das misst.
Antwort: 409 not_verifiable.

MCP

dc_confirm. Der Adapter hatte dc_next_task und dc_resolve, aber keinen Weg, eine
Nachpruefung abzuschliessen; dc_comment kennt nur action_taken, keinen Status.
Ein Pruefagent ueber MCP konnte einen Auftrag uebernehmen und dann nichts damit
anfangen. 16 Werkzeuge statt 15.

TESTKIT

dc-testkit confirm nutzt die Aktion und schickt die gepruefte Fassung als Feld.
Antwortet der Server mit 404, faellt es auf den alten Weg zurueck und sagt, dass
die Fassung dann nur im Kommentar landet.

Drei neue e2e-Faelle: dass confirm die gepruefte Fassung festhaelt, dass es nur
dort geht, wo es etwas zu bestaetigen gibt, und dass ein verify-Token
kommentieren, aber nicht beheben darf. 147 bestanden, 0 uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 10:59:57 +02:00

56 KiB
Raw Permalink Blame History

Linux-Testumgebung — Anleitung für Agenten

Stand: 31.08.2026 · Gilt projektübergreifend für alle Agenten und Entwickler, die auf der geteilten Test-VM arbeiten.

Diese Datei ist die maßgebliche Fassung. Sie liegt im Deploymentcenter-Repo unter docs/TESTUMGEBUNG.md. Die Kopie auf der Maschine (~/TESTUMGEBUNG.md) und die Verweise in den Projektrepos zeigen hierher. Wer etwas an der Umgebung ändert, ändert es hier und kopiert danach auf die VM — nicht umgekehrt.

Das ist zweimal unterblieben, zuletzt am 31.08.2026 bemerkt: Auf der Maschine lag noch der Stand vom 26.08. — ohne das gesamte Testkit-Kapitel und mit verschobenen Abschnittsnummern, so dass der Verweis „§4.6, Fremdmeldungen" dort bei „Erhöhte Rechte" landete. Wer die Datei hier ändert, kopiert sie im selben Arbeitsgang:

scp docs/TESTUMGEBUNG.md testvm-<projekt>:~/TESTUMGEBUNG.md

Bewusst nicht unter public/: Diese Datei nennt eine interne Adresse, einen Benutzernamen und den Weg, sudo zeitweise freizuschalten. /docs/ auf dem Deploymentcenter ist öffentlich erreichbar; dort gehört das nicht hin. Das Testkit-Skript dagegen schon — es enthält nichts davon.


1. Was diese Maschine ist

Eine Ubuntu-Desktop-VM zum Testen — nicht für Produktivbetrieb, nicht für echte Zugangsdaten, nicht für Geld bewegende Läufe.

System Ubuntu 26.04 LTS, Kernel 7.0, x86_64
Ausstattung 31 GB RAM, 5 CPUs, ~33 GB freier Plattenplatz
Erreichbar 192.168.40.48 über einen WireGuard-Tunnel
Benutzer richard
Besonderheit Desktop-Umgebung vorhanden — grafische Anwendungen sind testbar, nicht nur Dienste

Warum eine Desktop-VM? Weil sich damit beides prüfen lässt: der kopflose Serverbetrieb (systemd) und die grafische Oberfläche auf der Zielplattform.


2. Zugang einrichten

Der Zugang läuft über SSH-Schlüssel. Passwort-Anmeldung wird nicht verwendet — Agenten geben grundsätzlich keine Passwörter ein. Diese Regel gilt auch dort, wo es bequemer wäre: Der update-agent fragt bei --action install nach Zugangsdaten; genau deshalb nimmt das Testkit diesen Weg nicht (siehe §4.7).

2.1 Schlüssel erzeugen (auf dem eigenen Rechner)

ssh-keygen -t ed25519 -f ~/.ssh/testvm_<projekt> -N "" -C "<projekt>@testvm"

Ein eigener Schlüssel je Projekt — dann lässt sich einzelnen Projekten der Zugang entziehen, ohne alle anderen auszusperren.

2.2 Öffentlichen Schlüssel hinterlegen

Der Inhalt von ~/.ssh/testvm_<projekt>.pub wird auf der VM in ~/.ssh/authorized_keys eingetragen. Das muss ein Mensch tun (einmalig, mit Passwort). Direkt auf der VM:

mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '<INHALT DER .pub-DATEI>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

2.3 SSH-Konfiguration (auf dem eigenen Rechner)

Host testvm-<projekt>
    HostName 192.168.40.48
    User richard
    IdentityFile ~/.ssh/testvm_<projekt>
    IdentitiesOnly yes
    ServerAliveInterval 30
    ServerAliveCountMax 4

Der Aliasname trägt das Projekt. Ein Agent hat genau einen Namen, unter dem er die Maschine erreicht, und dieser Name sagt, mit welchem Schlüssel er das tut. Ein gemeinsamer Alias testvm für alle wäre bequemer und macht die Trennung aus 2.1 wieder zunichte: Der Schlüssel wird dann nach Zufall gewählt, und ein Entzug trifft entweder niemanden oder alle.

⚠️ Kein ControlMaster/ControlPath eintragen, wenn der Client unter Windows läuft — Windows-OpenSSH unterstützt Verbindungs-Multiplexing nicht und scheitert mit „Failed to connect to new control master". Stattdessen mehrere Prüfschritte pro Aufruf bündeln.

Test: ssh testvm-<projekt> "hostname"

Bestand (31.08.2026): Auf dem Arbeitsplatz existiert bisher nur polytrader-vm mit ~/.ssh/polytrader_vm — ein Schlüssel, den alle Projekte mitbenutzen. Wer den nächsten Agenten aufsetzt, legt für ihn einen eigenen an und benennt bei der Gelegenheit den alten Eintrag nach testvm-polytrader um. Bis dahin schlagen die Beispiele in dieser Datei wörtlich fehl.


3. Was installiert ist

Werkzeug Version Anmerkung
.NET SDK 10.0.111 (Runtime 10.0.11) SDK, nicht nur Runtime — auf der VM kann gebaut werden
MariaDB 11.8.6 Dienst mariadb, lauscht auf localhost:3306
Git 2.53.0
curl, python3 vorhanden Voraussetzung für das Testkit (§4)
Xvfb vorhanden Virtueller Framebuffer für GUI-Tests ohne Bildschirm (siehe 5.7)
xdotool vorhanden Klicks und Tastatureingaben skripten
ImageMagick vorhanden import für Bildschirmaufnahmen, convert zum Zuschneiden
JetBrains Rider vorhanden
Docker nicht installiert
Node.js nicht installiert
jq vorhanden (nachträglich) Das Testkit benutzt es nicht — es liest JSON mit python3, damit es auch auf einer Maschine ohne jq läuft

Nicht vorhandene Werkzeuge dürfen installiert werden — aber bitte in der Belegungsliste (Abschnitt 7) vermerken, damit andere wissen, was sich geändert hat.


4. Das Testkit — vorhandene Infrastruktur statt Eigenbau

Alles, was ein Testlauf braucht, gibt es im Deploymentcenter schon: Lizenzschlüssel, Release-Ablage, Update-Agent, Bugtracker. Was fehlte, war der Weg dorthin ohne einen Menschen, der im WebUI etwas anklickt.

dc-testkit ist dieser Weg. Ein Skript, ein Aufruf je Schritt, für alle Projekte dasselbe.

Warum überhaupt ein Skript und nicht „der Agent ruft die API direkt auf"? Weil sonst jeder Agent seine eigene Fassung der immer gleichen fünf curl-Aufrufe baut — jede mit anderen Vorgaben, anderer Fehlerbehandlung und anderem Umgang mit dem Token. Die Unterschiede fallen niemandem auf, solange nichts schiefgeht. Und wenn etwas schiefgeht, ist unklar, ob die Anwendung oder der Aufruf schuld war. Ein Weg, den alle nehmen, ist der einzige, dessen Fehler man findet.

4.1 Einmal einrichten

Der Agent läuft nicht auf der VM, sondern auf dem Arbeitsplatz. Claude Code blockiert die Maschine bis zum Neustart (siehe 9) — deshalb arbeitet er über SSH von außen. Daraus folgt die Aufteilung:

Was Wo Warum dort
dc-testkit auf der VM, ~/bin/dc-testkit Dort läuft der Test: Sandkasten, Update-Agent, Display
dc-testkit-remote auf dem Arbeitsplatz Dort sitzt der Agent
Konfiguration auf dem Arbeitsplatz, ~/.config/dc-testkit/<projekt> Dort liegen auch die Projekt-Repos — und damit die Token (4.2)
Werkbank-Token auf dem Arbeitsplatz, in der .mcp.json des Repos Es muss nirgends sonst hin

Auf der VM liegt keine Konfiguration. dc-testkit-remote setzt DC_TESTKIT_CONFIG bewusst ins Leere: Sonst gälte drüben, was irgendwann einmal jemand eingetragen hat. Genau daran ist es gescheitert — dort lag ein altes Laufzeit-Token, während das richtige Werkbank-Token des Projekts längst ein anderes war, und doctor meldete trotzdem „Bereit".

Auf der VM (einmal je Maschine):

mkdir -p ~/bin
curl -fsSL https://dc.mhdf.de/testkit/dc-testkit.sh -o ~/bin/dc-testkit
chmod +x ~/bin/dc-testkit

Auf dem Arbeitsplatz (einmal je Arbeitsplatz):

mkdir -p ~/bin
curl -fsSL https://dc.mhdf.de/testkit/dc-testkit-remote.sh -o ~/bin/dc-testkit-remote
chmod +x ~/bin/dc-testkit-remote

Danach läuft jeder Aufruf über den Wrapper — mit dem Projekt-Slug als erstem Argument:

dc-testkit-remote polytrader doctor
dc-testkit-remote polytrader run
dc-testkit-remote polytrader --skript pruefplan.sh

Warum ein Wrapper und nicht ssh von Hand? Aus demselben Grund, aus dem es dc-testkit überhaupt gibt: Sonst baut jeder Agent seine eigene Fassung derselben SSH-Zeile. Vier Dinge gehen dabei zuverlässig schief:

  1. Das Token stünde in ps aux. ssh vm 'DC_TOKEN=xyz dc-testkit run' legt es offen — der Befehlsstring ist die Befehlszeile der entfernten Shell, und auf einer geteilten Maschine liest jeder andere Benutzer fremde Befehlszeilen. Der Wrapper schickt es über die Standardeingabe; von dort geht es direkt in die Umgebung, und die liest nur, wem der Prozess gehört.
  2. ~/bin liegt über SSH nicht im PATH. ~/.profile nimmt es auf, wird aber nur von einer Login-Shell gelesen — und ssh vm 'befehl' startet keine. Die Antwort wäre Kommando nicht gefunden: sieht aus wie ein fehlgeschlagener Bezug und ist keiner. Der Wrapper ruft bash -l.
  3. Quoting durch drei Ebenen. Ein --title "Update bricht ab" muss durch lokale Shell, SSH und entfernte Shell. Ohne Sorgfalt kommt der Titel dort zerlegt an und die Meldung trägt den halben Text. Der Wrapper schickt den Befehl über die Standardeingabe, nicht durch das Quoting.
  4. Kein Verbindungs-Multiplexing unter Windows. Jeder Aufruf kostet einen vollen Aufbau durch den Tunnel; --skript bündelt einen ganzen Prüfplan in einen.

⚠️ Was dc-testkit-remote nicht kann: alles, was einen Bildschirminhalt zurückbringt. Aufnahmen entstehen mit shot auf der VM unter /tmp/<projekt>_<name>.png und werden danach abgeholt — dafür bleibt scp der Weg (5.8).

⚠️ Beides — dieser Download und dc-testkit license — setzt Deploymentcenter 2.6.1 voraus. Ist die Fassung auf dem Server älter, fehlt der Endpunkt /api/license/v1/dev-key. Er fällt dabei nicht mit 404 auf: Die Lizenz-API antwortet bei unbekannten Pfadsegmenten mit ihrer Verfügbarkeitsprüfung, also mit HTTP 200. Das Testkit erkennt das und sagt es; wer von Hand curlt, sucht sonst lange. Seit 2.7.1 vergleicht dc-testkit doctor die Fassung von sich aus, statt sie nur anzuzeigen.

Der Download unter /testkit/ funktionierte bis 2.7.1 gar nicht — und zwar unabhängig von der Fassung: Im Webroot lag die Datei nur unter /public/testkit/, die Route auf /testkit/ fehlte in der .htaccess. Wer vor 2.7.1 ein 404 sah, hat deshalb nicht unbedingt einen zu alten Server vor sich. Ausweichweg für alte Stände: scp public/testkit/dc-testkit.sh testvm-<projekt>:~/bin/dc-testkit — und für den Arbeitsplatz cp public/testkit/dc-testkit-remote.sh ~/bin/dc-testkit-remote.

Dann je Projekt eine Konfiguration. Der bessere Weg ist DC_PROJECT_DIR — dann steht das Token nur an einer Stelle, in der .mcp.json des Projekts (4.2):

mkdir -p ~/.config/dc-testkit
cat > ~/.config/dc-testkit/polytrader <<'EOF'
DC_REMOTE=testvm-polytrader
DC_PROJECT=polytrader
DC_PROJECT_DIR=J:/Softwareprojekte/PolytraderSharp/PolyTraderSharp
DC_CHANNEL=prod
DC_DISPLAY=:99
EOF
chmod 600 ~/.config/dc-testkit/polytrader

DC_PROJECT_DIR ist der Pfad auf dem Arbeitsplatz — dort liegt das Repo mit der .mcp.json. Was in den Projektverzeichnissen auf der VM liegt, spielt keine Rolle; der Agent arbeitet nie von dort aus.

Wo es kein Projekt-Repo gibt, kommt das Token in die Datei — nie in die Befehlszeile, sonst steht es in der Shell-History:

mkdir -p ~/.config/dc-testkit
cat > ~/.config/dc-testkit/config <<'EOF'
DC_URL=https://dc.mhdf.de
DC_TOKEN=<Werkbank-Token des Projekts>
DC_PROJECT=<slug>
DC_CHANNEL=prod
EOF
chmod 600 ~/.config/dc-testkit/config

Arbeitet ein Agent an mehreren Projekten, hält er je Projekt eine eigene Datei und wählt sie über DC_TESTKIT_CONFIG=~/.config/dc-testkit/polytrader dc-testkit ….

Ein Testagent gehört zu genau einem Projekt (siehe 4.8). Bei ihm steht der Pfad fest in seinem Prompt und nicht in seinem Gedächtnis — wer die Datei vergisst, prüft stillschweigend das falsche Projekt und meldet dorthin.

Was in der Datei stehen darf:

Schlüssel Bedeutung Vorgabe
DC_URL Instanz, gegen die gearbeitet wird https://dc.mhdf.de
DC_TOKEN Werkbank-Token des Projekts (4.2) —
DC_PROJECT_DIR Projekt-Repo; ohne DC_TOKEN wird es aus dessen .mcp.json gelesen —
DC_PROJECT Slug des Projekts —
DC_CHANNEL Release-Kanal prod
DC_INSTANCE Maschinenkennung, an der der DEV-Schlüssel hängt Kurzname des Rechners
DC_DISPLAY Virtuelles Display dieses Projekts (5.5, 5.8) — (dann keine Oberflächenprüfung)

DC_INSTANCE nur setzen, wenn mehrere Testläufe auf derselben Maschine auseinandergehalten werden müssen. Sie muss über Läufe hinweg gleich bleiben: Wechselt sie, sucht der Server jedes Mal einen anderen Schlüssel heraus, bis die Grenze von zwölf je Projekt greift.

Rangfolge: Schalter auf der Befehlszeile schlägt Umgebungsvariable schlägt Datei schlägt Vorgabe. Bis 1.1.0 war das genau verkehrt herum — die Datei überschrieb die Umgebung, und DC_PROJECT=x dc-testkit report meldete stillschweigend in das Projekt aus der Datei. Auf einer Maschine mit mehreren Projekten ist das die teuerste Sorte Fehler: Es geht nichts kaputt, es landet nur alles am falschen Ort.

Prüfen, ob alles steht:

dc-testkit doctor

doctor ist die einzige Auskunft, der zu trauen ist — aber nur, wenn er selbst aktuell ist. Er prüft deshalb seit 1.1.0 als Erstes den eigenen Stand gegen die ausgelieferte Fassung:

dc-testkit selfupdate            # auf die ausgelieferte Fassung bringen
dc-testkit selfupdate --pruefen  # nur nachsehen, nichts ändern

Warum das gebraucht wurde. Das Skript liegt auf jeder Maschine als Kopie und altert dort still. Am 31.08.2026 lag auf der Test-VM eine Fassung, die 236 Zeilen älter war als die ausgelieferte — beide meldeten 1.0.0. Deshalb sagte doctor dort „Bereit", während die aktuelle Fassung drei fehlende Rechte des Tokens angemeckert hätte. Die Fassungsnummer wird bei jeder Änderung hochgezählt; ohne das ist die Selbstprüfung wertlos. Sie sagt das auch: gleiche Nummer bei anderem Inhalt ist ein Befund, kein Achselzucken.

Ist die eigene Fassung neuer als die ausgelieferte, meldet doctor einen Entwicklungsstand und rät ausdrücklich nicht zum selfupdate — das holte die ältere Fassung zurück. selfupdate verweigert diesen Rückschritt von sich aus; --force hebt die Sperre auf.

4.2 Das Werkbank-Token

Es existiert wahrscheinlich schon. Jedes angebundene Projekt hat eines — es steht in der .mcp.json des Projekt-Repos, wo der MCP-Adapter es ohnehin braucht:

{ "mcpServers": { "deploymentcenter": { "env": { "DC_TOKEN": "dc_master_…" } } } }

Das Testkit holt es sich von dort. Dazu zeigt DC_PROJECT_DIR auf das Repo — gesucht wird im Verzeichnis selbst und eine Ebene tiefer:

DC_PROJECT_DIR=~/RiderProjects/PolyTraderSharp

Damit gibt es das Geheimnis einmal je Projekt, nicht zusätzlich in ~/.config/dc-testkit/config. Zwei Ablagen desselben Tokens heißt: Beim nächsten Wechsel wird eine davon vergessen. Genau so war es auf der Test-VM — dort lag in der Testkit-Konfiguration noch ein altes Laufzeit-Token (watchdog:ping, bugtracker:report, ohne Bindung), während das richtige Werkbank-Token des Projekts längst ein anderes war. doctor meldete trotzdem „Bereit", und die Rechte fehlten erst dort, wo sie gebraucht wurden.

.mcp.json kommt über Git nicht mit. Sie ist per .gitignore ausgeschlossen — auf einer frisch geklonten Maschine fehlt sie deshalb immer. Das ist kein Fehler im Repo und keiner in den Rechten. Das Token muss von Hand dorthin, aus der .mcp.json des Projekts auf dem Arbeitsplatz. dc-testkit sagt genau das, statt an einem 401 zu scheitern:

Fehler Kein Werkbank-Token.

In ~/RiderProjects/PolyTraderSharp liegt keine .mcp.json mit einem DC_TOKEN.

Das ist kein Fehler im Repo: .mcp.json ist per .gitignore ausgeschlossen
und wird ueber Git NICHT mitgeliefert.

Ein Platzhalter ("${DC_TOKEN}") zählt dabei nicht als Token — sonst liefe der Aufruf mit einer Zeichenkette los, die der Server nur mit 401 beantworten kann.

Wenn wirklich keines existiert, legt ein Mensch es an — im WebUI unter Token-Verwaltung → ➕ Token erstellen:

Feld Wert
Typ Master
Besitzerart dev_agent
Projekt der Slug des Projekts — nicht leer
Rechte bugtracker:manage, license:dev, updateservice:read

bugtracker:manage schließt Lesen, Melden und Prüfen ein, license:dev schließt updateservice:read ein — zwei Häkchen genügen also.

Für einen reinen Prüfagenten (4.8) reicht weniger, und weniger ist hier besser: bugtracker:verify statt manage, dazu license:dev. Dann kann dieses Token melden, übernehmen, kommentieren und bestätigen — aber nichts als behoben ausgeben und die Roadmap nicht anfassen.

Warum die Projektbindung nicht optional ist

Ein Token je Projekt, gebunden an dieses Projekt. Ein „allgemeines Token für die ganze VM" klingt praktischer und ist es nicht — aus zwei Gründen, die in verschiedene Richtungen wirken:

Es reicht nicht weit genug. Ein Testlauf prüft die Auslieferung eines Produkts: dessen Release aus der Ablage, dessen Lizenzschlüssel, dessen Update. Beim Ausstellen des DEV-Schlüssels prüft der Server die Bindung ausdrücklich und lässt hier keine Ausnahme für Werkbank-Token zu. Ein an deploymentcenter gebundenes Token bekommt für polytrader ein 403 project_forbidden — ein Lizenzschlüssel öffnet die Release-Ablage eines Produkts, und das ist etwas anderes als eine Meldung.

Und es reicht zu weit. Ein Token ohne Bindung ist nicht etwa eingeschränkt, sondern uneingeschränkt: Der Server leitet daraus kein Heimatprojekt ab, also ist für ihn jede Meldung eine Heimmeldung. Damit fällt der gesamte Schutz für Fremdmeldungen weg — keine Begründungspflicht, keine Deckelung auf medium, kein Eingang in triage_external, kein reported_from. Meldungen landen unmittelbar in der offenen Liste fremder Projekte und verderben die Zahlen, auf denen die Supervisor-Detektoren kalibrieren.

Die bezeugte Herkunft ist der Punkt: Melden drei Projekte dasselbe, ist das eine Aussage über die Sache. Ohne Bindung ist es eine Aussage über niemanden.

Zu *: Die vorhandenen Projekt-Token tragen durchweg Vollzugriff. Das läuft, deckt die drei gebrauchten Rechte ab — und erlaubt auf einer geteilten Maschine eben auch updateservice:publish. Ein Release aus einem Testlauf heraus wäre für Kunden sofort sichtbar. Wer die Token neu ausstellt, schneidet sie auf die drei Rechte zu.

4.3 DEV-Lizenzschlüssel selbst holen

dc-testkit license

Das ist der Aufruf, der bisher fehlte. Er stellt für diese Maschine und dieses Projekt einen befristeten Lizenzschlüssel aus und legt ihn mit Rechten 600 ab. Ausgegeben wird er nicht — wer ihn wirklich sehen muss, hängt --print an.

Was dabei passiert und warum:

Befristet 30 Tage (einstellbar über license.dev_ttl_days, höchstens 90). Ein Schlüssel, den beim Anlegen niemand befristet hat, läuft nie ab und liegt in drei Monaten noch in einem Chatverlauf.
Erkennbar Trägt dev-agent@testumgebung.invalid als Kundenadresse und DEV <maschine> als Namen. In der Aktivierungsliste ist Testhardware damit von echten Installationen zu unterscheiden.
Wiederholbar Derselbe Aufruf liefert denselben Schlüssel zurück und verlängert die Frist. Er darf deshalb am Anfang jedes Laufs stehen.
Öffnet die Release-Ablage /releases/<slug>/ liegt seit 2.4 hinter Basic-Auth, die am Lizenzschlüssel hängt. Der neue Schlüssel wird beim Ausstellen sofort in die .htpasswd des Produkts nachgezogen.
Begrenzt Höchstens 12 gleichzeitig je Projekt. Eine Schleife in einem fehlerhaften Testskript legt sonst über Nacht tausende an.

Zurückgeben, wenn die Maschine das Projekt nicht mehr testet:

dc-testkit license --revoke

Nötig ist das nicht — abgelaufene Schlüssel verschwinden von selbst. Es macht nur sofort Platz.

Ein DEV-Schlüssel ist kein Sonderfall im Lizenzsystem. Er durchläuft dieselbe Prüfung, dieselbe Aktivierungsgrenze, denselben Release-Zugang wie ein Kundenschlüssel. Das ist Absicht: Wäre er ein Sonderweg, prüfte der Test hinterher etwas anderes als den Betrieb.

4.4 Installations- und Updateroutinen prüfen

dc-testkit agent      # update-agent holen, Prüfsumme gegen installer.json abgleichen
dc-testkit install    # Erstauslieferung in einen leeren Sandkasten
dc-testkit verify     # Was liegt jetzt da, und passt es zu dem, was der Server führt?
dc-testkit update     # Update auf latest
dc-testkit verify

Drei weitere Aufrufe reichen den jeweiligen Modus des update-agent durch. Sie verändern den Sandkasten nicht — außer repair, der genau dafür da ist:

Aufruf Wofür
dc-testkit check Gibt es überhaupt ein Update? Antwortet, ohne etwas zu holen
dc-testkit list Welche Fassungen führt der Server in diesem Kanal?
dc-testkit repair Integritätsreparatur — stellt her, was im Sandkasten fehlt oder nicht zur Prüfsumme passt

repair gehört in einen Prüfplan, weil es der Weg ist, den ein Kunde nach einem abgebrochenen Update nimmt. Ein Modus, den niemand testet, ist im Ernstfall genau der, der nicht funktioniert.

Oder alles auf einmal, mit automatischer Meldung dessen, was scheitert:

dc-testkit run

Der Sandkasten liegt unter ~/.local/state/dc-testkit/<projekt>/sandbox — bewusst nicht in /opt. Ein Testlauf, der die echte Installation überschreibt, ist kein Test, sondern ein Deployment.

Was geprüft wird: Download aus der Release-Ablage mit dem eigenen Lizenzschlüssel, SHA256-Abgleich, Signaturprüfung, Staging und Rollback, das geschriebene manifest.json, und der Abgleich der installierten Fassung gegen die, die der Server als neueste führt.

Ein Rückfall auf eine ältere Fassung — der Weg, der im Ernstfall gebraucht wird und deshalb geprüft gehört:

dc-testkit update --version 1.3.2
dc-testkit verify

Der Lizenzschlüssel geht dabei über die Umgebungsvariable DC_LICENSE_KEY an den Agenten, nicht über --license-key. Auf einer geteilten Maschine sieht jeder andere Benutzer die Befehlszeilen fremder Prozesse in ps aux; die Umgebung eines Prozesses liest nur, wem er gehört.

4.5 Gefundenes melden

Ein Befund, von dem nur das Terminal weiß, ist kein Befund gewesen.

dc-testkit report --type bug --severity high \
  --title "Update bricht bei fehlender manifest.json ab" \
  --desc "Nach der Erstauslieferung fehlt manifest.json; der naechste Lauf haelt jede Version fuer neuer." \
  --file "client-dotnet/Deploymentcenter.UpdateAgent/Program.cs" --line 412

Auch ein Wunsch statt eines Fehlers:

dc-testkit report --type feature --severity wishlist \
  --title "update-agent: kopfloser Installationsmodus" --desc-file /tmp/begruendung.txt

Die Beschreibung darf über die Standardeingabe kommen — Fehlertexte entstehen meist als Ausgabe eines anderen Befehls:

./meinprogramm 2>&1 | dc-testkit report --title "Absturz beim Start" --severity critical

client_ref wird abgeleitet, nicht gewürfelt — aus Projekt, Titel und Fundstelle. Derselbe Befund aus zehn Testläufen legt damit ein Item an und nicht zehn. Wer bewusst ein zweites will, gibt --ref selbst an.

Der Tracker antwortet — melde nicht blind

Bevor du etwas meldest, ist die Frage nicht „ist das ein Fehler?", sondern „ist das hier schon bekannt, und in welcher Fassung wurde es behoben?" Diese Antwort steht bereits in der Datenbank: Jedes erledigte Item trägt seine Zielfassung in resolved_in_build.

Das Testkit schickt deshalb bei jeder Meldung die geprüfte Fassung mit — automatisch, gelesen aus der manifest.json im Sandkasten. Ohne diese Angabe kann der Server nicht unterscheiden, worum es sich handelt, und legt im Zweifel ein Regressionsitem an. Sie zu vergessen ist der häufigste Weg, den Tracker unbrauchbar zu machen.

Daraus fallen zwei verschiedene Antworten:

Behoben in einer späteren Fassung als deiner — der Regelfall auf einem Prüfstand, der nicht immer die neueste Fassung fährt:

  ok Nicht gemeldet - schon behoben.
     Item #128, behoben in 1.4.3; geprüft wurde 1.4.2.
     Es wurde nichts angelegt und nichts hochgezählt.
     Aktualisiere und prüfe erneut. Tritt es dann immer noch auf,
     ist es eine Regression und gehört gemeldet.

Es entsteht kein Item, kein Kommentar, kein hochgezählter Zähler. Der Befund sagt nichts über die Software aus, sondern nur über die Fassung, die du geprüft hast.

Behoben in einer Fassung, die du bereits hast — dann ist es ernst:

warn REGRESSION: Item #144 ist ein Wiederauftreten von #128.
warn Der Fehler galt als behoben und ist in 1.4.7 wieder da.

Hier entsteht ein neues Item mit Verweis auf das alte, und am alten wird vermerkt, in welcher Fassung es wieder auftrat. Das ist der Fall, für den die Kennzeichnung da ist.

Warum das vorher nicht ging. Der Server verglich die Fassungen nicht. Jede Meldung gegen ein bereits erledigtes Item wurde zur Regression erklärt — auch die von einem Prüfstand, der den Fix schlicht noch nicht hatte. Bei einer Testumgebung, die absichtlich auch ältere Fassungen prüft, wäre bald jede zweite Meldung eine „Regression" gewesen, und die echten wären darin untergegangen.

Der Prüfauftrag — Arbeit, die von selbst zu dir kommt

Ein Fix ist eine Behauptung, bis jemand ihn gegen ein ausgeliefertes Paket geprüft hat. Genau dafür gibt es seit 2.6.1 einen Platz in der Arbeitsschlange — und deshalb braucht der Rückkanal keine Zustellung: dc-testkit next gibt dir Prüfaufträge wie jede andere Arbeit.

==> Naechstes Item

  NACHPRUEFUNG, keine neue Fehlersuche:
    Item #128  Update bricht bei fehlender manifest.json ab
    Behoben laut Tracker in: 1.4.3
    Projekt: deploymentcenter

    Gegen diesen Build testen. Dann eines von beidem:
      traegt      -> dc-testkit confirm 128 "kurz was geprueft wurde"
      traegt nicht-> den Fehler erneut melden (dc-testkit report ...);
                     das Item wechselt dann selbst auf fix_insufficient.

Lies den Hinweis. Ein Prüfauftrag sieht im JSON aus wie jede andere Arbeit, stellt aber eine andere Frage: nicht „behebe das", sondern „trägt der Fix?". Wer das übersieht, fängt an, einen bereits behobenen Fehler ein zweites Mal zu beheben.

Der Lebenslauf

Status Bedeutung
open → in_progress Gewöhnliche Arbeit. Übernehmen befördert
resolved Ein Agent sagt: behoben in Build X. Noch nichts ausgeliefert
awaiting_verification Build X ist veröffentlicht — jetzt lässt sich nachsehen
in_verification Jemand sieht gerade nach. Entsteht durch denselben Claim
fix_insufficient Gegen den Build geprüft, Fehler immer noch da
closed Bestätigt weg

Der Übergang nach awaiting_verification passiert beim Veröffentlichen, nicht beim Beheben. Vorher gibt es kein Paket, gegen das man prüfen könnte — eine Prüfaufgabe zu diesem Zeitpunkt wäre eine, die niemand erledigen kann.

confirm ist nicht resolve. resolve behauptet „ich habe es behoben, in Build X" und setzt resolved_in_build. confirm behauptet nichts, sondern bestätigt den Fix eines anderen. Beides in einen Befehl zu legen hieße, den Unterschied zwischen Fixen und Prüfen einzuebnen.

Was passiert, wenn du nicht fertig wirst

Nichts Schlimmes — aber es bleibt nicht liegen:

  • Lease läuft ab → das Item geht zurück auf awaiting_verification und liegt wieder in der Schlange. (Dasselbe gilt seit 2.6.1 für gewöhnliche Arbeit: in_progress → open. Vorher blieb ein liegengelassenes Item auf in_progress stehen und wurde nie wieder ausgegeben — es war weg.)
  • Dreimal begonnen, nie beendet → ein Vermerk am Item holt einen Menschen. Still weiter einzureihen hieße, eine Schlange zu führen, in die niemand sieht.
  • 30 Tage niemand angenommen → wird ungeprüft geschlossen, mit einem Kommentar, der genau das sagt. Ein ehrliches Ende ist besser als eine Aufgabe, die niemand annimmt. Die Zahl „geschlossen" bedeutet dann eben nicht „bestätigt", und das steht am Item.

Auch für Befunde in fremden Projekten

Hast du einen Fehler im Deploymentcenter gemeldet (§4.6), bekommst du den Prüfauftrag, wenn er behoben ist — obwohl das Item dem Deploymentcenter gehört. Du bist als betroffen vermerkt, und oft bist du der Einzige, der bestätigen kann, dass der Fix bei dir wirkt.

Das ist die eine, eng gefasste Ausnahme von der Regel „ein Agent arbeitet nur im eigenen Projekt": nur Items, die auf Nachprüfung warten, nur wenn dein Projekt in affects steht. Du darfst sie kommentieren, im Status setzen und zurückgeben — aber nicht resolve. Was in einem fremden Projekt als „behoben in Build X" gilt, entscheidet dieses Projekt.

Den Kreis schließen, wenn daran gearbeitet wird:

dc-testkit next                    # naechstes Item uebernehmen (exklusiv)
dc-testkit comment 42 "Ursache gefunden: Pfadtrenner unter Linux." fix_proposed
dc-testkit resolve 42 v1.4.3 "Behoben in Installer.cs."   # ich habe es behoben
dc-testkit confirm 42 "Gegen 1.4.3 geprueft, Paket sauber."  # der Fix eines anderen traegt
dc-testkit unclaim 42 "Komme nicht weiter, brauche Zugriff auf die Kundendaten."

Übernehmen ist keine Formsache. Wer es überspringt, arbeitet irgendwann parallel zu jemandem am selben Problem. Antwortet der Server mit 409 already_claimed, nimm das nächste Item.

4.6 Befunde, die ein anderes Projekt betreffen

⚠️ Alles in diesem Abschnitt setzt Deploymentcenter 2.6.0 voraus (triage_external, Deckelung, reported_from, die Ablehnung von Laufzeit-Token). Ein älterer Server nimmt eine Fremdmeldung an, legt sie aber als gewöhnliches Item mit status=open und ohne reported_from ab — ohne das zu sagen. Wer das beobachtet, hat nicht drei Fehler vor sich, sondern einen zu alten Server; dc-testkit doctor sagt es seit 2.7.1 von sich aus.

Das ist der wichtigste Abschnitt dieser Datei. Ein Agent auf der Test-VM stolpert zwangsläufig über Dinge, die nicht seinem Projekt gehören: Der PolyTrader-Agent findet einen Fehler im update-agent. Ihm fällt auf, dass diese Anleitung an einer Stelle lügt. Er hätte einen Vorschlag, wie das Testkit besser wäre.

Landet das im eigenen Projekt, ist es verloren — dort liest es niemand, der es beheben könnte. Landet es gar nirgends, wiederholt sich derselbe Stolperstein bei jedem, der nach ihm kommt.

dc-testkit projects        # welche Slugs gibt es überhaupt?

dc-testkit report --project deploymentcenter   --type bug --severity medium   --title "update-agent schreibt bei --action update kein manifest.json"   --desc-file /tmp/befund.txt   --evidence "Nach dc-testkit install liegt kein manifest.json im Zielverzeichnis. Der naechste Lauf meldet deshalb 'Unbekannt' als installierte Fassung und haelt jede Version fuer neuer. Reproduziert mit polytrader und deploymentcenter, also nicht projektspezifisch."

Wohin gehört was

Befund Slug
Fehler im update-agent, im SDK, in der Lizenzprüfung, im Bugtracker deploymentcenter
Fehler oder Lücke in dieser Anleitung oder in dc-testkit deploymentcenter — beide liegen in dessen Repo
Die Test-VM selbst: fehlendes Werkzeug, belegtes Display, kaputte Konvention deploymentcenter, --type feature
Alles am eigenen Programm das eigene Projekt, ohne --project

Die Testumgebung ist kein eigenes Projekt. Sie hat keinen Slug. Wer sie verbessern will, meldet an deploymentcenter — dort liegen Skript und Anleitung, und dort wird beides gepflegt.

Drei Dinge, die dabei anders sind

1. Eine Begründung ist Pflicht — --evidence, mindestens 40 Zeichen. Nicht als Schikane: Ein Agent in Projekt X kann selten sicher unterscheiden, ob das fremde Projekt kaputt ist oder ob X es falsch benutzt. Die Voreinstellung eines hilfsbereiten Modells ist im Zweifel „melde es". Etwas ausformulieren zu müssen kostet diesen Reflex fast immer den Kopf. Beantworte darin: Was wurde beobachtet, und warum liegt die Ursache dort und nicht bei mir?

2. Der Schweregrad wird auf medium gedeckelt. high und critical vergibt die empfangende Seite — nur sie weiß, wer sonst noch betroffen ist. Das Testkit warnt, wenn du höher einstufst. Was dringlich ist, gehört in die Begründung, nicht in das Feld.

3. Die Meldung landet in triage_external, nicht in open. Das ist ein Eingang, kein Ablagefach: Fremdmeldungen zählen erst nach Bestätigung in die Statistik des Zielprojekts — sonst verdürben Fehlmeldungen die Zahlen, auf denen die Supervisor-Detektoren kalibrieren. Such dein Item also nicht in der offenen Liste des fremden Projekts.

Die Herkunft ist bezeugt, nicht behauptet

reported_from wird aus der Bindung deines Tokens abgeleitet und nie aus dem Body übernommen. Du kannst dich nicht als jemand anderes ausgeben. Genau das macht das Signal erst wertvoll: Melden drei Projekte dasselbe, ist das eine Aussage über die Sache und nicht über die Meldefreudigkeit eines Agenten. Meldet ein zweites Projekt einen bereits erfassten Befund, entsteht kein neues Item — es wird als weiterer Betroffener geführt (affects).

Voraussetzung ist ein Werkbank-Token (Besitzerart dev_agent, §4.2). Nur solche Token dürfen projektübergreifend melden. Ein Laufzeit-Token darf es nicht: Es steckt in der ausgelieferten Anwendung und ist extrahierbar.

Wenn ein Fremdbefund dich betrifft

Du musst nicht benachrichtigt werden — du kannst fragen. Meldest du denselben Befund erneut, antwortet der Tracker mit known_fixed_in (siehe §4.5): behoben in Fassung X, du hast Y. Das beantwortet die Frage „ist mein Workaround noch nötig?" ohne Rückkanal, und zwar für Fremdmeldungen genauso wie für eigene.

Was nicht von selbst passiert: Niemand stößt dich an. Wird ein Item behoben, das du gemeldet hast, erfährst du es erst, wenn du das nächste Mal fragst — beim Melden oder über den Changelog:

curl -s "https://dc.mhdf.de/api/updateservice/v1/changelog?since=<deine Fassung>"

Für einen Testlauf, der ohnehin regelmäßig läuft, genügt das: Er fragt bei jedem Durchgang. Wer einen Workaround gebaut hat und ihn zurückbauen möchte, ohne darauf zu warten, braucht den aktiven Rückkanal (Roadmap C3).

4.7 Was das Testkit nicht kann — und warum

Den interaktiven Installationsdialog. update-agent --action install ist ein Dialog mit Auswahllisten und Passwortabfrage. Er lässt sich nicht kopflos treiben, und ein Agent darf ohnehin kein Passwort eingeben (§2).

Was dc-testkit install stattdessen tut, ist die Erstauslieferung in ein leeres Verzeichnis über den Update-Weg. Das ist keine Notlösung: Der Installer holt die Dateien laut SETUP_INTEGRATION_GUIDE über genau denselben Pfad — Download, SHA256, Signaturprüfung, Staging, Rollback. Was der Dialog zusätzlich tut, ist fragen und eine Konfiguration schreiben.

Geprüft wird also die Auslieferung, nicht die Gesprächsführung. Die Auflösung von setup.json und das Schreiben der Konfiguration bleiben ungeprüft. Ein kopfloser Installationsmodus (--action install --answers antworten.json) würde diese Lücke schließen; er steht in der Roadmap unter G1.

4.8 Ein Agent, der nur prüft

Ein Prüfagent ist etwas anderes als ein Entwickleragent, der nebenbei testet. Er gehört zu genau einem Projekt, läuft auf dieser Maschine — und behebt nichts.

Der Unterschied ist keine Geschmacksfrage. Wer einen Fix schreibt und ihn anschließend selbst abnimmt, prüft gegen die Erwartung, die er beim Schreiben hatte. Genau dagegen ist der Tracker gebaut: resolve behauptet „ich habe es behoben, in Build X"; confirm behauptet nichts, sondern bestätigt den Fix eines anderen (siehe 4.5, „Der Lebenslauf").

Der Prüfagent tut Er tut nicht
Auslieferung und Update gegen den Sandkasten prüfen (4.4) Code des Projekts ändern
Oberflächen begutachten (5.8) dc-testkit resolve aufrufen
Befunde melden — auch in fremden Projekten (4.5, 4.6) Die Roadmap ändern
Prüfaufträge übernehmen und mit confirm oder erneuter Meldung schließen Ein Item beheben, das er selbst gemeldet hat

Seit 2.8.0 trägt ein Recht diese Trennung — vorher stand sie nur im Prompt. bugtracker:verify erlaubt genau das, was ein Prüfstand braucht:

bugtracker:verify bugtracker:manage
next, claim, release ja ja
comment ja ja
confirm ja ja
melden (report) ja ja
resolve nein ja
status, update, bulk_update nein ja
Roadmap schreiben nein ja

bugtracker:manage schließt verify ein; vorhandene Token verlieren nichts. Dass status bei manage bleibt, ist Absicht: Darüber ließe sich sonst jeder Zustand setzen — auch resolved — und die Trennung wäre um einen Aufruf herum umgangen.

Trotzdem gehört die Grenze in den Prompt. Ein Recht verhindert den Aufruf, aber nicht, dass ein Agent anfängt, den Fehler selbst zu suchen und zu beheben, statt ihn zu melden. Der fertige Textbaustein ist E in AGENT_PROMPT_TEMPLATE.md.

Gegen welche Fassung? Jede Meldung trägt die geprüfte Fassung mit, gelesen aus der manifest.json im Sandkasten. Ohne sie kann der Server nicht unterscheiden, ob ein Befund neu ist oder ob die Maschine schlicht einen älteren Stand fährt — und legt im Zweifel ein Regressionsitem an (4.5). Für einen Agenten, der nichts anderes tut als prüfen, ist das die wichtigste einzelne Angabe überhaupt.

Dasselbe gilt beim Bestätigen: confirm schickt die geprüfte Fassung als verified_in_build mit, nicht im Fließtext. Der Server hält sie neben resolved_in_build — der Behauptung des Behebenden („ab 1.4.3 ist es weg") steht damit die Beobachtung des Prüfenden gegenüber („in 1.4.5 nicht mehr aufgetreten"). Gehen die beiden auseinander, wurde gegen etwas anderes geprüft als angekündigt, und das ist sichtbar statt verschwunden. Bis 2.8.0 war beides status=closed mit einem Kommentar — eine bestätigte Nachprüfung war von „jemand hat das Item zugemacht" nicht zu unterscheiden.

Was ihn einrichtet, steht in AGENTEN_ANBINDEN.md; die Reihenfolge ist dort dieselbe wie für jeden anderen Agenten, nur mit anderem Prompt-Baustein und anderer Abnahmefrage.


5. Konventionen für mehrere Projekte

Die Maschine wird von mehreren Projekten geteilt. Diese Regeln verhindern, dass sich Tests gegenseitig zerstören.

5.1 Datenbanken — je Projekt eine eigene

Niemals eine fremde Datenbank mitbenutzen und niemals eine Produktivdatenbank verbinden.

# Passwort erzeugen und ablegen (nur für den Besitzer lesbar)
PW=$(openssl rand -base64 18 | tr -d "/+=" | head -c 20)
echo "$PW" > ~/.<projekt>_test_dbpw && chmod 600 ~/.<projekt>_test_dbpw

sudo mariadb -e "CREATE DATABASE IF NOT EXISTS <projekt>_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
                 CREATE USER IF NOT EXISTS '<projekt>_test'@'localhost' IDENTIFIED BY '$PW';
                 GRANT ALL PRIVILEGES ON <projekt>_test.* TO '<projekt>_test'@'localhost';
                 FLUSH PRIVILEGES;"
Was Schema Beispiel
Datenbank <projekt>_test polytrader_test
Benutzer <projekt>_test@localhost polytrader_test
Passwortdatei ~/.<projekt>_test_dbpw (Rechte 600)

Die Rechte enden an der eigenen Datenbank — GRANT ALL ON <projekt>_test.*, nie global.

5.2 Verzeichnisse

Zweck Ort
Quellcode ~/RiderProjects/<projekt>
Sandkasten des Testkits ~/.local/state/dc-testkit/<projekt>/sandbox
Publish/Testlauf von Hand ~/<projekt>-test (voller Projektname, keine Kürzel)
Installation als Dienst /opt/<projekt>

5.3 systemd-Dienste

Unit-Name = Projektname: <projekt>.service. Eigenes Dienstkonto je Projekt:

sudo useradd --system --home-dir /opt/<projekt> --shell /usr/sbin/nologin <projekt>

Nach dem Test abschalten (Abschnitt 8) — sonst laufen irgendwann fünf Dienste, die niemand mehr zuordnen kann, und der nächste Test misst gegen fremde Last.

Im Dienst ohne Heimatverzeichnis (systemd User= ohne $HOME) fällt der Lizenz-Cache auf /var/lib/<slug>/license zurück. Ist auch das nicht beschreibbar, gibt es keinen Offline-Cache — dann muss DEPLOYMENTCENTER_STORAGE_DIR auf ein beschreibbares Verzeichnis zeigen. Das ist ein Fehlerbild, das nur im Dienstbetrieb auftritt und deshalb hier gehört.

5.4 Ports

Vor dem Belegen prüfen: ss -tlnp. Belegte Ports stehen in Abschnitt 7. Wer einen Port dauerhaft belegt, trägt ihn dort ein.

5.5 X-Displays — feste Nummer je Projekt

Damit mehrere Agenten gleichzeitig Oberflächen testen können, bekommt jedes Projekt ein eigenes virtuelles Display. Zwei Prozesse auf demselben Display sehen die Fenster des jeweils anderen — Klicks landen im falschen Programm, Aufnahmen zeigen fremde Anwendungen.

Display Projekt
:99 PolyTrader
:98 Predictalytics
:97 ClawdDotNet
:96 IBKRTrader
:95 und absteigend künftige Projekte (nächstes freies von oben nach unten)

Wer ein neues Projekt aufnimmt, trägt seine Nummer in die Belegungsliste (Abschnitt 7) ein.

Belegte Displays prüfen:

ls /tmp/.X11-unix/          # X0 = der echte Desktop, X99 = PolyTrader usw.
pgrep -a Xvfb               # zeigt, welcher Xvfb auf welchem Display läuft

Verwaiste Sockets — beide Befehle gemeinsam lesen: Steht in /tmp/.X11-unix/ ein X98, zu dem pgrep keinen Prozess zeigt, ist das ein Überbleibsel eines abgestürzten oder hart beendeten Xvfb. Die Nummer wirkt dann belegt, obwohl sie frei ist. Aufräumen:

sudo rm -f /tmp/.X11-unix/X98

(Genau so entstanden: ein xvfb-run -a, das sich :98 nahm, weil :99 gerade lief — siehe die Warnung in 5.7.)

⚠️ Drei Regeln, damit der Parallelbetrieb wirklich trägt:

  1. DISPLAY in jedem Skript setzen (export DISPLAY=:98). Fehlt die Variable, landet man auf :0 — dem echten Desktop — und stört den Menschen, der dort gerade arbeitet.
  2. Nie pkill Xvfb ohne Displaynummer. Das erschlägt die Sitzungen aller anderen Projekte. Immer pkill -f "Xvfb :98". Dasselbe gilt für die eigene Anwendung: pkill -f mit einem zu allgemeinen Muster trifft fremde Prozesse — besser die gemerkte PID beenden.
  3. Dateien in /tmp mit Projektpräfix. /tmp/v_1.png überschreibt sich gegenseitig; nimm /tmp/<projekt>_v_1.png. Gleiches gilt für Logdateien und Skripte.

5.6 Erhöhte Rechte

sudo verlangt normalerweise ein Passwort. Für Testläufe kann es befristet freigeschaltet werden (ein Mensch, einmalig):

echo "richard ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/99-testing && sudo chmod 440 /etc/sudoers.d/99-testing

Nach Abschluss wieder entfernen:

sudo rm /etc/sudoers.d/99-testing

Das hebt den Schutz für das Konto vollständig auf — vertretbar auf einer Test-VM hinter VPN, aber nicht dauerhaft und niemals auf einer Produktivmaschine.

Stand 31.08.2026: /etc/sudoers.d/99-testing liegt dort und gilt. Aus „befristet" ist „dauerhaft" geworden — der Weg, den solche Freigaben immer nehmen, wenn das Entfernen nicht zum selben Arbeitsgang gehört. Wer als Nächstes etwas mit sudo erledigt, nimmt sie danach zurück. Ein Prüfagent braucht sie nicht.

Für das Testkit wird das nicht gebraucht. Es arbeitet vollständig im Benutzerkontext.

5.7 GUI-Anwendungen ohne Bildschirm testen

Braucht eine Anwendung X11, scheitert sie über SSH mit XOpenDisplay failed. Für einen einzelnen Durchlauf genügt:

xvfb-run -n <deine-nummer> ./MeineAnwendung --irgendein-schalter

⚠️ Nicht xvfb-run -a verwenden. Das -a sucht sich selbst ein freies Display und beginnt dabei bei :99 — also ausgerechnet auf PolyTraders Nummer. Läuft dort gerade nichts, belegt es diese Nummer; startet PolyTrader kurz darauf, kollidieren beide. Mit -n 98 (bzw. der eigenen Nummer aus 5.5) bleibt die Zuordnung eindeutig.

5.8 Oberflächen selbst begutachten — ohne Bildschirm, ohne fremde Hilfe

Ein Agent kann die Oberfläche eigenständig prüfen. Kein Mensch muss am Bildschirm sitzen und Bildschirmfotos schicken. Das Verfahren hat sich am 26.08.2026 bewährt und sieben Befunde zutage gefördert, darunter einen, der sämtliche Tabellen der Anwendung unsichtbar machte.

Drei Werkzeuge, alle installiert: Xvfb (virtuelles Display), xdotool (Klicken und Tippen), ImageMagick (import für Aufnahmen, convert zum Zuschneiden). dc-testkit doctor sagt seit 1.1.0, ob sie da sind und ob das eigene Display frei ist.

Den immer gleichen Teil nimmt das Testkit ab — Display starten, aufnehmen, abräumen. Setze dazu DC_DISPLAY in deiner Konfiguration (die Nummer steht in Abschnitt 7):

dc-testkit display status          # frei, belegt oder verwaister Socket?
dc-testkit display start           # Xvfb auf DC_DISPLAY, Vorgabe 1600x1000x24
dc-testkit shot 1_start            # nach /tmp/<projekt>_1_start.png
dc-testkit display stop            # nur das eigene, nie fremde

Das sind genau die drei Stellen, an denen ein selbstgeschriebenes Skript regelmäßig danebengreift: DISPLAY nicht gesetzt (dann landet alles auf :0, dem echten Desktop), pkill Xvfb ohne Nummer (das erschlägt die Sitzungen aller anderen Projekte), Dateinamen ohne Projektpräfix (zwei Läufe überschreiben sich gegenseitig). display start verweigert :0 und :1 von sich aus und erkennt einen verwaisten Socket, statt an ihm zu scheitern.

Was projektspezifisch bleibt, ist der Klickplan dazwischen — Fenstername, Koordinaten, Wartezeiten. Den kapselt das Testkit bewusst nicht: Er ist bei jedem Programm anders, und ein Werkzeug, das ihn zu erraten versucht, wäre unzuverlässiger als das Rezept unten.

Anders als xvfb-run wird das Display hier dauerhaft gestartet, damit sich zwischen den Aufnahmen navigieren lässt:

#!/bin/bash
# Kopf anpassen - das ist alles, was sich je Projekt unterscheidet:
PROJEKT="polytrader"
DISP=":99"                    # eigene Nummer aus Abschnitt 5.5!
FENSTER="^PolyTrader$"

cd ~/$PROJEKT-test
export DISPLAY=$DISP

pkill -f "Xvfb $DISP" 2>/dev/null; sleep 1
Xvfb $DISP -screen 0 1600x1000x24 >/dev/null 2>&1 &
sleep 3

nohup ./MeineAnwendung > /tmp/${PROJEKT}_gui.log 2>&1 &
APP=$!
sleep 25                      # Startzeit großzügig bemessen

# Fenster-ID ermitteln (bestaetigt, dass die Oberflaeche ueberhaupt erscheint)
xdotool search --name "$FENSTER" | head -1

# navigieren und aufnehmen - Dateinamen mit Projektpraefix, sonst
# ueberschreiben sich parallel laufende Projekte gegenseitig
shoot() { sleep 5; import -window root /tmp/${PROJEKT}_$1.png; echo "  $1"; }
shoot 1_start
xdotool mousemove 127 205 click 1; shoot 2_zweite_ansicht
xdotool mousemove 127 237 click 1; shoot 3_dritte_ansicht

kill $APP 2>/dev/null; sleep 2; pkill -f "Xvfb $DISP" 2>/dev/null

Danach die Bilder abholen und ansehen:

scp testvm-polytrader:'/tmp/polytrader_*.png' .

Was sich dabei bewährt hat:

  • Großzügige Wartezeiten. 25 Sekunden nach dem Start, 5 Sekunden nach jedem Klick. Wird zu früh aufgenommen, entsteht ein halb gezeichnetes Fenster, und man sucht Fehler, die keine sind.
  • Klickpunkte aus dem ersten Bild ablesen. Erst eine Aufnahme machen, dort die Koordinaten der Menüpunkte bestimmen, dann das Skript damit bestücken.
  • Details vergrößern statt raten. Bei einem Verdacht den Ausschnitt herausschneiden:
    convert v_1_start.png -crop 560x130+265+540 +repage -resize 200% /tmp/detail.png
    
  • Kontraste nachmessen statt schätzen. Ob Text lesbar ist, lässt sich ausrechnen — Pixelfarben auslesen und das WCAG-Verhältnis bestimmen (4,5:1 für normalen Text). So wird aus „wirkt blass" ein belegter Befund.
  • Nur eine Instanz des eigenen Projekts gleichzeitig. Läuft der systemd-Dienst noch, schreiben zwei Prozesse in dieselbe Datenbank und es entstehen Fehler, die nichts mit der Oberfläche zu tun haben. Vorher sudo systemctl stop <projekt>. (Ein anderes Projekt auf seinem eigenen Display stört dagegen nicht — dafür ist die Trennung aus 5.5 da.)
  • Auf Zeichen achten, die falsch aussehen. Fehlt eine Icon-Schriftart, ersetzt Fontconfig sie stillschweigend durch etwas anderes — ein Kalendersymbol wird dann zu einer sinnlosen Zahl. Das fällt nur im Bild auf, nie im Log.

Was dabei auffällt, gehört in den Bugtracker — nicht in eine Notizdatei. Siehe §4.5.

Für einen Test mit echtem Bildschirm kann trotzdem jemand am Desktop der VM sitzen (oder RDP nutzen, Port 3389 ist offen) — nötig ist es für die Sichtprüfung nicht.


6. Was hier nicht passieren darf

  1. Keine Produktivdatenbank verbinden. Hintergrunddienste schreiben oft ungefragt — bei PolyTrader etwa synchronisiert der MarketSyncService selbständig Marktdaten.
  2. Keine echten Handels- oder Zugangsdaten hinterlegen. Keine privaten Schlüssel, keine Live-API-Zugänge. Lizenzschlüssel nur als DEV-Schlüssel aus dc-testkit license — ein Kundenschlüssel auf einer geteilten Maschine verbraucht dessen Aktivierungsplätze und ist hinterher nicht mehr sauber freizubekommen.
  3. Kein Token mit *. Für Testläufe genügen zwei Rechte, siehe §4.2.
  4. Keine fremden Projektverzeichnisse oder Datenbanken anfassen.
  5. Nichts veröffentlichen. updateservice:publish gehört in die CI, nicht auf die Test-VM. Ein Release aus einem Testlauf heraus wäre für Kunden sofort sichtbar.
  6. Nichts Dauerhaftes hinterlassen, was nicht in Abschnitt 7 steht.

7. Belegung (bei Änderungen fortschreiben)

Erhoben am 31.08.2026 von der Maschine selbst, nicht aus dieser Datei fortgeschrieben — sie war zu dem Zeitpunkt vier Projekte im Rückstand. Wer etwas anlegt, trägt es hier ein; wer sich darauf verlassen will, prüft nach:

ssh testvm-<projekt> 'sudo mariadb -e "SHOW DATABASES;"; ls /opt ~; ss -tlnp; ls /tmp/.X11-unix/; pgrep -a Xvfb'

Datenbanken

Datenbank Benutzer Projekt seit
polytrader_test polytrader_test@localhost PolyTrader 26.08.2026
ibkrtrader_test ibkrtrader_test@localhost IBKRTrader vor 31.08.2026

Dienste

Unit Konto Verzeichnis Projekt seit
polytrader.service polytrader /opt/polytrader PolyTrader 26.08.2026

Am 31.08.2026 lief keiner dieser Dienste — wie es Abschnitt 8 verlangt.

Verzeichnisse

Projekt Publish-Verzeichnis Dienst
PolyTrader ~/polytrader-test /opt/polytrader
IBKRTrader ~/ibkrtrader-test —
ClawdDotNet ~/clawddotnet-test —
Predictalytics ~/predictalytics ⚠️ —

⚠️ ~/predictalytics bricht die Konvention aus 5.2 (~/<projekt>-test). Aus dem Namen ist nicht mehr abzulesen, ob dort ein Testlauf oder eine Arbeitskopie liegt — und genau diese Frage stellt sich, wenn jemand aufräumt. Beim nächsten Anfassen umbenennen.

Quellbäume liegen daneben unter ~/RiderProjects/<Projekt> (5.2): ClawdDotNet, Deploymentcenter, IBKRTrader, PolyTraderSharp, Predictalytics.

Werkbank-Token (Vergabe siehe 4.2)

Die richtigen existieren längst — je Projekt eines, in der .mcp.json des Repos auf dem Arbeitsplatz:

Projekt Token Bindung Rechte
PolyTrader DEV-Polytrader polytrader *
IBKRTrader DEV-IBKRTrader ibkrtrader *
Deploymentcenter DEV-Deploymentcenter deploymentcenter *
ClawdDotNet — — .mcp.json enthält nur den Platzhalter

Alle sind dev_agent und korrekt gebunden. Zu * siehe die Anmerkung am Ende von 4.2.

⚠️ Auf der VM kommt davon nichts an. Unter ~/RiderProjects/ liegt keine einzige .mcp.json — die Repos sind dort frisch geklont, und die Datei ist per .gitignore ausgeschlossen. In ~/.config/dc-testkit/config steckt stattdessen ein altes Sub-Token („PolyTrader Avalonia (Dev, alle Umgebungen)", watchdog:ping + bugtracker:report, ohne Bindung). Damit sind license, install, update, verify, run, next, comment, resolve und confirm gesperrt; übrig bleibt report.

Zu beheben ist das nicht durch ein neues Token, sondern indem das vorhandene DEV-Polytrader auf die Maschine kommt — als .mcp.json im Projektverzeichnis, auf das dann DC_PROJECT_DIR zeigt. dc-testkit doctor benennt seit 1.1.0 jeden dieser Punkte einzeln und nennt die Herkunft des benutzten Tokens.

X-Displays (Vergabe siehe 5.5)

Display Projekt Stand
:0, :1 echter Desktop und Anmeldebildschirm — nie belegen
:99 PolyTrader 26.08.2026
:98 Predictalytics reserviert
:97 ClawdDotNet reserviert
:96 IBKRTrader reserviert
:95 abwärts frei für künftige Projekte

Am 31.08.2026 lief kein Xvfb; belegt waren nur :0 und :1. Dass :1 existiert, stand hier bisher nicht — es gehört zum Anmeldebildschirm und ist genauso tabu wie :0.

Ports (Stand 31.08.2026)

Port Belegt durch
22 SSH
53 systemd-resolved (nur localhost)
631 CUPS (localhost, IPv4 und IPv6)
3306 MariaDB (nur localhost)
3389 RDP
5000 vorhandener Dienst (localhost, IPv4 und IPv6)

Port 5000 war hier als „nur IPv6-localhost" geführt. Das stimmt nicht mehr — wer ihn für einen Testdienst nehmen wollte, wäre auf beiden Familien kollidiert.

Nachträglich installiert: MariaDB 11.8.6, Xvfb, xdotool, ImageMagick, x11-utils (alle am 26.08.2026 für PolyTrader), jq (undatiert, von unbekannter Hand — das Testkit braucht es nicht).


8. Aufräumen nach dem Test

Das Testkit räumt sein eigenes auf:

dc-testkit cleanup            # Sandkasten weg, DEV-Schlüssel zurückgegeben, Agent gelöscht
dc-testkit cleanup --sandbox  # nur der Sandkasten, Schlüssel behalten

Alles Übrige von Hand:

# Virtuelles Display beenden (falls noch offen) - NUR das eigene!
pkill -f "Xvfb :<deine-nummer>"
rm -f /tmp/<projekt>_*.png /tmp/<projekt>_gui.log

# Dienst abschalten und entfernen
sudo systemctl disable --now <projekt>
sudo rm -f /etc/systemd/system/<projekt>.service /etc/logrotate.d/<projekt>
sudo systemctl daemon-reload

# Dateien
sudo rm -rf /opt/<projekt> ~/<projekt>-test
sudo userdel <projekt>

# Datenbank (nur wenn nicht mehr gebraucht)
sudo mariadb -e "DROP DATABASE <projekt>_test; DROP USER '<projekt>_test'@'localhost';"
rm -f ~/.<projekt>_test_dbpw

# sudo-Freigabe zurücknehmen
sudo rm -f /etc/sudoers.d/99-testing

Danach Abschnitt 7 nachziehen.


9. Bekannte Einschränkungen

  • Claude Code lässt sich auf dieser VM nicht betreiben. Es blockiert die Maschine bis zum Neustart; mehrere Reparaturversuche waren erfolglos, es wird auf ein Update gewartet. Agenten arbeiten deshalb über SSH von außen. (Speichermangel ist nicht die Ursache — die Maschine hat 31 GB, davon 25 GB frei.)

    Für Prüfagenten ist das die bestimmende Einschränkung. Ein Prüfagent je Projekt läuft nicht auf der Maschine, sondern auf einem Arbeitsplatz und greift über testvm-<projekt> zu. Daraus folgt zweierlei: Er kann node und damit den MCP-Adapter dort ohnehin nicht benutzen (der ist auch nicht installiert), und er sollte mehrere Prüfschritte pro SSH-Aufruf bündeln — Verbindungs-Multiplexing steht ihm unter Windows nicht zur Verfügung (2.3).

  • Verbindungs-Multiplexing funktioniert nicht mit Windows-OpenSSH (siehe 2.3).

  • Der Installationsdialog ist nicht kopflos prüfbar (siehe 4.7). Damit bleiben die Auflösung von setup.json und das Schreiben der Konfiguration ungeprüft.

  • Die Systemzeitzone ist Testgegenstand. Für realistische Servertests wird sie zeitweise auf UTC gestellt (sudo timedatectl set-timezone UTC). Wer sie ändert, stellt sie danach zurück — Standard hier ist Europe/Berlin.


Weiterführend

LICENSE_INTEGRATION_GUIDE.md Wie die Lizenzprüfung in der Anwendung selbst funktioniert
UPDATESERVICE_INTEGRATION_GUIDE.md Release-Verteilung, Kanäle, Signatur, Zugangsschutz der Ablage
SETUP_INTEGRATION_GUIDE.md Erstinstallation und setup.json
Agenten-Handbuch Vollständige Beschreibung des Bugtracker-Workflows
GET /api/openapi.json Die Schnittstelle maschinenlesbar