feat(2.7.0): Roadmap im DC, Testinstanz mit Testtor, Supervisor und MCP

Sammelcommit fuer den ueber mehrere Sitzungen gewachsenen, bisher nicht
eingecheckten Stand. Getrennte Commits waeren ehrlicher gewesen, sind es
nachtraeglich aber nicht mehr: BugRepo, changelog.json und die Doku tragen
Aenderungen aus allen Straengen gleichzeitig, und keine Zwischenstufe waere
lauffaehig.

ROADMAP IM DEPLOYMENTCENTER (2.7.0)

Planungsstand, Reihenfolge und Zustaendigkeit lagen in handgepflegten
Markdown-Dateien neben einem Bugtracker, der genau diese drei Dinge bereits
fuehrt. Zwei Pfleger fuer dasselbe Feld - Drift war eine Frage der Zeit.

Ab jetzt: Zustand im DC, Begruendung in der Datei. Kein Feld hat zwei Pfleger.

  - Migration 016: epic, roadmap_rank, target_build, defer_trigger,
    doc_anchor, Status "deferred", Tabelle roadmap_epics.
    KEINE Roadmap-Tabelle - ein Eintrag ist ein bugtracker_item.
  - Zugehoerigkeit ist ein Feld: Ein Item steht auf der Roadmap, wenn es ein
    epic hat. Ein zweites Kennzeichen daneben koennte auseinanderlaufen.
  - Kennung ist die Item-ID. Handvergebene Kuerzel ("A12") erzeugen die
    schlimmste Fehlerart: Beide Seiten stimmig, Verschiedenes gemeint, weil
    die Kennung nach einer Loeschung neu vergeben wurde.
  - Zurueckstellen verlangt defer_trigger. Ohne benannten Anlass ist ein
    Vorhaben nicht zurueckgestellt, sondern vergessen. Die Regel sitzt im
    Repository, damit sie auf allen drei Schreibwegen gleich greift.
  - Modules/Bugtracker/Roadmap.php, /api/bugtracker/v1/roadmap.php,
    WebUI-Modul mit Filtern, cli/roadmap.php --write/--check,
    dc_roadmap und dc_roadmap_set im MCP-Adapter.
  - cli/roadmap.php --write ERZEUGT den Zustandsteil der Markdown-Datei;
    --check prueft nur die Prosa. Was erzeugt wird, kann nicht driften.
  - docs/ROADMAP_MIGRATION.md: Uebernahme fuer die anderen Projekte.

TESTINSTANZ UND TESTTOR

  - dc-dev.mhdf.de: zweite Installation, eigene DB, eigene Schluessel,
    Basic-Auth davor, Rocket.Chat aus.
  - 124 Testfaelle in tests/e2e gegen API, Rechte, Migrationen, Sperre.
  - scripts/deploy.py bringt vor jedem Upload erst den anstehenden Stand auf
    die Testinstanz und laesst dann den Testlauf laufen. Erst bei
    Rueckgabewert 0 geht etwas auf die Produktion (--ohne-test als Notausgang).
    Andersherum prueft der Lauf aelteren Code als den, der gleich hochgeht.
  - tests/ ist aus dem Produktiv-Deploy ausgeschlossen; testctl.php kann
    nicht versehentlich auf dc.mhdf.de landen.
  - Ein Lauf mit mehr uebersprungenen als geprueften Faellen endet mit
    Rueckgabewert 2 statt 0. Nichts ist kaputt, geprueft wurde es aber auch
    nicht - der Unterschied darf nicht am Rueckgabewert verschwinden.

SUPERVISOR, PROJEKTLANDSCHAFT, NACHPRUEFUNG (2.6.0/2.6.1)

  - Migration 013: projektuebergreifend melden (dev_agent, evidence,
    triage_external, affects).
  - Migration 014: supervisor_findings, Katalog, fuenf serverseitige
    Detektoren im Tick.
  - Migration 015: Nachpruefung als Arbeitsschlange statt Benachrichtigung.
  - DEV-Lizenzschluessel zur Selbstbedienung, dc-testkit fuer die Test-VM.
  - MCP-Adapter: 15 Werkzeuge, ohne Fremdabhaengigkeiten, mit Abgrenzung
    von Fremddaten gegen Prompt-Injektion.

BEHOBEN

  - observe und status im Supervisor legten den Zustand des Befundes als
    "status" in die Antwort. Http::ok() merged ['status' => 'success']
    darunter, also gewann der Befundzustand - eine angenommene Meldung kam
    als status="open" an. Heisst jetzt finding_status. War Item #59.

Migrationen 013-016 sind auf dem Produktivserver noch NICHT ausgefuehrt.
Ohne sie bricht das WebUI, weil die Roadmap-Ansicht bei jedem Seitenaufruf
gegen bugtracker_items.epic abfragt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Deploymentcenter Bot
2026-08-29 21:59:20 +02:00
co-authored by Claude Opus 5
parent 9d0261306c
commit 1d5ac92051
52 changed files with 15261 additions and 103 deletions
+132 -4
View File
@@ -21,8 +21,35 @@ Die vollständige Schnittstellenbeschreibung liegt maschinenlesbar unter
### Projekt bestimmen
`GET /api/bugtracker/v1/projects` liefert alle Slugs.
Bekannt: `deploymentcenter`, `myapp`, `polytrader`, `predictalytics`.
Fällt dir ein Fehler im Deployment Center selbst auf, melde ihn unter
`deploymentcenter`.
**Melde in dem Projekt, in dem die Ursache liegt — nicht in dem, in dem du
gerade arbeitest.** Ein Fehler im SDK, im `update-agent`, in der Lizenzprüfung
oder im Bugtracker gehört unter `deploymentcenter`, auch wenn er dir bei der
Arbeit an etwas anderem aufgefallen ist. Legst du ihn im eigenen Projekt ab,
liest ihn niemand, der ihn beheben könnte.
### In ein fremdes Projekt melden
Dabei gelten drei Regeln, die beim eigenen Projekt nicht greifen:
1. **`evidence` ist Pflicht**, mindestens 40 Zeichen — sonst `400
missing_evidence`. Beantworte darin: *Was wurde beobachtet, und warum liegt
die Ursache dort und nicht bei mir?* Der Grund für die Hürde: Du kannst
selten sicher unterscheiden, ob das fremde Projekt kaputt ist oder ob du es
falsch benutzt.
2. **Der Schweregrad wird auf `medium` gedeckelt.** `high` und `critical`
vergibt die empfangende Seite — nur sie weiß, wer sonst noch betroffen ist.
Was dringlich ist, schreibst du in die Begründung.
3. **Die Meldung landet in `triage_external`, nicht in `open`.** Such sie
also nicht in der offenen Liste des fremden Projekts.
`reported_from` wird aus deinem Token abgeleitet und **nie** aus dem Body
übernommen — du kannst dich nicht als jemand anderes ausgeben. Meldet ein
zweites Projekt dieselbe Sache, entsteht kein neues Item; es wird als weiterer
Betroffener geführt (`affects`).
Voraussetzung ist ein **Werkbank-Token** (`owner_type: dev_agent`). Ein
Laufzeit-Token darf nicht projektübergreifend melden — es steckt in der
ausgelieferten Anwendung und ist extrahierbar.
### Etwas melden
`POST /api/bugtracker/v1/report`
@@ -108,15 +135,111 @@ POST /api/bugtracker/v1/manage?action=resolve&id=<ID>
Body: {"resolved_in_build": "v1.4.3", "resolution_notes": "Was geändert wurde"}
```
### Prüfaufträge erkennen
Kommt ein Item mit Status `in_verification` aus `?action=next`, ist die Frage
**nicht** „behebe das", sondern „trägt der Fix?". Der Fehler gilt als behoben;
in `resolved_in_build` steht, in welcher Fassung. Gegen diese Fassung testen,
dann eines von beidem:
- **trägt** → `?action=status` mit `{"status": "closed"}` und einer Notiz, was
geprüft wurde
- **trägt nicht** → den Fehler ganz normal erneut melden. Das Item wechselt von
selbst auf `fix_insufficient` und liegt wieder in der Arbeitsschlange — es
entsteht **kein** neues Item
Wer den Status übersieht, behebt einen bereits behobenen Fehler ein zweites Mal.
**Schicke bei jeder Meldung `build_version` mit.** Daran entscheidet der Server,
ob dein Befund eine Regression ist, ein unzureichender Fix, oder ob du schlicht
eine ältere Fassung prüfst als die, in der der Fix steckt. Ohne die Angabe fällt
die Entscheidung im Zweifel auf den ungünstigsten Ausgang.
Kommst du nicht weiter, gib das Item zurück statt es blockieren zu lassen:
```
POST /api/bugtracker/v1/manage?action=release&id=<ID>
Body: {"note": "Grund"}
```
### Die Roadmap des Projekts
**Vor der Arbeit aufrufen, nicht nachher.** Der Planungsstand steht im
Deploymentcenter, nicht in einer Markdown-Datei:
```
GET /api/bugtracker/v1/roadmap.php?action=list&status=aktiv
```
Mit MCP-Adapter: `dc_roadmap`. Die Antwort beantwortet zwei Fragen, die man
sich sonst falsch beantwortet: *Was ist als Nächstes dran?* und *arbeitet
schon jemand daran?*
Eine `ROADMAP.md` im Projektverzeichnis zeigt nur eine **erzeugte Kopie**
zwischen `<!-- dc:roadmap … -->`-Markern. Sie darf älter sein als der echte
Stand. Was zwischen den Markern steht, niemals von Hand ändern — der nächste
Lauf überschreibt es.
Ein Vorhaben auf die Roadmap heben heißt: ihm einen Themenbereich geben.
```
dc_roadmap_set(id: 142, epic: "A", roadmap_rank: 10, target_build: "1.5.0",
doc_anchor: "ROADMAP.md#142-titel")
```
Zurückstellen nur **mit Auslöser** — woran erkennt man, dass „später" jetzt
ist? Ohne diese Angabe wird der Aufruf abgelehnt, und das ist Absicht: Ein
zurückgestelltes Vorhaben ohne Anlass ist ein vergessenes.
```
dc_roadmap_set(id: 147, status: "deferred",
defer_trigger: "sobald strukturierte Logauszüge hereinkommen")
```
**Steht etwas auf `rejected` oder `deferred`, lies erst die Begründung**, die
`doc_anchor` nennt, bevor du es erneut vorschlägst. Ein neuer Vorschlag
braucht ein Argument, das dort noch nicht steht.
**Was nur in einer Datei steht, ist keine eingeplante Arbeit.** Die
Arbeitsschlange ist `?action=next`; was dort nicht auftaucht, macht niemand.
Ein Punkt, der etwas werden soll, muss ein Item sein.
Erstmalige Übernahme einer vorhandenen Roadmap-Datei:
`Deploymentcenter/docs/ROADMAP_MIGRATION.md`.
### Auf der Linux-Testumgebung
Arbeitest du auf der geteilten Test-VM, benutze `dc-testkit` statt eigener
`curl`-Aufrufe — es holt den DEV-Lizenzschlüssel, prüft Auslieferung und
Update und meldet, was scheitert:
```
dc-testkit doctor # Werkzeuge, Netz, Token, Projekt
dc-testkit license # befristeten DEV-Schlüssel holen (wiederholbar)
dc-testkit run # ausliefern, prüfen, aktualisieren; meldet Fehlschläge
dc-testkit projects # welche Slugs gibt es?
dc-testkit report --type bug --title "…" --desc "…"
dc-testkit next # auch Prüfaufträge; der Hinweis dazu steht in der Ausgabe
dc-testkit confirm <id> "was geprüft wurde"
```
Was dir **an der Testumgebung selbst** auffällt — ein fehlendes Werkzeug, eine
Konvention, die nicht trägt, eine Stelle in der Anleitung, die nicht stimmt —
gehört ebenfalls gemeldet, unter `deploymentcenter`. Die Testumgebung ist kein
eigenes Projekt; Skript und Anleitung liegen in dessen Repo:
```
dc-testkit report --project deploymentcenter --type feature --severity wishlist \
--title "…" --desc "…" --evidence "…"
```
**Nimm nie einen Kundenlizenzschlüssel auf die Test-VM.** Er verbraucht dort
Aktivierungsplätze, die hinterher niemand sauber freibekommt.
Anleitung: `Deploymentcenter/docs/TESTUMGEBUNG.md`, Fremdmeldungen in §4.6.
### Release melden
Nach einem Release schließen sich Items mit passendem `resolved_in_build`
automatisch:
Beim Veröffentlichen werden Items mit passendem `resolved_in_build`
fortgeschrieben: noch offene werden abgeschlossen, bereits als gelöst markierte
gehen auf `awaiting_verification` — der Fix ist jetzt ausgeliefert und damit
erstmals prüfbar. Sie tauchen danach als Prüfauftrag in `?action=next` auf.
```
POST /api/updateservice/v1/publish
Body: {"product_slug": "myapp", "version": "1.4.3",
@@ -171,6 +294,11 @@ description, error_message, stack_trace, severity, file_path, line_no,
client_ref). Vor der Arbeit an einem Item: POST .../manage?action=next zum
Übernehmen. Danach ?action=resolve mit resolved_in_build.
Planungsstand: GET .../bugtracker/v1/roadmap.php?action=list&status=aktiv —
was als Nächstes dran ist und wer schon dransitzt. Eine ROADMAP.md im Repo
zeigt nur eine erzeugte Kopie; zwischen den dc:roadmap-Markern nichts von Hand
ändern.
Deploymentcenter-Anbindung: SDK 2.5.1
Bevor du sie anfasst: GET /api/updateservice/v1/changelog?since=2.5.1 —
zeigt nur, was seitdem dazukam. Danach die Zeile hochsetzen.
+28 -4
View File
@@ -9,9 +9,13 @@ Monitoring und einen Bugtracker, den Coding-Agenten selbständig bedienen.
| Dokument | Wofür |
|---|---|
| **[ROADMAP.md](./ROADMAP.md)** | **Der eine Ort für alles Geplante, Zurückgestellte und bewusst Verworfene.** Ab 2.7.0 gilt die Arbeitsteilung: Zustand im Deploymentcenter (WebUI → Roadmap, `dc_roadmap`), Begründung hier. |
| **[ROADMAP_MIGRATION.md](./ROADMAP_MIGRATION.md)** | **Eine vorhandene `ROADMAP.md` oder `TODO.md` ins Deploymentcenter überführen.** Einmalig je Projekt, sechs Schritte. Für die Agenten der angebundenen Projekte. |
| **[UPGRADE.md](./UPGRADE.md)** | **Ablaufplan für die Umstellung auf 2.0.** Enthält Pflichtschritte: Zugangsdaten wechseln, Migration, Evaluator-Cron. |
| **[Änderungen](../public/docs/changelog.json)** | Was sich je Fassung geändert hat und was zu tun ist. Öffentlich unter `/docs/changelog.php`, maschinenlesbar über `GET /api/updateservice/v1/changelog?since=X` |
| **[Release-Anleitung für Agenten](../public/docs/release.md)** | Ein Projekt veröffentlichungsfähig machen: Vorlage kopieren, konfigurieren, ausliefern |
| **[Linux-Testumgebung](./TESTUMGEBUNG.md)** | **Die geteilte Test-VM.** Wie ein Agent dort selbständig testet: DEV-Lizenz holen, Auslieferung prüfen, Befunde melden — mit `dc-testkit` |
| **[Testinstanz & automatisierter Testlauf](./TESTINSTANZ.md)** | **`dc-dev.mhdf.de` — die zweite Installation zum Kaputtmachen.** 124 Fälle gegen API, Rechte, Migrationen und Zugangssperre (`tests/e2e/run.py`). **`scripts/deploy.py` führt sie vor jedem Produktiv-Upload aus** und bricht bei Fehlschlägen ab (`--ohne-test` umgeht das) |
| [Agent-Prompt-Vorlage](./AGENT_PROMPT_TEMPLATE.md) | Textbaustein für `CLAUDE.md` / `AGENTS.md` eines Projekts |
| [Agenten-Handbuch](../public/docs/bugtracker.md) | Vollständige Beschreibung des Bugtracker-Workflows, öffentlich unter `/docs/` |
@@ -22,6 +26,13 @@ Monitoring und einen Bugtracker, den Coding-Agenten selbständig bedienen.
- **[UpdateService](./UPDATESERVICE_INTEGRATION_GUIDE.md)** — Release-Verteilung und Update-Prüfung
- **[Erstinstallation](./SETUP_INTEGRATION_GUIDE.md)** — `setup.json`, Installationskonto, `update-agent --action install`
- **[Bugtracker](./BUGTRACKER_INTEGRATION_GUIDE.md)** — Anbindung aus Anwendungen heraus
- **[Supervisor](./SUPERVISOR_INTEGRATION_GUIDE.md)** — Befunde: was ist ungewöhnlich? Läuft serverseitig auf ohnehin eingehenden Daten, **ohne Einbau in die Anwendungen**. Enthält auch den Rückbau begonnener Vorbereitungen
- **[MCP-Adapter](../mcp/README.md)** — Claude Code und andere Werkzeuge direkt an Bugtracker, Roadmap und Supervisor anschließen
Hintergrund zu Supervisor und MCP, inklusive der Frage, warum der Client keinen
Quelltext sieht: **[archiv/SUPERVISOR_MCP_DESIGN.md](./archiv/SUPERVISOR_MCP_DESIGN.md)**
— archiviert, die Herleitung gilt weiter, der Stand steht in der
[Roadmap](./ROADMAP.md).
---
@@ -32,7 +43,8 @@ Monitoring und einen Bugtracker, den Coding-Agenten selbständig bedienen.
| **Bugtracker** | Fehler, Feature Requests und Ideen; Agenten-Workflow mit Claim/Lease | `/api/bugtracker/v1/report`<br>`/api/bugtracker/v1/projects`<br>`/api/bugtracker/v1/manage` | Token mit `bugtracker:*` |
| **UpdateService** | Release-Verteilung, semantischer Versionsvergleich | `/api/updateservice/v1/check`<br>`/api/updateservice/v1/publish` | Lesen offen, Publish braucht `updateservice:publish` |
| **Watchdog** | Heartbeat-Monitoring, Zustandsbewertung, Alarmierung | `/api/watchdog/v1/ping`<br>`/api/watchdog/v1/evaluate` | Token mit `watchdog:ping` |
| **Lizenzen** | Lizenzprüfung, Hardware-ID v2, Offline-Cache | `/api/license/v1/validate`<br>`/api/license/v1/deactivate` | Validierung offen, Deaktivierung authentifiziert |
| **Supervisor** | Befunde: was ist ungewöhnlich? Fünf Detektoren laufen serverseitig im Tick und brauchen keine Client-Änderung | `/api/supervisor/v1/observe`<br>`/api/supervisor/v1/findings`<br>`/api/supervisor/v1/catalog` | Melden `supervisor:report`, Lesen `supervisor:read`, Einstufen `supervisor:manage` |
| **Lizenzen** | Lizenzprüfung, Hardware-ID v2, Offline-Cache, befristete DEV-Schlüssel zur Selbstbedienung | `/api/license/v1/validate`<br>`/api/license/v1/deactivate`<br>`/api/license/v1/dev-key` | Validierung offen, Deaktivierung authentifiziert, DEV-Schlüssel `license:dev` |
| **Tokens** | Selbst-Provisionierung von Sub-Tokens | `/api/tokens/v1/provision` | Master-Token |
| **Setup** | Erstinstallation: Anmeldung, Katalog, Anwendungstoken | `/api/setup/v1/login`<br>`/api/setup/v1/catalog`<br>`/api/setup/v1/token` | Login offen, Rest `setup:*` |
| **System** | Verfügbarkeit, Schema-Status, Schnittstellenbeschreibung | `/api/health`<br>`/api/openapi.json` | Health optional, OpenAPI offen |
@@ -60,7 +72,9 @@ nicht sofort wiederholen.
|---|---|---|
| `/api/errors/v1/report` | **300** | `bugtracker.error_rate` |
| `/api/bugtracker/v1/report` | 60 | `bugtracker.report_rate` |
| `/api/supervisor/v1/observe` | 120 | `supervisor.observe_rate` |
| `/api/license/v1/*` | 120 | fest |
| `/api/license/v1/dev-key` | 10 | fest — ein Schlüssel je Testlauf, nicht je Sekunde |
| `/api/updateservice/v1/*` | 240 | fest |
| `/api/setup/v1/login` | 10 | fest — dort werden Passwörter geprüft |
| `/api/tokens/v1/provision` | 20 | fest |
@@ -99,13 +113,23 @@ Menschen und kann sich ändern.
```
config/ Zugangsdaten (nicht versioniert), Vorlage in config.example.php
src/ Anwendungscode, PSR-4 unter dem Namensraum Deploymentcenter\
Core/ Bootstrap, Konfiguration, DB, Auth, CSRF, HTTP, Tokens, Migrator
Modules/ Bugtracker, License, UpdateService, Watchdog, Notify
public/ Webroot-Inhalte: WebUI, API-Endpunkte, öffentliche Dokumentation
Core/ Bootstrap, Konfiguration, DB, Auth, CSRF, HTTP, Tokens, Migrator,
ProjectLinks (welches Projekt nutzt welches, in welcher Fassung)
Modules/ Bugtracker, License, Supervisor, UpdateService, Watchdog, Notify
public/ Webroot-Inhalte: WebUI, API-Endpunkte, öffentliche Dokumentation,
testkit/dc-testkit.sh (das Werkzeug für die Linux-Test-VM)
sql/ Schema und Migrationen (fortlaufend nummeriert)
var/log/ Laufzeitprotokolle
scripts/ Deployment
mcp/ MCP-Adapter für Claude Code — eine Datei, keine Abhängigkeiten
client-dotnet/ Client-SDK: Lizenz, Update, Setup
```
Der Supervisor läuft ausschließlich hier — an den Detektoren in
`Modules/Supervisor/ServerDetectors.php`
im minütlichen Tick mit (gedrosselt auf zehn Minuten). Er arbeitet auf Daten,
die ohnehin hereinkommen — Fehlerberichte, Heartbeats, Kennzahlen — und
braucht dafür keine Änderung an den Anwendungen.
Neue Klassen werden automatisch geladen, sobald sie dem Namensraum-Pfad
entsprechen — eine `require`-Zeile ist nicht mehr nötig.
+618
View File
@@ -0,0 +1,618 @@
# Roadmap
Der eine Ort für alles Geplante, Zurückgestellte und bewusst Verworfene.
Bis 2.6.0 lagen diese Angaben über mehrere Entwurfsdokumente verstreut — mit
der Folge, dass eine Liste offener Punkte noch als offen galt, obwohl der Code
sie längst erledigt hatte. Deshalb: **Was hier steht, gilt. Wer etwas umsetzt,
zieht den Eintrag hier nach.**
Die abgelösten Entwürfe liegen unter [archiv/](./archiv/) — sie enthalten die
ausführliche Begründung, aber keinen gültigen Stand mehr.
---
## Status-Kennzeichnung
| | Bedeutung |
|---|---|
| ✅ | Erledigt und ausgeliefert |
| 🔨 | In Arbeit |
| 📋 | Geplant, als Nächstes dran |
| 🧊 | **Zurückgestellt** — bewusst nicht jetzt, Auslöser genannt |
| ❌ | **Verworfen** — mit Begründung, nicht erneut vorschlagen |
| ⚙️ | Betriebsaufgabe, kein Code |
---
## Als Nächstes
1. **[I8] Migration 016 ausführen** — ohne sie steht das Roadmap-Modul, das
2.7.0 mitbringt, vor leeren Spalten
2. **[I9] Diese Datei selbst übernehmen** — das Verfahren einmal hier
durchlaufen, bevor die anderen Projekte es aus der Anleitung bekommen
3. **[B1] Befunde in der Oberfläche** — sie sind derzeit nur über API und MCP
erreichbar. Ohne Sicht auf die Befunde ist alles Weitere blind
4. **[G5] `dc-testkit findings`** — dasselbe von der Test-VM aus, und
Voraussetzung für G8
5. **[G8] Testartefakt oder echter Befund?** — die Frage, an der sich
entscheidet, ob Befunde aus Testläufen etwas wert sind
6. **[A4] Symbol-Upload und `dc_symbolicate`** — Stacktraces aus gestrippten
Binaries auflösen, ohne dass Quelltext das Haus verlässt
Davor stehen die Betriebsaufgaben ⚙️ aus Abschnitt E — ohne sie laufen zwei
der fünf serverseitigen Detektoren gar nicht.
**Diese Liste ist die letzte ihrer Art.** Ab I9 ergibt sie sich aus Reihenfolge
und Status und wird erzeugt, statt hier fortgeschrieben zu werden — sie ist
genau die Sorte Angabe, die von Hand zuverlässig veraltet.
Nicht mehr auf der Liste: der Supervisor im ausgelieferten Programm. Verworfen,
siehe H.
---
## A — Supervisor und Befunde
Der Supervisor beantwortet die Frage „ist das ungewöhnlich?". Die Frage „ist
das schlimm?" beantwortet das Deploymentcenter, wo Verlauf, Flotte und
Quelltext bekannt sind. Diese Trennung ist die Grundentscheidung des ganzen
Entwurfs — siehe [archiv/SUPERVISOR_MCP_DESIGN.md](./archiv/SUPERVISOR_MCP_DESIGN.md),
Abschnitt 0.
> **Der Supervisor lebt nur noch auf der Serverseite.** Ein Beobachter im
> ausgelieferten Programm ist verworfen (H) — er hätte zu viele Baustellen
> aufgemacht und wäre den Mehraufwand nicht wert gewesen. Was bleibt:
>
> - die **serverseitigen Detektoren**, die auf ohnehin eingehenden Daten
> arbeiten und nichts in den Anwendungen brauchen
> - der Eingang `POST /api/supervisor/v1/observe` für alles, was von außen
> meldet — heute Prüfwerkzeuge und Testläufe
> - der **Katalog** als gemeinsame Sprache für Befundnamen
>
> Der Zweck, den ein Client-Beobachter erfüllt hätte, ist bei
> **Error-Reporting und Bugtracker** besser aufgehoben. Die werden
> weiterentwickelt.
| | Vorhaben | Stand |
|---|---|---|
| A1 | Detektor-Katalog mit stabilen Namen (`Modules/Supervisor/Catalog.php`) | ✅ 2.6.0 |
| A2 | Fünf serverseitige Detektoren im Tick: `novel.error_signature`, `silence.heartbeat`, `deviation.self`, `deviation.fleet`, `regression.build` | ✅ 2.6.0 |
| A3 | ~~Supervisor-Client für .NET und PHP~~ | ❌ verworfen, siehe H |
| A4 | **Symbol-Upload und `dc_symbolicate`** | 📋 |
| A5 | `novel.log_template` — Logzeilen-Schablonen, die noch nie auftraten | 🧊 |
| A6 | Längsschnitt für `regression.build` | 🧊 |
| A7 | Schwellen der serverseitigen Detektoren an echten Daten nachprüfen | 📋 |
### A4 — Symbol-Upload und `dc_symbolicate` 📋
Der Packager lädt beim `publish` die PDB bzw. Sourcemap mit hoch, verknüpft mit
`commit_sha`. Ausgeliefert wird ein gestripptes Binary. Der Kunde bekommt
`at 0x4a2f`, du bekommst `UserAuthService.cs:42`.
Der Gewinn: Berichte werden so gut wie mit einem Debug-Build, **ohne dass
Quelltext das Haus verlässt**. Das ist der Hebel, der den größten Nutzen pro
Aufwand bringt — und die Voraussetzung dafür, dass ein Diagnose-Agent mit einer
Fehlermeldung überhaupt etwas anfangen kann.
Betroffen: `client-dotnet/Deploymentcenter.Packager`, ein Ablageort für
Symboldateien, `dc_symbolicate` im MCP-Adapter.
### A5 — `novel.log_template` 🧊
**Auslöser:** sobald Logauszüge strukturiert hereinkommen. Heute liefert nichts
sie in einer Form, aus der sich Schablonen bilden ließen.
### A6 — Längsschnitt für `regression.build` 🧊
`regression.build` vergleicht heute die beiden höchsten Fassungen im Feld im
**Querschnitt** — verschiedene Maschinen zur selben Zeit, nicht dieselbe
Maschine vor und nach dem Update. Bei gemischten Kohorten kann ein Unterschied
deshalb auch von der Maschine kommen statt von der Fassung.
**Auslöser:** wenn Fehlzuordnungen auffallen. Nötig wäre eine Historie der
`app_version` je Monitor; das Feld wird heute überschrieben.
### A7 — Schwellen nachprüfen 📋
Die serverseitigen Detektoren rechnen mit gesetzten, aber nirgends
nachgeprüften Werten: `SIGMA = 3.0` für `deviation.self` und `deviation.fleet`,
`MIN_COHORT = 5`, `REGRESSION_RATIO = 1.25`, `NOVEL_WINDOW_MIN = 120` — alle in
`ServerDetectors`.
Beurteilbar erst, wenn eine Weile echte Daten durchgelaufen sind. Die Frage
lautet für jeden Wert gleich: Wie viele der erzeugten Befunde hat jemand als
brauchbar eingestuft? Der Status `ignored` an den Befunden beantwortet das von
selbst, sobald gesichtet wird.
---
## B — Oberfläche
| | Vorhaben | Stand |
|---|---|---|
| B1 | **Befunde im WebUI: Liste, Filter nach Detektor, Einstufen** | 📋 |
| B2 | Fremdmeldungen sichtbar (`triage_external` in Filter, Abzeichen, Bearbeiten) | ✅ 2.6.0 |
| B3 | Monitoren ein Projekt zuweisen | 📋 |
### B1 — Befunde im WebUI 📋
Befunde sind heute nur über `/api/supervisor/v1/findings` und die MCP-Werkzeuge
erreichbar. Für die Sichtung durch einen Menschen fehlt die Ansicht — und damit
auch der einzige Weg, `supervisor:manage` sinnvoll zu nutzen.
Gebraucht wird: Zählung je Detektor als Einstieg, dahinter die Einzelbefunde
mit `observed`/`baseline`, und die Statuswechsel (`acknowledged`, `promoted`,
`resolved`, `ignored`). Beim Übernehmen in ein Bugtracker-Item die Verknüpfung
über `supervisor_findings.bug_item_id` setzen — die Spalte gibt es bereits.
### B3 — Monitoren ein Projekt zuweisen 📋
`watchdog_monitors.project_slug` wird administrativ gesetzt, nicht vom Agenten
gemeldet. Dafür fehlt das Bedienelement in der Monitor-Verwaltung. Bis dahin
nur per SQL — siehe E1.
---
## C — Projektlandschaft
| | Vorhaben | Stand |
|---|---|---|
| C1 | Projektübergreifend melden (`dev_agent`, `evidence`, `triage_external`, `affects`) | ✅ 2.6.0 |
| C2 | Beziehungen zwischen Projekten (`dc_project_links`, `integrated_version`) | ✅ 2.6.0 |
| C3 | **Rückkanal: „was du gemeldet hast, ist behoben"** | ✅ 2.6.1 — als Arbeitsschlange, nicht als Benachrichtigung |
| C4 | Agent-Prompt-Vorlage in den Projekten durch MCP ablösen | 📋 |
### C3 — Rückkanal ✅
Wird ein Item behoben, das ein anderes Projekt gemeldet hat, erfährt dieses
Projekt heute nichts davon. Folge: Es meldet denselben Punkt in drei Monaten
erneut und baut seinen Workaround nie zurück.
`GET /api/updateservice/v1/changelog?since=…` liefert bereits `action_items`.
Zu ergänzen ist der Abgleich über `bugtracker_item_affects`: „Item #42, das du
gemeldet hast, ist in 2.7.0 erledigt."
Das schließt den Kreis, den C1 geöffnet hat. Ohne ihn ist der Meldeweg ein
Briefkasten ohne Leerung.
**Gelöst in 2.6.1 — aber anders als hier ursprünglich gedacht.** Der Entwurf
sah eine Benachrichtigung vor. Gebaut wurde stattdessen eine Arbeitsschlange,
und das ist die bessere Antwort auf dieselbe Frage:
- **Passiv:** Wer erneut meldet, bekommt die Auskunft von selbst. Der
Report-Endpunkt vergleicht `build_version` gegen `resolved_in_build` und
antwortet mit `known_fixed_in`, statt ein Regressionsitem anzulegen.
- **Aktiv:** Ein veröffentlichter Fix stellt sein Item auf
`awaiting_verification`. Damit steht es in `?action=next` — auch beim
meldenden Fremdprojekt, über `bugtracker_item_affects`. Der Melder wird also
gefragt, ob der Fix bei ihm wirkt.
**Warum keine Zustellstrecke.** Agenten fragen ohnehin nach Arbeit. Ein Status,
der in der Warteschlangenabfrage vorkommt, *ist* die Zustellung — ohne
Speicher, ohne Entstörung, ohne Quittungen. Der Workaround-Fall, der die ganze
Diskussion trug, ist damit erledigt: Er brauchte keinen Push, sondern einen
Anlass, noch einmal hinzusehen.
Was bleibt: Wer einen Workaround gebaut hat, muss den Prüfauftrag auch
annehmen. Nimmt ihn niemand an, wird nach 30 Tagen ungeprüft geschlossen — mit
einem Vermerk, der das ausdrücklich sagt.
### C4 — Prompt-Vorlage entlasten 📋
[AGENT_PROMPT_TEMPLATE.md](./AGENT_PROMPT_TEMPLATE.md) ist in jedes Projekt
einkopiert, samt der von Hand gepflegten Versionszeile.
**Der Zuschnitt hat sich mit 2.6.1 geändert.** Ursprünglich stand hier
„durch MCP ablösen". Inzwischen gibt es drei Wege zum Deploymentcenter, und
keiner löst die anderen ab:
| Wo der Agent arbeitet | Weg | Was er braucht |
|---|---|---|
| Projektverzeichnis mit `.mcp.json` | MCP-Adapter | keine Vorlage |
| Linux-Test-VM | `dc-testkit` | Kurzfassung, kein `curl` |
| alles Übrige | REST direkt | die volle Vorlage |
Die Vorlage bleibt also — sie ist der Rückfallweg und beschreibt zugleich, was
`dc-testkit` tut. Zu tun ist trotzdem etwas: In Projekten **mit** eingerichtetem
Adapter kann sie auf die Kurzfassung schrumpfen, und die handgepflegte
Versionszeile ersetzt in allen Fällen `dc_link_project`.
Das ist der Punkt, an dem es sich lohnt, die Vorlage in drei Bausteine zu
zerlegen statt in einen Block: Grundlagen, „mit MCP", „auf der Test-VM". Heute
trägt jedes Projekt alles mit.
---
## D — Werkzeuganbindung (MCP)
| | Vorhaben | Stand |
|---|---|---|
| D1 | stdio-Adapter mit 13 Werkzeugen, ohne Fremdabhängigkeiten | ✅ 2.6.0 |
| D2 | Fremddaten im Werkzeugergebnis abgegrenzt kennzeichnen | ✅ 2.6.0 |
| D3 | Abgleich: Werkzeugliste gegen `openapi.php` | 📋 |
| D4 | Entfernter MCP-Server unter `/mcp` mit OAuth | 🧊 |
| D5 | `dc_roadmap` und `dc_roadmap_set` — Planungsstand als Werkzeug | ✅ 2.7.0 (siehe I) |
### D3 — Abgleich Werkzeugliste ↔ OpenAPI 📋
Adapter und OpenAPI beschreiben dieselben Endpunkte. Ohne Abgleich laufen sie
auseinander, und es fällt erst auf, wenn ein Werkzeug ins Leere greift.
Das Projekt hat **keine CI-Strecke**. Als Erstes reicht deshalb ein Skript, das
von Hand oder aus `deploy.py` heraus läuft. Eine echte Pipeline wäre ein
eigenes Vorhaben und lohnt erst, wenn es mehr zu prüfen gibt als diese eine
Zusage.
### D4 — Entfernter Server 🧊
**Auslöser:** wenn mehrere Maschinen oder Personen dranhängen. Dann aber mit
OAuth statt Bearer-Token in einer Konfigurationsdatei — sonst liegt dasselbe
Token auf jeder Maschine. Solange der stdio-Adapter je Projekt reicht, lohnt
der Aufwand nicht.
---
## E — Betrieb
Kein Code, aber Voraussetzung dafür, dass Gebautes wirkt.
| | Aufgabe | Stand |
|---|---|---|
| E1 | Migrationen 013 und 014 auf dem Server ausführen | ⚙️ |
| E2 | PHP-Seite von 2.6.0 prüfen — auf der Entwicklungsmaschine lief kein Lint | ⚙️ |
| E3 | Monitoren ein Projekt zuordnen (`watchdog_monitors.project_slug`) | ⚙️ |
| E4 | Je Projekt ein Werkbank-Token (`dev_agent`) anlegen | ⚙️ |
| E5 | Supervisor-Vorbereitungen in den Projekten zurückbauen | ⚙️ |
**E3 ist Voraussetzung für `deviation.fleet` und `regression.build`.** Ohne
Zuordnung überspringen beide Detektoren die betroffenen Monitore; die übrigen
drei laufen weiter.
**E4 vor E5.** Ohne Werkbank-Token laufen `dc_link_project` und `dc_report` aus
den Projekten heraus ins Leere.
**E2 gehört auf die Test-VM.** Die gesamte PHP-Seite von 2.6.0 — Migrationen
013 und 014, der Supervisor-Endpunkt, das projektübergreifende Melden — wurde
auf einer Maschine ohne PHP geschrieben. Weder Lint noch ein Lauf haben sie je
gesehen. Seit 2.6.1 gibt es die geteilte Test-VM (Abschnitt G); dort gehört das
geprüft, bevor es auf den Produktivserver geht. Mindestens:
```bash
find src public cli -name '*.php' -print0 | xargs -0 -n1 php -l
php cli/migrate.php
php cli/tick.php # ohne --quiet: zeigt den Detektorlauf
```
**E5 ist reiner Rückbau geworden.** In mehreren Projekten wurden
Vorbereitungen für einen lokalen Supervisor begonnen — UI-Steuerelemente,
teilweise ein KI-Agent. Da der Client-Supervisor verworfen ist (H), fällt das
ersatzlos weg; nichts tritt an seine Stelle. Was an den Vorbereitungen wertvoll
ist, ist die Überlegung, **worauf** zu achten wäre — die gehört als Prüfung in
die Testläufe oder als Fehlermeldung in den Bugtracker, nicht in ein
Beobachterteil.
Der Auftrag zum Einfügen steht in
[SUPERVISOR_INTEGRATION_GUIDE.md](./SUPERVISOR_INTEGRATION_GUIDE.md).
---
## F — Quelltext-Zugriff für die Diagnose
Die Software bleibt geschlossen. Es gibt keine öffentliche Stelle, an der
Quelltext zu sehen ist. Drei Stufen, in aufsteigender Preisgabe:
| | Stufe | Stand |
|---|---|---|
| F1 | **Stufe 0** — Diagnose-Agent läuft auf eigener Maschine mit lokalem Checkout | ✅ Der gegenwärtige Weg |
| F2 | **Stufe 1** — Quelltext-Index im DC, nur Ausschnitte | 🧊 |
| F3 | **Stufe 2** — nur Metadaten: Signaturen, Aufrufgraph, keine Rümpfe | 🧊 |
### F2 — Quelltext-Index Stufe 1 🧊
**Auslöser:** wenn ein Agent autonom rund um die Uhr laufen soll, ohne dass
eine eigene Maschine läuft. Solange Claude Code den Checkout ohnehin lokal hat,
wäre das zusätzliche Angriffsfläche ohne Gegenwert.
Falls es kommt, dann als Schnittstelle, die **nie ein Ganzes herausgibt**:
```
GET /api/source/v1/snippet?commit=a21536f&file=…&line=42&context=40
GET /api/source/v1/search?q=…&limit=20
```
Neuer Scope `source:read`, nie an Client-Token, verschlüsselt abgelegt, Budget
je Token und Tag, jeder Abruf im Prüfprotokoll. Der Unterschied zwischen „darf
nachschlagen" und „darf mitnehmen".
### F3 — Stufe 2 🧊
**Auslöser:** falls selbst Ausschnitte zu viel sind. Signaturen, Aufrufgraph
und Doc-Kommentare ohne Methodenkörper reichen zur Triage, aber nicht zum
Nachbauen.
---
## G — Testumgebung und Selbstbedienung
Die geteilte Linux-Test-VM. Anleitung: **[TESTUMGEBUNG.md](./TESTUMGEBUNG.md)** — dort
steht auch, was auf der Maschine belegt ist.
| | Vorhaben | Stand |
|---|---|---|
| G1 | **Kopfloser Installationsmodus fuer den `update-agent`** | 📋 |
| G2 | DEV-Lizenzschluessel zur Selbstbedienung (`POST /api/license/v1/dev-key`, Scope `license:dev`) | ✅ 2.6.1 |
| G3 | `dc-testkit` — ein Weg fuer Lizenz, Auslieferung und Meldung | ✅ 2.6.1 |
| G4 | Werkbank-Token je Projekt auf der Test-VM hinterlegen | ⚙️ |
| G5 | **`dc-testkit supervisor` — prüfen, ob eine Anbindung wirklich meldet** | 📋 |
| G6 | Test-Monitore aus dem Flottenvergleich heraushalten | 📋 |
| G7 | Befunde tragen die Umgebung, ausserhalb `production` eine Stufe niedriger | ✅ 2.6.1 |
| G8 | **Testartefakt oder echter Befund? — die offene Frage** | 📋 |
| G8 | Nachpruefung als Arbeitsschlange (`awaiting_verification`, `in_verification`, `fix_insufficient`) | ✅ 2.6.1 |
| G9 | Auswertung über `meta_json`: wie oft fällt ein Fix durch die Prüfung? | 🧊 |
### G1 — Kopfloser Installationsmodus 📋
`update-agent --action install` ist ein Dialog mit Auswahllisten und Passwortabfrage. Ein
Agent kann ihn nicht treiben: Auswahllisten brauchen Pfeiltasten an einem echten Terminal,
und ein Agent darf grundsaetzlich kein Passwort eingeben.
Folge: `dc-testkit install` prueft die **Auslieferung** in ein leeres Verzeichnis — denselben
Pfad, den der Installer intern nimmt, mit Download, SHA256, Signatur, Staging und Rollback.
Ungeprueft bleiben damit die Aufloesung von `setup.json` und das Schreiben der Konfiguration.
Das ist genau der Teil, der je Anwendung anders aussieht und deshalb am ehesten bricht.
Gebraucht wird `--action install --answers antworten.json`: dieselben Felder, die der Dialog
erfragt, aus einer Datei. Fehlt ein Pflichtfeld, bricht der Lauf ab, statt zu fragen. Das
Installationskonto bliebe dabei aussen vor — mit einem DEV-Lizenzschluessel kommt der Agent
ohnehin an die Release-Ablage.
Betroffen: `client-dotnet/Deploymentcenter.UpdateAgent/Installation.cs`.
### G5 — `dc-testkit findings` 📋
Die Nahtstelle zwischen Testumgebung und Supervisor, die heute fehlt.
Ein Testlauf erzeugt Befunde — teils durch `novel.error_signature` aus den
gemeldeten Fehlern, teils künftig direkt über `observe`. Sehen kann man sie von
der VM aus nicht: `dc-testkit` kennt `doctor`, `license`, `install`, `update`,
`run`, `projects` und `report`, aber nichts zum Supervisor.
Gebraucht wird wenig:
```
dc-testkit findings # was hat dieser Lauf ausgelöst?
dc-testkit findings --env all # auch das, was in production auffiel
```
Dahinter `GET /api/supervisor/v1/findings?project_slug=…`. Derselbe Zuschnitt
wie `dc_findings` im MCP-Adapter — die Abfrage existiert, es fehlt nur der Weg
dorthin von der VM aus.
Das ist zugleich die Voraussetzung für G8: Ohne Sicht auf die eigenen Befunde
kann niemand entscheiden, ob einer ein Artefakt des Prüfstands war.
Das Token braucht dafür zusätzlich `supervisor:read`; heute trägt es auf der
VM nur `bugtracker:manage` und `license:dev` (§4.2 der Anleitung).
### G6 — Test-Monitore aus dem Flottenvergleich 📋
`deviation.fleet` bildet Kohorten aus (Projekt, `app_version`) und meldet, wer
daraus ausschert. `watchdog_monitors` hat **kein** Feld für die Umgebung.
Bekommt die Test-VM einen Monitor mit demselben Projekt und derselben Fassung
wie die Produktivinstallationen, landet sie in deren Kohorte — und fällt dort
zwangsläufig auf: andere Maschine, andere Last, andere Plattenbelegung. Das
Ergebnis wären Befunde, die nichts über die Software aussagen, dafür aber die
Kohortenwerte verschieben und damit echte Ausreißer verdecken.
Zwei Wege, beide vertretbar:
- ein Feld `environment` an `watchdog_monitors`, und `deviation.fleet` sowie
`regression.build` sehen nur `production` an
- oder eine Kennzeichnung über `group_key`, ohne Schemaänderung
Solange auf der VM kein Monitor mit Produktivfassung läuft, ist nichts kaputt.
Es fällt nur genau dann auf, wenn es zu spät ist — deshalb hier vermerkt.
### G7 — Umgebung am Befund ✅ 2.6.1
Testläufe **sollen** Befunde erzeugen — ein Lauf, der einen Fehler auslöst, den
es vorher nicht gab, hat genau das getan, wofür er da ist. Die Detektoren
laufen deshalb in jeder Umgebung.
Unterschieden wird zweifach: über `environment` am Befund, und über den
Schweregradhinweis. Was außerhalb von `production` auffällt, ist eine Stufe
niedriger eingestuft — nicht weil es unwichtig wäre, sondern weil dort
systematisch mehr Rauschen entsteht. Wer beides gleich hoch einstuft, liest
bald keines von beiden mehr.
### G8 — Testartefakt oder echter Befund? 📋
**Die eigentlich schwierige Frage.** Ein Testlauf meldet „API-Aufruf
fehlgeschlagen". Liegt das daran, dass auf der VM keine Live-Zugangsdaten
liegen — oder wäre es beim Kunden genauso passiert?
Vollständig automatisch ist das nicht zu beantworten. Vier Ansätze, die
zusammen den Aufwand aber klein halten:
**1. Der Prüfstand sagt, was ihm fehlt.**
Die Testumgebung weiß, was sie *nicht* hat: keine Zahlungs-API, kein echter
SMTP, DEV-Lizenz statt Kundenlizenz. Wird das einmal als Liste hinterlegt und
bei jeder Meldung mitgeschickt, lässt sich ein Befund, der eine bekannte Lücke
berührt, sofort als `environment_limited` kennzeichnen. Das ist der billigste
Schritt und nutzt Wissen, das ohnehin vorhanden und stabil ist.
**2. Die Umkehrung: nicht das Scheitern zählt, sondern der Umgang damit.**
Der wichtigste Gedanke hier. Fehlende Zugangsdaten sind ein Artefakt — *wie die
Software darauf reagiert*, ist es nicht. Läuft sie in eine endlose
Wiederholung? Schluckt sie den Fehler? Bleibt Zustand halb geschrieben zurück?
Landet das Passwort im Protokoll?
Das sind Produktionsbefunde, auch wenn der Auslöser künstlich war — denn in
Produktion laufen Zugangsdaten ab, werden rotiert, fällt eine Gegenstelle aus.
**Der Prüfstand ist damit kein Störfaktor, sondern eine billige
Fehlereinspeisung.** Ein `retry.storm`, ausgelöst durch fehlende Zugangsdaten,
ist ein echter Befund.
**3. Vergleich über Umgebungen statt Beurteilung des Einzelfalls.**
Feuert derselbe Detektor mit demselben `subject` auch in `production`, ist die
Frage beantwortet. Feuert er ausschließlich in `development`, ist er
verdächtig. Das ist dieselbe Mechanik wie bei `deviation.fleet`, nur über
Umgebungen statt über Installationen — Populationen vergleichen, nicht
Einzelbefunde deuten.
**4. Einmal entscheiden, nicht jedes Mal.**
Was übrig bleibt, entscheidet ein Mensch oder ein Agent — aber **je Signatur,
nicht je Vorkommnis**. Dafür gibt es das Vorbild bereits: `IgnoreRules` im
Bugtracker (Muster, Begründung, Trefferzähler). Ein Befund, der einmal als
Artefakt eingestuft wurde, bleibt es für seine Signatur und zählt trotzdem
weiter hoch — fällt er plötzlich häufiger an, sieht man es.
Die Kosten sind damit eine Entscheidung je neuer Signatur, nicht je Lauf. Das
ist tragbar; alles darüber hinaus wäre Rätselraten mit mehr Schritten.
**Reihenfolge:** 1 und 4 zuerst — beide klein und sofort wirksam. 3 ergibt sich
fast von selbst, sobald Befunde aus beiden Umgebungen nebeneinander liegen. 2
ist keine Umsetzung, sondern eine Lesehaltung: beim Sichten nicht fragen
„warum ist das fehlgeschlagen", sondern „wie hat sich die Software dabei
verhalten".
### G9 — Auswertung der Statuswechsel 🧊
**Auslöser:** wenn die Frage „wie oft trägt ein Fix nicht?" tatsächlich gestellt
wird. Seit 2.6.1 schreibt jeder Statuswechsel strukturiertes `meta_json`
(`from`, `to`, `actor`, `since`) in `bugtracker_comments`. Damit lassen sich
Verweildauern und Durchläufe auswerten, **ohne** eine Historientabelle daneben.
Eine eigene Tabelle verdient sich erst bei Fragen über Items hinweg — Median der
Prüfdauer, Quote durchgefallener Fixes je Projekt. Vorher wäre sie eine zweite
Wahrheit über denselben Vorgang.
### G4 — Werkbank-Token auf der Test-VM ⚙️
Ohne das Token laufen `dc-testkit license` und `dc-testkit report` ins Leere. Je Projekt eines
anlegen (Besitzerart `dev_agent`, Rechte `bugtracker:manage` und `license:dev`), auf der VM
unter `~/.config/dc-testkit/` hinterlegen und in der Belegungsliste vermerken. Deckt sich mit
E4 — dasselbe Token traegt beides.
---
## I — Roadmap im Deploymentcenter
Diese Datei war bis 2.6.1 der einzige Ort für Planung — und pflegte damit
Status, Reihenfolge und Zuständigkeit von Hand, neben einem Bugtracker, der
genau diese drei Dinge bereits führt. Zwei Pfleger für dasselbe Feld.
Ab 2.7.0 gilt die Arbeitsteilung: **Zustand im DC, Begründung hier.**
| | Vorhaben | Stand |
|---|---|---|
| I1 | Migration 016: `epic`, `roadmap_rank`, `target_build`, `defer_trigger`, `doc_anchor`, Status `deferred`, Tabelle `roadmap_epics` | ✅ 2.7.0 |
| I2 | `Modules/Bugtracker/Roadmap.php` — Sicht, Reihenfolge, Erzeugung, Abgleich | ✅ 2.7.0 |
| I3 | `/api/bugtracker/v1/roadmap.php` — list, epics, next, active, render, check, set, move, reorder, epic | ✅ 2.7.0 |
| I4 | WebUI-Modul **Roadmap**: Übersicht, Liste, Aufnahmeliste, Themenbereiche | ✅ 2.7.0 |
| I5 | `cli/roadmap.php --write/--check/--list` | ✅ 2.7.0 |
| I6 | `dc_roadmap`, `dc_roadmap_set` im MCP-Adapter | ✅ 2.7.0 |
| I7 | Übernahmeleitfaden für die anderen Projekte | ✅ 2.7.0 — [ROADMAP_MIGRATION.md](./ROADMAP_MIGRATION.md) |
| I8 | **Migration 016 auf dem Server ausführen** | ⚙️ |
| I9 | **Diese Datei selbst übernehmen** — Einträge A1–H als Items anlegen, Kennungen auf Item-IDs umstellen | 📋 |
| I10 | Die anderen Projekte übernehmen (myapp, polytrader, predictalytics) | 📋 |
| I11 | `--check` in `scripts/deploy.py` einhängen | 📋 |
| I12 | Auswertung: wie lange steht ein Vorhaben in welcher Lage? | 🧊 |
### I1–I7 — der gebaute Teil ✅
Kein neues Modul und keine Roadmap-Tabelle: Ein Roadmap-Eintrag **ist** ein
`bugtracker_item`. Es fehlten nur vier Angaben, die ein Backlog nicht braucht
und eine Planung schon — Themenbereich, Reihenfolge, Zielfassung und der
Auslöser für Zurückgestelltes.
**Die Zugehörigkeit ist ein Feld.** Ein Item steht auf der Roadmap, wenn es ein
`epic` hat. Ein zweites Kennzeichen daneben wären zwei Angaben, die dasselbe
behaupten und auseinanderlaufen können.
**Die Kennung ist die Item-ID.** Handvergebene Kürzel (`A12`) erzeugen die
schlimmste Fehlerart: Beide Seiten sind für sich stimmig und meinen
Verschiedenes, weil die Kennung nach einer Löschung neu vergeben wurde.
AUTO_INCREMENT vergibt nie zweimal dieselbe Nummer — eine gelöschte Zeile
hinterlässt eine hängende Referenz, und die *meldet* der Abgleich.
**Zurückstellen verlangt einen Auslöser.** `status = deferred` ohne
`defer_trigger` wird abgelehnt, in Repository, API und Oberfläche gleichermaßen.
Ohne benannten Anlass ist ein Vorhaben nicht zurückgestellt, sondern vergessen —
und die Regel gehört an die Stelle, an der geschrieben wird, nicht nur in ein
Formular.
### I9 — diese Datei übernehmen 📋
Die Einträge A1–G9 stehen noch als handvergebene Kürzel hier. Sie werden Items;
die Abschnitte werden Themenbereiche (in Migration 016 bereits angelegt).
Danach ersetzt ein erzeugter Block die Statustabellen, und die Prosa bleibt.
**Reihenfolge:** erst I8 (Migration), dann I9, dann I11 (Abgleich im Deploy).
I10 kommt zuletzt — die anderen Projekte bekommen ein Verfahren, das hier
einmal durchlaufen ist, nicht eines aus der Anleitung.
Bis dahin gilt diese Datei unverändert weiter. Sie ist erst abgelöst, wenn der
Block darin steht.
### I12 — Verweildauern 🧊
**Auslöser:** wenn die Frage „warum dauert das so lange?" tatsächlich gestellt
wird. `status_changed_at` und das strukturierte `meta_json` aus 2.6.1 tragen
die Antwort bereits; eine Auswertung daneben lohnt erst, wenn jemand sie liest.
---
## H — Verworfen
Diese Punkte sind geprüft und abgelehnt. **Nicht erneut vorschlagen** — und
falls doch, dann nur mit einem Argument, das hier noch nicht steht.
| Vorschlag | Warum nicht |
|---|---|
| ❌ **Supervisor-Beobachter im ausgelieferten Programm** (auch regelbasiert, ohne Sprachmodell) | Zu viele Baustellen für den Ertrag. Er hätte in jeder Anwendung Einbau, Rückbau bestehender Vorbereitungen, eigenen Zustand auf Kundenhardware, ein weiteres Token im Binary und eine zweite Fassungspflege gebraucht — und dabei überwiegend das gemeldet, was Error-Reporting und Bugtracker ohnehin abdecken. Der Code war fertig und ist am 29.08.2026 wieder entfernt worden; er liegt in der Historie, falls die Abwägung je anders ausfällt. **Stattdessen:** Error-Reporting und Bugtracker weiterentwickeln, Befunde serverseitig ermitteln |
| ❌ Sprachmodell-Supervisor auf der Kundenmaschine | API-Schlüssel läge auf fremder Hardware; Kontext im Speicher fremder Maschinen; kann nicht im Minutentakt hinsehen; hat keinen Verlauf und sieht die Flotte nicht; antwortet zweimal verschieden auf dieselbe Lage |
| ❌ Quelltext mit dem Client ausliefern, auch verschlüsselt | Der Client muss entschlüsseln können, also kann es der Kunde auch |
| ❌ Deploy-Key oder Repo-Token im Client | Dekompiliert in Minuten |
| ❌ Obfuskation als Schutz einplanen | Verzögert, verhindert nicht |
| ❌ MCP-Werkzeuge 1:1 aus OpenAPI erzeugen | 24 Endpunkte; Werkzeugdefinitionen liegen dauerhaft im Kontextfenster. Ein aufgeblähter Adapter wäre schlechter als die Prompt-Vorlage, die er ersetzt |
| ❌ `publish`, `provision`, `deactivate` als MCP-Werkzeug | Ein Release gehört in die CI, nicht an ein Werkzeug, das ein Modell im Zweifel „hilfreich" auslöst |
| ❌ Befunde puffern, wenn das DC ausfällt | Eine wachsende Warteschlange in einer ohnehin auffälligen Anwendung ist genau der Schaden, den der Supervisor verhindern soll |
| ❌ Benachrichtigung je Befund | Ein Befund behauptet nicht, dass etwas kaputt ist. Wer bei jeder Auffälligkeit alarmiert, hat nach einer Woche einen Kanal, den niemand mehr liest |
| ❌ Lokale Befund-Oberfläche in den Anwendungen | Zweite Wahrheit, die nur den eigenen Ausschnitt kennt. Der Client hält keine Historie vor |
| ❌ `enforceProject()` pauschal für `dev_agent` öffnen | Hätte auch `publish` und `setup` in fremden Projekten erlaubt. Die Ausnahme wird je Aufrufstelle gesetzt |
| ❌ Feste Schwellwerte je Kennzahl als Hauptweg | Finden nur, woran vorher jemand gedacht hat. Die tragenden Detektoren vergleichen gegen eigenen Verlauf, Vorversion oder Flotte |
---
## Pflege dieser Datei
**Bis I9 abgeschlossen ist**, gilt weiterhin von Hand:
- Ein Vorhaben umgesetzt? Zeile auf ✅ setzen und die Fassung dazuschreiben.
- Etwas verworfen? Nach H, **mit Begründung**. Die Begründung ist der Wert des
Eintrags, nicht die Ablehnung.
- Etwas zurückgestellt? Nach 🧊, **mit Auslöser**. Ohne Auslöser ist es nicht
zurückgestellt, sondern vergessen.
- Ausführliche Herleitungen gehören ins Archiv, nicht hierher. Diese Datei soll
lesbar bleiben.
**Danach** gilt die Arbeitsteilung aus Abschnitt I:
| | Wahrheit | Die andere Seite hält |
|---|---|---|
| Status, Reihenfolge, Zielfassung, Bearbeiter | **Deploymentcenter** | den erzeugten Block zwischen `<!-- dc:roadmap -->`-Markern |
| Begründung, Auslöser, Verworfenes | **diese Datei**, außerhalb der Marker | den Verweis `doc_anchor` am Item |
- Ein Vorhaben entsteht **immer im DC**, nie hier. `dc_report` legt es an,
`dc_roadmap_set` hebt es auf die Roadmap, dann erst kommt die Prosa dazu.
So kann ein Punkt, der nur in einer Datei steht und den niemand angeht, gar
nicht erst entstehen.
- Zwischen den Markern **nichts von Hand ändern**. `php cli/roadmap.php --write`
überschreibt es beim nächsten Lauf.
- Vor dem Ausliefern `php cli/roadmap.php --check --project=deploymentcenter`.
Rückgabewert 1 heißt: Ein Absatz verweist auf ein Item, das es nicht gibt.
- 🧊 und ❌ behalten ihren Absatz hier. Der Statuswert allein sagt nicht, warum —
und ohne das Warum schlägt es der nächste Agent wieder vor.
+298
View File
@@ -0,0 +1,298 @@
# Roadmap ins Deploymentcenter übernehmen
**Für Agenten und Entwickler in den angebundenen Projekten.** Diese Anleitung
beschreibt, wie eine vorhandene `ROADMAP.md`, `TODO.md`, `PLAN.md` oder eine
Sammlung von Entwurfsdokumenten in das Roadmap-System des Deploymentcenters
überführt wird — einmalig, pro Projekt.
Verfügbar ab **2.7.0**. Voraussetzung: Migration `016_roadmap.sql` ist auf dem
Server gelaufen.
---
## Warum überhaupt
Vorhaben lagen bisher in Markdown-Dateien: Status, Reihenfolge und
Zuständigkeit von Hand gepflegt, neben einem Bugtracker, der genau diese drei
Dinge bereits führt. Zwei Pfleger für dasselbe Feld — Drift war keine
Möglichkeit, sondern eine Frage der Zeit.
Konkret ging dabei verloren:
- Ein Punkt stand als offen in der Datei, obwohl der Code ihn längst erledigt
hatte. Wer die Datei las, arbeitete an etwas Fertigem.
- Zwei Agenten nahmen sich dasselbe Vorhaben vor, weil die Datei nicht sagen
kann, dass jemand daran sitzt.
- Ein verworfener Vorschlag kam nach drei Monaten wieder, weil die Begründung
in einem Entwurfsdokument stand, das niemand mehr aufschlug.
## Die Arbeitsteilung
**Kein Feld hat zwei Pfleger.** Das ist die ganze Regel; alles Weitere folgt
daraus.
| | Wahrheit | Die andere Seite hält |
|---|---|---|
| Status, Reihenfolge, Zielfassung, Bearbeiter | **Deploymentcenter** | eine maschinell erzeugte Kopie zwischen Markern |
| Begründung, Auslöser, Verworfenes, Herleitung | **Die Datei** | nur einen Verweis (`doc_anchor`) |
Die Datei verschwindet also **nicht**. Sie verliert nur die Hälfte, die sie
ohnehin nie zuverlässig getragen hat.
## Die Kennung
Handvergebene Kennungen (`A12`, `TODO-7`) sind der Grund für die schlimmste
Fehlerart: Beide Seiten sind für sich stimmig und meinen Verschiedenes, weil
die Kennung nach einer Löschung neu vergeben wurde. Niemand bemerkt das.
Deshalb ist die Kennung die **Item-ID** aus dem Deploymentcenter.
AUTO_INCREMENT vergibt nie zweimal dieselbe Nummer; eine gelöschte Zeile
hinterlässt eine hängende Referenz, und die *meldet* der Abgleich, statt sie
stillschweigend umzudeuten.
Der Themenbuchstabe bleibt — aber nur als Gruppe:
```markdown
### [A] #142 — Symbol-Upload und dc_symbolicate
```
---
## Übernahme in sechs Schritten
### 1. Bestandsaufnahme
Sortiere jeden Eintrag deiner Datei in genau eine der vier Kisten:
| Kiste | Kennzeichen | Wohin |
|---|---|---|
| **Vorhaben** | hat einen Zustand, jemand könnte es übernehmen | Item im DC |
| **Zurückgestellt** | bewusst nicht jetzt, mit benanntem Anlass | Item (`deferred`) + Absatz |
| **Verworfen** | geprüft und abgelehnt, mit Begründung | Item (`rejected`) + Absatz |
| **Erledigt** | schon ausgeliefert | Item (`resolved`) — oder ersatzlos streichen |
Was in keine Kiste passt, ist meist ein Gedanke und kein Vorhaben. Der gehört
in die Prosa, nicht in die Datenbank. **Faustregel:** Wenn niemand es
übernehmen kann, ist es kein Item.
### 2. Themenbereiche anlegen
Die Abschnitte deiner Datei werden Themenbereiche. Kurzes Kürzel, sprechender
Titel:
```bash
curl -X POST "https://dc.mhdf.de/api/bugtracker/v1/roadmap.php?action=epic" \
-H "Authorization: Bearer $DC_TOKEN" -H "Content-Type: application/json" \
-d '{"project_slug":"meinprojekt","epic":"A","title":"Handelslogik","sort_order":10}'
```
Oder im WebUI unter **Roadmap → Themenbereiche**.
Halte die Zahl klein. Fünf bis zehn Bereiche sind übersichtlich, zwanzig sind
eine zweite Ablage, die selbst wieder sortiert werden will.
### 3. Vorhaben anlegen
Jedes Vorhaben wird ein Item. Mit MCP-Adapter:
```
dc_report(title: "Symbol-Upload und dc_symbolicate",
type: "feature_request",
description: "…",
client_ref: "roadmap-a4")
```
`client_ref` immer setzen: Ein wiederholter Lauf legt dann kein Duplikat an —
und bei einer Übernahme von zwanzig Einträgen läuft irgendetwas immer zweimal.
Ohne MCP:
```bash
curl -X POST "https://dc.mhdf.de/api/bugtracker/v1/report" \
-H "Authorization: Bearer $DC_TOKEN" -H "Content-Type: application/json" \
-d '{"project_slug":"meinprojekt","type":"feature_request",
"title":"…","description":"…","client_ref":"roadmap-a4"}'
```
### 4. Auf die Roadmap heben
Ein Vorhaben steht auf der Roadmap, **sobald es einen Themenbereich hat**. Das
ist das einzige Kennzeichen — ein zweites Flag daneben gäbe es zwei Angaben,
die dasselbe behaupten und auseinanderlaufen können.
```
dc_roadmap_set(id: 142, epic: "A", roadmap_rank: 10, target_build: "2.7.0",
doc_anchor: "docs/ROADMAP.md#142-symbol-upload")
```
Zur Reihenfolge: **Zehnerschritte.** Die Lücken sind Absicht — ein Eintrag
lässt sich später auf Rang 15 dazwischenschieben, ohne dass alle folgenden neu
nummeriert werden müssen.
Zurückgestelltes braucht den Auslöser, sonst wird der Aufruf abgelehnt:
```
dc_roadmap_set(id: 147, epic: "A", status: "deferred",
defer_trigger: "sobald Logauszüge strukturiert hereinkommen")
```
Ohne benannten Anlass ist ein Vorhaben nicht zurückgestellt, sondern vergessen.
Genau deshalb ist das Feld Pflicht und nicht bloß empfohlen.
### 5. Die Datei umbauen
Drei Änderungen:
**a) Zustandstabellen raus.** Jede von Hand gepflegte Tabelle mit Status-Spalte
wird ersatzlos gelöscht. Sie kommt gleich erzeugt zurück.
**b) Überschriften auf Item-IDs umstellen.**
```markdown
### [A] #142 — Symbol-Upload und dc_symbolicate
Der Packager lädt beim `publish` die PDB mit hoch, verknüpft mit `commit_sha`.
Ausgeliefert wird ein gestripptes Binary. …
```
Kein Status in der Überschrift, kein Häkchen, keine Zielfassung — das steht
alles im erzeugten Block und wäre hier eine zweite Wahrheit.
**c) Den Block einsetzen.** Beim ersten Lauf wird er ans Dateiende gehängt;
verschiebe ihn samt Markern dorthin, wo er stehen soll. Künftige Läufe finden
ihn dort wieder.
```bash
php cli/roadmap.php --write --project=meinprojekt --file=/pfad/zu/ROADMAP.md
```
Liegt das Projekt auf einer anderen Maschine als das Deploymentcenter, holst du
den Block über die API:
```bash
curl -s "https://dc.mhdf.de/api/bugtracker/v1/roadmap.php?action=render&project_slug=meinprojekt" \
-H "Authorization: Bearer $DC_TOKEN" | jq -r .markdown
```
### 6. Abgleich einrichten
```bash
php cli/roadmap.php --check --project=meinprojekt --file=/pfad/zu/ROADMAP.md
```
Was geprüft wird — nur die **Prosa**, denn der erzeugte Block kann nicht
abweichen:
| Befund | Bedeutung | Grad |
|---|---|---|
| `unknown_item` | Absatz nennt `#142`, das Item gibt es nicht | Fehler |
| `foreign_item` | `#142` gehört einem anderen Projekt | Fehler |
| `not_on_roadmap` | Absatz vorhanden, aber kein Themenbereich gesetzt | Warnung |
| `title_drift` | Überschrift und Item-Titel haben nichts mehr gemeinsam | Warnung |
| `decision_undocumented` | zurückgestellt/verworfen, aber keine Begründung in der Datei | Warnung |
Rückgabewert 0 heißt in Ordnung (Warnungen erlaubt), 1 heißt Fehler. Damit
lässt sich der Aufruf in einen Deploy-Schritt oder Pre-Commit-Hook hängen.
Ein Item **ohne** Absatz ist bewusst kein Befund: Nicht jedes Vorhaben braucht
eine Herleitung, die meisten sind eine Zeile wert und mehr nicht.
---
## Nach der Übernahme: die Regeln im Alltag
**Ein Vorhaben entsteht immer im DC, nie in der Datei.** `dc_report` legt das
Item an → `--write` schreibt die Zeile → *danach* schreibst du die Begründung
darunter. So kann ein Eintrag, der nur in der Datei steht, gar nicht erst
entstehen — und damit auch kein offener Punkt, den niemand angeht.
**Die Arbeitsschlange ist die Wahrheit über Arbeit.** Was nicht über
`?action=next` ausgegeben wird, macht niemand — unabhängig davon, wie es in
einer Datei markiert ist. Ein Eintrag, der etwas werden soll, muss ein Item
sein.
**Vor dem Anfangen `dc_roadmap` aufrufen.** Es beantwortet „was ist als
Nächstes dran" und „arbeitet schon jemand daran". Beides, bevor du beginnst.
**Vor dem Wiedervorschlagen die Begründung lesen.** Steht ein Vorhaben auf
`rejected` oder `deferred`, führt `doc_anchor` zu dem Absatz, der sagt warum.
Ein erneuter Vorschlag braucht ein Argument, das dort noch nicht steht.
**Der erzeugte Block darf altern.** Er ist eine Kopie, und Kopien altern. Der
Live-Stand kommt aus `dc_roadmap`; der Block ist für Menschen, die die Datei
aufschlagen. `--check` weist auf einen Block hin, der älter als ein Tag ist.
---
## Was nicht ins DC gehört
Der Umzug ist keine Einladung, alles in die Datenbank zu kippen.
- **Architekturentscheidungen mit Herleitung** bleiben in der Datei. Ein
Absatz, der eine Abwägung erklärt, überlebt keine Tabellenzelle.
- **Verworfenes mit Begründung** bleibt in der Datei. Der Wert des Eintrags ist
die Begründung, nicht die Ablehnung. Im DC steht nur der Status daneben.
- **Lose Gedanken** gehören weder hierhin noch dorthin. Wenn niemand sie
übernehmen kann, sind sie kein Vorhaben — im Zweifel als Notiz ins Archiv.
- **Alte Entwurfsdokumente** nach `docs/archiv/` verschieben, nicht löschen.
Sie enthalten die ausführliche Begründung, aber keinen gültigen Stand mehr.
Ein Hinweis darauf gehört an den Kopf der Roadmap-Datei.
---
## Werkzeuge im Überblick
| Werkzeug | Wofür |
|---|---|
| `dc_roadmap` | lesen: was ist dran, wer sitzt dran (MCP) |
| `dc_roadmap_set` | Bereich, Rang, Zielfassung, Anker setzen (MCP) |
| `GET /api/bugtracker/v1/roadmap.php?action=list` | dasselbe über REST |
| `?action=render` | den Markdown-Block holen |
| `?action=check` | Abgleich mit mitgeschickter Datei (`content`) |
| `php cli/roadmap.php --write\|--check\|--list` | von der DC-Maschine aus |
| WebUI → **Roadmap** | Übersicht, Filter, Reihenfolge, Aufnahmeliste |
Token braucht `bugtracker:read` zum Lesen und `bugtracker:manage` zum
Schreiben. Ein Werkbank-Token (`dev_agent`) hat beides.
---
## Beispiel: eine Datei vorher und nachher
**Vorher** — Zustand und Begründung durcheinander, beides von Hand:
```markdown
## A — Supervisor
| | Vorhaben | Stand |
|---|---|---|
| A4 | Symbol-Upload | 📋 geplant für 2.7.0 |
| A5 | Logzeilen-Schablonen | 🧊 später |
### A4 — Symbol-Upload
Der Packager lädt die PDB mit hoch…
```
**Nachher** — Zustand erzeugt, Begründung von Hand, verbunden über die ID:
```markdown
<!-- dc:roadmap stand=2026-08-29T14:02Z items=7 projekt=deploymentcenter -->
### A — Supervisor und Befunde
| | Vorhaben | Stand | Ziel |
|---|---|---|---|
| #142 | Symbol-Upload und dc_symbolicate | 📋 Offen | `2.7.0` |
| #143 | novel.log_template | 🧊 Zurückgestellt — Auslöser: sobald Logauszüge strukturiert hereinkommen | — |
<!-- /dc:roadmap -->
## Begründungen
### [A] #142 — Symbol-Upload
Der Packager lädt die PDB mit hoch, verknüpft mit `commit_sha`. Ausgeliefert
wird ein gestripptes Binary. Der Kunde bekommt `at 0x4a2f`, du bekommst
`UserAuthService.cs:42` — ohne dass Quelltext das Haus verlässt.
```
Die Statuszeile stimmt jetzt, weil sie niemand mehr pflegt.
+185
View File
@@ -0,0 +1,185 @@
# Supervisor — Befunde und Rückbau
Der Supervisor sieht nach, ob etwas ungewöhnlich ist, und legt Befunde ab.
Er beantwortet die Frage **„ist das ungewöhnlich?"** — nicht „ist das
schlimm?". Die zweite Frage beantwortet ein Mensch oder ein Agent bei der
Sichtung, mit Verlauf, Flotte und Quelltext vor Augen.
Gültiger Stand aller Vorhaben: **[ROADMAP.md](ROADMAP.md)**<br>
Ausführliche Begründung (archiviert): [archiv/SUPERVISOR_MCP_DESIGN.md](archiv/SUPERVISOR_MCP_DESIGN.md)
---
## Wichtig: es gibt nichts einzubauen
> **Ein Supervisor im ausgelieferten Programm ist verworfen.** Er hätte in
> jeder Anwendung Einbau, Rückbau, eigenen Zustand auf Kundenhardware, ein
> weiteres Token im Binary und eine zweite Fassungspflege gebraucht — und dabei
> überwiegend gemeldet, was Error-Reporting und Bugtracker ohnehin abdecken.
>
> **Für Anwendungen ändert sich damit nichts.** Wer Fehler an den Bugtracker
> meldet, ist fertig. Die Detektoren laufen im Deploymentcenter auf den Daten,
> die dadurch ohnehin hereinkommen.
Diese Anleitung beschreibt deshalb zweierlei: **was der Supervisor findet und
wie man es ansieht** — und **wie begonnene Vorbereitungen zurückgebaut werden.**
---
## Woher Befunde kommen
**Serverseitig, von selbst.** Fünf Detektoren laufen im minütlichen Tick
(gedrosselt auf zehn Minuten) und brauchen keine Änderung an irgendeiner
Anwendung:
| Detektor | Findet |
|---|---|
| `novel.error_signature` | Fehlersignatur, die es in dieser Fassung noch nie gab |
| `silence.heartbeat` | Instanz meldet sich nicht mehr |
| `deviation.self` | Abweichung vom eigenen Verlauf der letzten Tage |
| `deviation.fleet` | Ausreißer gegenüber anderen Installationen derselben Fassung |
| `regression.build` | Kennzahl verschiebt sich nach einem Update |
Keiner davon muss den gesuchten Fehler vorher kennen. Der Maßstab kommt nicht
aus einem Kopf, sondern aus dem eigenen Verlauf, aus der Vorversion oder aus
den anderen Installationen — deshalb finden sie Dinge, an die niemand gedacht
hat.
**Von außen eingeliefert**, über `POST /api/supervisor/v1/observe` mit dem
Scope `supervisor:report`. Heute nutzen das Prüfwerkzeuge und Testläufe. Der
[Katalog](../src/Modules/Supervisor/Catalog.php) legt die Namen fest, unter
denen gemeldet wird — abrufbar über `GET /api/supervisor/v1/catalog`.
Dass die Namen stabil sind, ist der ganze Zweck: Befunde aus verschiedenen
Projekten müssen vergleichbar bleiben. Ein umbenannter Detektor zerreißt jede
Auswertung, die über den Zeitpunkt der Umbenennung hinausgeht.
---
## Befunde ansehen
Über MCP in Claude Code:
```
dc_finding_summary → Zählung je Detektor
dc_findings detector: "deviation" → die Einzelbefunde
dc_metrics source: "app-01" metric: … → der Verlauf dahinter
```
Ohne MCP:
```
GET /api/supervisor/v1/summary
GET /api/supervisor/v1/findings?detector=deviation&status=active
GET /api/supervisor/v1/catalog
```
Befunde tragen ihre Umgebung. Was außerhalb von `production` auffällt, ist eine
Stufe niedriger eingestuft — nicht weil es unwichtig wäre, sondern weil auf
Testmaschinen systematisch mehr Rauschen entsteht.
**Ob ein Befund aus einem Testlauf ein Artefakt des Prüfstands ist oder auch
beim Kunden aufträte, ist die schwierige Frage.** Sie ist offen; die Ansätze
stehen in der [Roadmap unter G8](ROADMAP.md). Der brauchbarste Gedanke daraus:
Fehlende Zugangsdaten sind ein Artefakt — *wie die Software darauf reagiert*,
ist keines. Ein Wiederholungssturm, ausgelöst durch fehlende Zugangsdaten, ist
ein echter Befund, denn auch beim Kunden laufen Zugangsdaten ab.
---
## Vorbereitungen in den Projekten zurückbauen
In mehreren Projekten wurde bereits von Hand etwas in Richtung Supervisor
begonnen — UI-Steuerelemente, teilweise ein lokaler KI-Agent. Das fällt
**ersatzlos** weg; es tritt nichts an seine Stelle.
### Erkennen
```bash
grep -rniE "supervisor|healthcheck|selfcheck|anomal" --include=*.cs --include=*.php .
```
Typisch: eine eigene Timer-Schleife, ein Verzeichnis mit Zählern, ein eigener
`HttpClient`-Aufruf gegen das Deploymentcenter, eine JSON-Datei mit Zustand,
eine Klasse namens `HealthCheck` oder ähnlich.
### Was verschwindet
- **Lokal laufender KI-Agent** samt API-Schlüssel. Der Schlüssel läge auf
Kundenhardware, der Kontext im Speicher fremder Maschinen.
- **UI-Listen, -Tabellen und -Schaltflächen für Befunde.** Es gibt lokal keine
Befundhistorie anzuzeigen, und Abhaken wäre `supervisor:manage` — ein Recht,
das kein Laufzeit-Token trägt.
- **Eigene Warteschlangen** für nicht zugestellte Meldungen. Eine wachsende
Warteschlange in einer ohnehin auffälligen Anwendung ist genau der Schaden,
den man vermeiden wollte.
- **Eigene Alarmierung** je Auffälligkeit. Wer bei jeder Kleinigkeit alarmiert,
hat nach einer Woche einen Kanal, den niemand mehr liest.
- **Feste Schwellwerte je Kennzahl.** Die tragenden Detektoren vergleichen
gegen eigenen Verlauf, Vorversion oder Flotte. Ein Schwellwert findet nur,
woran vorher jemand gedacht hat.
- **Timer-Schleifen, Zustandsdateien, eigene POSTs** an das Deploymentcenter.
Was bleiben darf, wenn es schon gebaut ist: eine **einzelne Statuszeile** im
Administrationsbereich — meldet die Anwendung Fehler, wann zuletzt. Das ist die
einzige lokal beantwortbare Frage.
### Was übrig bleibt und wertvoll ist
Wo in der Vorbereitung schon überlegt wurde, **worauf** zu achten wäre, ist
diese Überlegung das Wertvollste am ganzen Bestand. Sie wandert nicht ins
Nichts, sondern:
| Überlegung | Neuer Ort |
|---|---|
| „Hier darf nie ein negativer Saldo entstehen" | Prüfung im Testlauf |
| „Dieser `catch` verschluckt etwas Wichtiges" | Fehlermeldung an den Bugtracker, statt zu schlucken |
| „Diese Warteschlange kann volllaufen" | Kennzahl im Heartbeat — `deviation.self` sieht den Rest |
| „Dieser Job stirbt manchmal still" | Heartbeat-Monitor — `silence.heartbeat` sieht den Rest |
Die letzten beiden sind der Punkt: Was als Kennzahl im Heartbeat landet, wird
serverseitig ohnehin überwacht. Dafür braucht es keinen Beobachter im Programm,
nur einen Wert mehr im Ping.
### Auftrag für den Agenten im Projektverzeichnis
Zum Einfügen in eine frische Sitzung im jeweiligen Projekt:
```text
In diesem Projekt wurde eine Supervisor-Vorbereitung begonnen. Sie wird
ersatzlos zurückgebaut — ein Supervisor im ausgelieferten Programm ist
verworfen. Es tritt nichts an seine Stelle.
1. Bestandsaufnahme. Suche:
grep -rniE "supervisor|healthcheck|selfcheck|anomal" .
Berichte, was du findest, bevor du etwas änderst.
2. Ersatzlos entfernen:
- lokal laufender KI-Agent samt API-Schlüssel
- UI-Listen, -Tabellen und -Schaltflächen für Befunde
- eigene Warteschlangen, eigene Alarmierung, feste Schwellwerte
- eigener POST ans Deploymentcenter, eigene Zustandsdatei
- eigene Timer-/Hintergrundschleife
Behalten: höchstens eine Statuszeile im Administrationsbereich.
3. Die Überlegungen retten. Wo jemand notiert hat, WORAUF zu achten wäre,
überführe das:
- fachliche Zusicherung -> Prüfung im Testlauf
- verschluckter Fehler -> Fehlermeldung an den Bugtracker statt Schlucken
- Wert, der volllaufen kann -> Kennzahl im Heartbeat
4. Sicherstellen, dass Fehler gemeldet werden: globaler Exception-Handler an
POST /api/errors/v1/report, mit build_version. Ohne diese Angabe kann der
Tracker eine Regression nicht von einem veralteten Prüfstand unterscheiden.
5. Fassung festhalten: dc_link_project mit to_slug "deploymentcenter",
integrated_version "2.6.1". Ohne MCP: POST /api/bugtracker/v1/projects.
Fällt dir dabei ein Fehler im Deploymentcenter selbst auf, melde ihn mit
dc_report gegen project_slug "deploymentcenter" samt evidence.
```
Schritt 4 ist der wichtigste. Er ersetzt den ganzen verworfenen Beobachter:
Kommen Fehler mit Fassungsangabe herein, arbeiten die serverseitigen Detektoren
darauf — ohne eine Zeile Beobachtercode in der Anwendung.
+289
View File
@@ -0,0 +1,289 @@
# Testinstanz und automatisierter Testlauf
**Gilt für:** `https://dc-dev.mhdf.de` — eine vollständige zweite Installation des
Deploymentcenters, die jederzeit gelöscht und neu aufgebaut werden darf.
> **Bewusst nicht unter `public/`.** Diese Datei nennt interne Adressen und den Weg, die
> Datenbank der Testinstanz zu leeren. `/docs/` auf dem Deploymentcenter ist öffentlich
> erreichbar; dort gehört das nicht hin. Zugangsdaten stehen ausschließlich in
> `Serverdaten.txt` und `scripts/deploy_dev_config.json` — beide sind per `.gitignore`
> ausgeschlossen und werden nie hochgeladen.
---
## 1. Wozu es sie gibt — und wozu nicht
| | Testinstanz `dc-dev.mhdf.de` | Test-VM (siehe [TESTUMGEBUNG.md](TESTUMGEBUNG.md)) |
|---|---|---|
| Prüft | **den Server**: API, Rechte, Migrationen, Zugangssperre | **den Client**: Installer, Updateagent, Oberfläche |
| Datenbestand | wird bei jedem Lauf gelöscht | bleibt |
| Zugriff | HTTPS + Basic-Auth | SSH |
Beides zusammen deckt die Kette ab. Getrennt zu halten ist Absicht: Ein Testlauf, der
Tabellen löscht, darf nicht auf derselben Maschine laufen wie ein Test, der eine
Installation über Stunden beobachtet.
**Was die Testinstanz nicht prüft:** die Produktion. Sie hat eigene Schlüssel, eine eigene
Datenbank und **Rocket.Chat ist abgeschaltet** (`rocketchat.enabled = false`). Ein Testlauf,
der echte Kanäle vollschreibt, wäre der Fehler, der am ehesten unbemerkt teuer wird.
---
## 2. Aufbau
```
dc-dev.mhdf.de Subdomain, Docroot = /deploymentcenter_dev/ auf dem FTP
├── .htaccess Sperre + Routen (erzeugt, siehe §5)
├── config/
│ ├── .htpasswd erzeugt aus scripts/deploy_dev_config.json
│ ├── config.php erzeugt aus scripts/dev/config.dev.php.tmpl
│ └── release-signing.pem auf der Instanz erzeugt (testctl genkey)
├── public/ src/ sql/ var/ cli/ wie in der Produktion
└── testctl.php nur hier — Steuerung des Testlaufs (§6)
```
Im Repository:
| Datei | Zweck |
|---|---|
| `scripts/deploy_dev.py` | Deployment auf die Testinstanz |
| `scripts/deploy_dev_config.json` | Zugangsdaten (**nicht im Git**, Vorlage: `…example.json`) |
| `scripts/dev/htaccess-prelude.conf` | der Sperrblock, der der `.htaccess` vorangestellt wird |
| `scripts/dev/config.dev.php.tmpl` | Vorlage der `config.php` der Testinstanz |
| `tests/server/testctl.php` | wird nach `/testctl.php` hochgeladen — **nur** von `deploy_dev.py` |
| `tests/e2e/` | der Testlauf |
`scripts/deploy.py` (Produktion) schließt `tests/` vollständig aus. `testctl.php` kann nicht
versehentlich auf `dc.mhdf.de` landen.
---
## 3. Deployment
```bash
python scripts/deploy_dev.py --dry-run
```
```bash
python scripts/deploy_dev.py
```
Überträgt nur geänderte Dateien (eigener Cache: `scripts/.deploy_dev_cache.json`).
`--full` ignoriert den Cache.
Drei Dinge macht das Skript anders als das Produktiv-Deployment:
1. **Die `.htaccess` wird erzeugt** — der Sperrblock aus `scripts/dev/htaccess-prelude.conf`
plus die `.htaccess` des Repositories. Eine zweite gepflegte Kopie der Routen gibt es
damit nicht; Änderungen an den Routen wirken auf beiden Instanzen.
2. **Die `config.php` wird erzeugt** — mit der Testdatenbank, eigenen Schlüsseln, `debug =
true` und abgeschaltetem Rocket.Chat. Die Produktivkonfiguration wird nie übertragen.
3. **`testctl.php` kommt dazu.**
Das Skript bricht ab, wenn die Zugangsdaten auf den Produktivserver zeigen oder `base_url`
kein `dc-dev` enthält.
### Das Testtor vor der Produktion
`scripts/deploy.py` (Produktion) führt seit dem 29.08.2026 **vor** jedem Upload aus:
1. `scripts/deploy_dev.py` — der Stand, der gleich hochgehen soll, kommt auf die Testinstanz
2. `tests/e2e/run.py` — der vollständige Testlauf
Erst bei Rückgabewert 0 beginnt der Upload auf `dc.mhdf.de`. Schlägt etwas fehl, wird
**nichts** übertragen.
Die Reihenfolge ist der Punkt: Liefe der Testlauf gegen den vorhandenen Dev-Stand, prüfte er
einen älteren Code als den, der gleich produktiv geht — und gäbe grünes Licht für etwas, das
er nie gesehen hat.
```bash
python scripts/deploy.py --ohne-test
```
Der Notausgang. Er gehört in die Hand eines Menschen, der weiß, warum er ihn nimmt —
`--dry-run` läuft ohnehin ohne Testlauf, weil dabei nichts übertragen wird.
---
## 4. Testlauf
```bash
python tests/e2e/run.py
```
Der Lauf setzt zuerst die Datenbank zurück, spielt alle Migrationen ein, legt einen
Administrator und ein Master-Token an — und prüft dann in dieser Reihenfolge:
| Gruppe | Was geprüft wird |
|---|---|
| `sperre` | Basic-Auth, gesperrte Verzeichnisse, Sicherheitskopfzeilen, `testctl`-Schlösser |
| `kern` | Health, OpenAPI, Migration (auch: wiederholbar), 404/405/400-Verhalten |
| `token` | Master-/Sub-Token, Rechteeinschränkung, keine Rechteausweitung |
| `bugtracker` | melden, Idempotenz, Arbeit holen, Reservierung, Kommentar, Abschluss |
| `roadmap` | Epics, Einplanung, Lesewege |
| `fehlerstrom` | Ausnahmen aus Clients |
| `lizenz` | Entwicklungsschlüssel, Prüfung, Rückgabe |
| `watchdog` | Lebenszeichen, Ereignisse, Kennzahlen, Auswertung |
| `update` | Release veröffentlichen, Update finden bzw. nicht finden, Signierschlüssel |
| `setup` | Erstinstallation: Anmeldung, Katalog, Projekttoken, Monitor |
| `supervisor` | Befunde melden, lesen, einstufen |
Nützliche Schalter:
```bash
python tests/e2e/run.py --gruppe sperre --gruppe kern
```
```bash
python tests/e2e/run.py --nur roadmap --log
```
```bash
python tests/e2e/run.py --bericht bericht.json --kein-reset
```
| Schalter | Wirkung |
|---|---|
| `--gruppe X` | nur diese Gruppe (mehrfach möglich) |
| `--nur TEXT` | Namensfilter |
| `--kein-reset` | Datenbestand behalten (dann `master_token` in der Konfiguration hinterlegen) |
| `--log` | bei Fehlschlägen das Serverlog der Instanz anhängen |
| `--stopp` | beim ersten Fehlschlag abbrechen |
| `--bericht DATEI` | Ergebnisse als JSON |
| `--liste` | nur auflisten, nichts ausführen |
| `--ipv4` | IPv4 erzwingen (siehe §8) |
**Rückgabewert:** `0` alles bestanden, `1` Fehlschläge, `2` der Lauf sagt nichts aus.
Damit lässt er sich ohne Auswertung der Ausgabe in eine Pipeline hängen.
`2` kommt in zwei Fällen: der Lauf konnte gar nicht starten (Zurücksetzen fehlgeschlagen,
kein Testfall passt zur Auswahl) — oder es wurde **mehr übersprungen als geprüft**.
Der zweite Fall trifft fast nur gefilterte Läufe. Übersprungen wird, wenn eine
Voraussetzung aus einem früheren Testfall fehlt, und bei `--gruppe`/`--nur` ist der oft
mit weggefiltert: `ctx['freies_item']` etwa entsteht in der Gruppe `bugtracker`, wird aber
in `roadmap` gebraucht. Vorher endete so ein Lauf mit „2 bestanden, 0 fehlgeschlagen,
5 übersprungen" und Rückgabewert 0 — grün, obwohl fünf von sieben Fällen nie liefen.
Nichts ist dabei kaputt; geprüft wurde es aber auch nicht, und der Unterschied zwischen
„kaputt" und „nicht prüfbar" darf nicht ausgerechnet am Rückgabewert verschwinden. Der
Bericht führt dasselbe als `aussagekräftig: false`.
### Einen Testfall ergänzen
```python
@test('bugtracker', 'Kurze Aussage, was gelten soll')
def _(ctx):
"""Warum das wichtig ist - eine Zeile, kein Roman."""
r = ctx.client.post('/api/bugtracker/v1/report', body={...})
erwarte_erfolg(r)
ctx['bug_id'] = r.feld('item_id') # steht dem nächsten Testfall zur Verfügung
```
Die Reihenfolge der Registrierung ist die Reihenfolge der Ausführung; die Importe in
`run.py` legen die Reihenfolge der Dateien fest. Fehlt eine Voraussetzung (`ctx['bug_id']`
wurde nie gesetzt), wird der Testfall **übersprungen** statt fälschlich rot — der
Unterschied zwischen „kaputt" und „nicht prüfbar" muss im Bericht sichtbar bleiben.
### Bekannte Befunde
Ein gefundener Fehler bekommt zweierlei: ein Item im Bugtracker **und** einen Testfall, der
ihn festhält. Damit dieser Testfall nicht jedes Deployment blockiert, bis der Fehler behoben
ist, steht er in `tests/e2e/bekannte_befunde.json`:
```json
"supervisor/Antwortumschlag von observe bleibt erkennbar": {
"item": 59,
"seit": "2026-08-29",
"grund": "observe ueberschreibt das Umschlagfeld 'status' mit dem Zustand des Befundes."
}
```
Der Fall läuft weiter mit, erscheint als `BEK` und im Bericht — aber der Lauf bleibt grün.
Die beiden Alternativen wären schlechter: den Testfall löschen heißt, den Befund zu
vergessen; mit einem dauerhaft roten Lauf leben heißt, dass niemand mehr hinsieht, wenn
etwas Neues rot wird.
**Ein Eintrag ohne Item-Nummer ist eine Ausrede, kein Befund.** Besteht der Fall wieder,
meldet der Lauf das ausdrücklich (`NEU!`) — dann gehört der Eintrag heraus und das Item auf
`resolved`.
---
## 5. Die Zugangssperre
Die Testinstanz läuft mit `debug = true`, mit Beispieldaten und mit einem Datenbestand, den
jeder Testlauf löschen darf. Nichts davon gehört ins offene Netz.
* **Basic-Auth vor allem**, was ein Mensch im Browser aufruft — WebUI, `/docs/`,
`install_db.php`, `testctl.php`.
* **Ausnahme für `/api/`**, wenn der Aufruf bereits ein Token der Anwendung mitbringt
(`X-Agent-Token`, `X-Master-Token`, `X-License-Key`, `X-Watchdog-Key` oder
`Authorization: Bearer …`).
Ohne diese Ausnahme müsste jedes SDK zusätzlich zu seinem Token die Basic-Auth-Daten kennen.
Die Testinstanz prüfte dann einen Aufrufweg, den es auf `dc.mhdf.de` nicht gibt. Die
Ausnahme öffnet nichts, was in der Produktion nicht ohnehin offen wäre — geprüft wird das
Token weiterhin von `ApiAuth`/`TokenManager`. Ein Token-Header allein schaltet weder WebUI
noch Dokumentation frei; die Gruppe `sperre` prüft genau das bei jedem Lauf.
`config/.htpasswd` liegt im Webroot, ist aber durch die zweite `.htaccess` in `config/`
gesperrt (`Require all denied`) — die Datei muss dort liegen, weil `AuthUserFile` einen
absoluten Pfad braucht.
---
## 6. `testctl.php`
Steuert die Instanz. Erreichbar nur mit Basic-Auth **und** Shared Key, und nur auf einer
Instanz mit `testing.enabled = true` und `app.env = 'test'`.
```bash
curl -s -u deploy:PASSWORT -H "X-Master-Token: SHARED_KEY" \
"https://dc-dev.mhdf.de/testctl.php?action=status"
```
| Aktion | Wirkung |
|---|---|
| `status` | Tabellen, Zeilenzahlen, angewendete Migrationen, Debug-Zustand |
| `reset` | löschen, migrieren, Administrator und Master-Token anlegen (braucht `confirm`) |
| `wipe` | nur löschen (braucht `confirm` = Name der Datenbank) |
| `migrate` | Migrationen nachziehen |
| `genkey` | Signierschlüssel für den Updateservice erzeugen |
| `log` | die letzten Zeilen aus `var/log/` |
| `clearlog` | Logdateien leeren |
`reset` und `wipe` verlangen `confirm` mit dem Namen der Datenbank. Damit lässt sich der
Aufruf nicht versehentlich gegen die falsche Instanz absetzen.
---
## 7. Wenn etwas fehlschlägt
1. `python tests/e2e/run.py --nur "<Name>" --log` — der einzelne Fall mit Serverlog.
2. `--kein-reset` dazu, wenn der Datenbestand des fehlgeschlagenen Laufs erhalten bleiben
soll.
3. Die Instanz steht im Browser offen: `https://dc-dev.mhdf.de` (Basic-Auth, dann
Anmeldung mit dem Administrator aus `Serverdaten.txt`).
Ein Fehlschlag ist **nicht** automatisch ein Fehler der Anwendung — er kann auch heißen,
dass der Testfall einen Vertrag beschreibt, den es so nicht mehr gibt. Beides ist ein
Befund; die Frage ist nur, welche Seite geändert wird.
---
## 8. Bekannte Eigenheiten der Umgebung
**IPv6.** Alle Hoster-Adressen haben einen AAAA-Eintrag. Ist von einem Arbeitsplatz aus
keine IPv6-Route vorhanden, läuft in Python **jeder** Aufruf zuerst 21 Sekunden in einen
TCP-Timeout, bevor er auf IPv4 zurückfällt (`curl` tut das sofort, Python nicht). Der
Testlauf prüft IPv6 deshalb einmal beim Start und schaltet bei Bedarf für den ganzen Lauf
auf IPv4 um; die gewählte Familie steht in der Kopfzeile der Ausgabe. Betrifft nur den
Testlauf — für andere Python-Werkzeuge gegen `dc.mhdf.de` gilt dasselbe.
**PHP 8.2.33, FPM**, `memory_limit` 512 MB, `max_execution_time` 120 s. Die Produktion kann
davon abweichen; wo eine Laufzeitgrenze zum Befund gehört, muss sie mitgeprüft werden.
**Datenbank `watchdog`** auf `lwrs.your-database.de` — vorher die Datenbank des alten
Watchdogs, jetzt ausschließlich Testbestand. Sie wird bei jedem Lauf mit `reset` geleert.
+833
View File
@@ -0,0 +1,833 @@
# Linux-Testumgebung — Anleitung für Agenten
**Stand: 28.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.
>
> **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)
```bash
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:
```bash
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
HostName 192.168.40.48
User richard
IdentityFile ~/.ssh/testvm_<projekt>
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 4
```
⚠️ **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 "hostname"`
---
## 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 | **nicht** installiert | Wird auch nicht gebraucht — das Testkit liest JSON mit `python3` |
**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
Das Skript liegt im Deploymentcenter-Repo unter `public/testkit/dc-testkit.sh` und wird mit
`deploy.py` ins Webroot ausgeliefert. Auf der VM:
```bash
mkdir -p ~/bin
curl -fsSL https://dc.mhdf.de/testkit/dc-testkit.sh -o ~/bin/dc-testkit
chmod +x ~/bin/dc-testkit
```
> ⚠️ **Beides — dieser Download und `dc-testkit license` — setzt Deploymentcenter 2.6.1 voraus.**
> Solange die Fassung auf dem Server älter ist, gibt es die Datei unter `/testkit/` nicht, und der
> Endpunkt `/api/license/v1/dev-key` fehlt. Der fehlende Endpunkt 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 `curl`t, sucht sonst lange.
>
> Bis dahin geht es auch per `scp` aus dem Repo:
> `scp public/testkit/dc-testkit.sh testvm:~/bin/dc-testkit`
Dann je Projekt eine Konfiguration — **das Token gehört in die Datei, nicht in die Befehlszeile**,
sonst steht es in der Shell-History:
```bash
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 …`.
Prüfen, ob alles steht:
```bash
dc-testkit doctor
```
### 4.2 Das Werkbank-Token
Ein Mensch legt es **einmal je Projekt** an — im WebUI unter *System → Tokens*:
| Feld | Wert |
|---|---|
| Typ | Master |
| Besitzerart | `dev_agent` |
| Projekt | der Slug des Projekts |
| Rechte | `bugtracker:manage`, `license:dev`, `updateservice:read` |
Mehr braucht es nicht, und mehr sollte es nicht sein. `bugtracker:manage` schließt Lesen und
Melden ein, `license:dev` schließt `updateservice:read` ein — zwei Häkchen genügen also.
**Nicht `*` vergeben.** Ein Token mit allen Rechten auf einer geteilten Testmaschine darf auch
Releases veröffentlichen und Lizenzen fremder Projekte anfassen.
### 4.3 DEV-Lizenzschlüssel selbst holen
```bash
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:
```bash
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
```bash
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
```
Oder alles auf einmal, mit automatischer Meldung dessen, was scheitert:
```bash
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:
```bash
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.
```bash
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:
```bash
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:
```bash
./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:
```bash
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
**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.
```bash
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:
```bash
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](./SETUP_INTEGRATION_GUIDE.md) ü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](./ROADMAP.md) unter G1.
---
## 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.
```bash
# 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:
```bash
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:**
```bash
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:
```bash
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**):
```bash
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:**
```bash
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.
**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:
```bash
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).
Anders als `xvfb-run` wird das Display hier **dauerhaft** gestartet, damit sich zwischen den
Aufnahmen navigieren lässt:
```bash
#!/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:
```bash
scp testvm:'/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:
```bash
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)
**Datenbanken**
| Datenbank | Benutzer | Projekt | seit |
|---|---|---|---|
| `polytrader_test` | `polytrader_test`@localhost | PolyTrader | 26.08.2026 |
**Dienste**
| Unit | Konto | Verzeichnis | Projekt | seit |
|---|---|---|---|---|
| `polytrader.service` | `polytrader` | `/opt/polytrader` | PolyTrader | 26.08.2026 |
**Verzeichnisse**
| Projekt | Publish-Verzeichnis | Dienst |
|---|---|---|
| PolyTrader | `~/polytrader-test` | `/opt/polytrader` |
**Werkbank-Token** (Vergabe siehe 4.2)
| Projekt | Token angelegt | Rechte |
|---|---|---|
| *noch keins* | | |
**X-Displays** (Vergabe siehe 5.5)
| Display | Projekt | Stand |
|---|---|---|
| `:0` | echter Desktop — **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 | |
**Ports** (Stand 26.08.2026)
| Port | Belegt durch |
|---|---|
| 22 | SSH |
| 3306 | MariaDB (nur localhost) |
| 3389 | RDP |
| 5000 | vorhandener Dienst (nur IPv6-localhost) |
| 631 | CUPS |
**Nachträglich installiert:** MariaDB 11.8.6, Xvfb, xdotool, ImageMagick, x11-utils
(alle am 26.08.2026 für PolyTrader).
---
## 8. Aufräumen nach dem Test
Das Testkit räumt sein eigenes auf:
```bash
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:
```bash
# 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.)
- **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](./LICENSE_INTEGRATION_GUIDE.md) | Wie die Lizenzprüfung in der Anwendung selbst funktioniert |
| [UPDATESERVICE_INTEGRATION_GUIDE.md](./UPDATESERVICE_INTEGRATION_GUIDE.md) | Release-Verteilung, Kanäle, Signatur, Zugangsschutz der Ablage |
| [SETUP_INTEGRATION_GUIDE.md](./SETUP_INTEGRATION_GUIDE.md) | Erstinstallation und `setup.json` |
| [Agenten-Handbuch](../public/docs/bugtracker.md) | Vollständige Beschreibung des Bugtracker-Workflows |
| `GET /api/openapi.json` | Die Schnittstelle maschinenlesbar |
+42
View File
@@ -0,0 +1,42 @@
# Archiv
Abgelöste Entwurfs- und Planungsdokumente.
**Diese Dateien beschreiben keinen gültigen Stand mehr.** Sie werden
aufbewahrt, weil sie die ausführliche Herleitung enthalten — warum eine
Entscheidung so und nicht anders getroffen wurde, welche Alternativen geprüft
und aus welchem Grund verworfen wurden. Diese Begründungen sind zu wertvoll zum
Wegwerfen und zu lang für die Roadmap.
**Der gültige Stand steht in [../ROADMAP.md](../ROADMAP.md).**
Wer hier etwas liest, sollte jeden Punkt gegen die Roadmap prüfen, bevor er
danach handelt. Die Erfahrung, die zu diesem Archiv geführt hat: In
`SUPERVISOR_MCP_DESIGN.md` stand eine Liste offener Punkte, die der Code längst
erledigt hatte — ein Agent hat sie gelesen und daraufhin Arbeit vorgeschlagen,
die es nicht mehr gab.
---
| Dokument | Ursprung | Was daraus wurde |
|---|---|---|
| [SUPERVISOR_MCP_DESIGN.md](./SUPERVISOR_MCP_DESIGN.md) | Entwurf für Supervisor, Detektor-Katalog und MCP-Anbindung, geschrieben vor der Umsetzung | Etappen 1–3 sind in 2.6.0 ausgeliefert. Der Etappenplan, die offenen Punkte und die verworfenen Ansätze sind in die Roadmap übernommen. Hier steht weiterhin die vollständige Begründung — besonders Abschnitt 0 (warum Beobachtung und Urteil getrennt sind) und Abschnitt 7 (warum der Client keinen Quelltext sieht) |
---
## Nicht archiviert
Die Integrationsanleitungen in `../` sind **kein** Archiv. Sie beschreiben,
wie eine Anwendung heute anzubinden ist, werden von den Agenten in den anderen
Projekten gelesen und bleiben dort, wo sie sind:
- `SUPERVISOR_INTEGRATION_GUIDE.md`
- `BUGTRACKER_INTEGRATION_GUIDE.md`
- `LICENSE_INTEGRATION_GUIDE.md`
- `SETUP_INTEGRATION_GUIDE.md`
- `UPDATESERVICE_INTEGRATION_GUIDE.md`
- `WATCHDOG_INTEGRATION_GUIDE.md`
Ebenso bleibt `UPGRADE.md`: ein Ablaufplan für die Umstellung bestehender
Installationen. Er beschreibt Vergangenes, wird aber weiterhin gebraucht, wenn
jemand von einer älteren Fassung kommt.
+599
View File
@@ -0,0 +1,599 @@
# Supervisor, Detektor-Katalog und MCP-Server — Entwurf
> **ARCHIVIERT.** Dieses Dokument beschreibt keinen gültigen Stand mehr.
> Die Etappen 1 bis 3 sind in 2.6.0 ausgeliefert; der Etappenplan, die offenen
> Punkte und die verworfenen Ansätze sind in **[../ROADMAP.md](../ROADMAP.md)**
> übernommen. Dort steht, was gilt.
>
> Aufbewahrt wird der Entwurf wegen der Herleitung — besonders Abschnitt 0
> (warum Beobachtung und Urteil getrennt sind) und Abschnitt 7 (warum der
> Client keinen Quelltext sieht). Wer hier etwas liest, prüft es gegen die
> Roadmap, bevor er danach handelt.
Stand: 2026-08-21. Betrifft alle Projekte, nicht nur das Deploymentcenter.
Drei Vorhaben, die aneinanderhaengen und deshalb zusammen beschrieben werden:
1. ein **Supervisor**, der in jeder Anwendung laufend nachsieht, ob etwas
schieflaeuft, und Befunde meldet
2. ein **Detektor-Katalog**, der festlegt, wonach er sucht — in allen
Projekten unter denselben Namen
3. ein **MCP-Server**, ueber den Claude Code und andere Werkzeuge an
Error-Reporting, Supervisor und Bugtracker andocken
Die Reihenfolge im Dokument folgt der Sache, nicht der Umsetzung. Der Bauplan
steht am Ende unter "Etappen".
---
## 0. Die Grundentscheidung: Beobachtung und Urteil trennen
Der Supervisor ist bewusst kein einzelnes Ding. Er zerfaellt an der
Vertrauensgrenze in zwei Haelften:
| | Beobachter | Diagnostiker |
|---|---|---|
| laeuft | auf der Kundenmaschine | auf eigener Infrastruktur |
| fragt | Ist das *ungewoehnlich*? | Ist das *schlimm*? |
| Takt | dauernd, minuetlich | selten, bei Auffaelligkeit |
| Kosten je Pruefung | muessen nahe null sein | duerfen hoch sein |
| kennt Quellcode | nein | ja |
| Technik | Regeln, Zaehler, Schwellen | Agent mit Repo-Zugriff |
Zwei Gruende, warum die Trennung nicht verhandelbar ist:
**Sicherheit.** Alles, was der ausgelieferte Client lesen kann, kann auch der
Kunde lesen. Ein Token im Binary ist extrahierbar, TLS auf der eigenen Maschine
abhoerbar, .NET dekompilierbar. Duerfte der Client Quellcode abrufen, waere die
Software faktisch offen. Dagegen hilft keine Verschluesselung — der Client
muesste entschluesseln koennen, also koennte es der Kunde auch.
**Eignung.** Ein Sprachmodell ist schlecht im dauernden Hinsehen und gut im
Deuten. Es kann nicht im Minutentakt schauen, hat keinen Verlauf im Kopf, sieht
die anderen Installationen nicht und antwortet zweimal verschieden auf dieselbe
Lage. Die Aufteilung waere also auch ohne den Sicherheitsaspekt richtig.
Der Beobachter meldet **Symptome und Koordinaten** (`build`, `commit_sha`,
`file`, `line`). Der Diagnostiker loest die Koordinaten gegen den Quellcode auf.
So braucht der Client den Code nie zu sehen.
---
## 1. Detektor-Katalog
Der Katalog ist der Kern. Er legt fest, wonach gesucht wird, und zwar unter
stabilen Namen — damit Befunde aus verschiedenen Projekten vergleichbar bleiben
und `dc_findings` etwas Auswertbares liefert.
Ein Detektor-Name ist eine Zeichenkette der Form `familie.sache`. Er wandert
nie mehr; kommt etwas dazu, bekommt es einen neuen Namen.
### 1.1 Detektoren, die den Fehler nicht vorher kennen muessen
Diese Gruppe ist der eigentliche Gewinn. Sie findet Dinge, an die niemand
gedacht hat, weil der Massstab nicht aus einem Kopf kommt, sondern aus der
Wirklichkeit.
| Name | Findet | Laeuft |
|---|---|---|
| `novel.error_signature` | Fehlersignatur, die es in dieser Fassung noch nie gab | Server |
| `novel.log_template` | Logzeilen-Schablone, die noch nie auftrat | Server |
| `silence.periodic` | Bekanntes wiederkehrendes Ereignis bleibt aus (still gestorbener Hintergrundjob) | Client |
| `silence.heartbeat` | Instanz meldet sich nicht mehr | Server |
| `deviation.self` | Abweichung vom eigenen Verlauf der letzten Wochen | Server |
| `deviation.fleet` | Ausreisser gegenueber anderen Installationen derselben Fassung | Server |
| `regression.build` | Kennzahl verschiebt sich nach einem Update gegenueber der Vorgaengerfassung | Server |
`deviation.self` ist bereits vorhanden: `MetricStore::deviation()` rechnet die
Abweichung mit einem Faktor auf die Streuung. `novel.error_signature` ist
nahezu geschenkt, weil `BugRepo` mit `first_seen_at` und `occurrence_count`
schon gruppiert und zaehlt.
`deviation.fleet` ist der staerkste und bisher ungenutzte: dieselbe Fassung
laeuft auf N Installationen, eine faellt bei irgendeiner Kennzahl aus dem
Rahmen — auch bei einer, an die niemand gedacht hat.
### 1.2 Detektoren ueber die Zeitachse
Ein Unit-Test laeuft Millisekunden auf frischem Zustand, ein Kundenprozess
Wochen. Alles, was sich ansammelt, ist fuer Tests strukturell unsichtbar.
| Name | Findet | Laeuft |
|---|---|---|
| `trend.monotonic` | Kennzahl waechst ueber N Fenster nur nach oben (Speicher-, Handle-, Cache-Leck) | Client |
| `trend.saturation` | Kennzahl naehert sich einer harten Grenze (Platte, Verbindungspool, Dateihandles) | Client |
| `trend.latency` | Antwortzeiten driften ueber Tage nach oben (Index-Degradation) | Client |
### 1.3 Detektoren, mit denen die Anwendung sich selbst verpfeift
Der ergiebigste und billigste Fang: Zaehler auf die Stellen, an denen die
Software heute schon still verzeiht. Dort sitzen die Fehler, die niemand
meldet.
| Name | Findet | Laeuft |
|---|---|---|
| `swallowed.exception` | `catch`-Bloecke, die einen Fehler schlucken und weitermachen | Client |
| `fallback.usage` | Rueckfall auf den degradierten Pfad haeuft sich | Client |
| `retry.storm` | Wiederholungsversuche haeufen sich | Client |
| `probe.failed` | Registrierte Probe schlaegt fehl | Client |
| `probe.slow` | Probe antwortet, aber deutlich langsamer als ueblich | Client |
`fallback.usage` ist der unterschaetzte Eintrag: "Wir haben heute 4.000-mal den
Notfallweg genommen und es ist niemandem aufgefallen" ist ein Befund, den weder
ein Test noch ein Anwender je erzeugt.
### 1.4 Fachliche Zusicherungen
| Name | Findet | Laeuft |
|---|---|---|
| `invariant.violated` | Vom Projekt formulierte Zusicherung gegen echte Daten verletzt | Client |
Das ist die einzige Gruppe, die tatsaechlich "Unit-Test zur Laufzeit" ist — und
fuer ihren Zweck ist genau das richtig. Sie deckt die Klasse ab, die sonst
komplett durchfaellt: **semantisch falsche, aber unauffaellige Ergebnisse.** Der
Bericht zeigt eine falsche Zahl, die Rundung stimmt seit Monaten nicht, ein
Mandant sieht Daten eines anderen. Nichts stuerzt ab, keine Kennzahl weicht ab,
keine Signatur ist neu — kein anderer Detektor sieht das.
Beispiele, wie ein Projekt sie formuliert:
Supervisor.Expect("saldo.nonnegative", () => konto.Saldo >= 0);
Supervisor.Expect("beleg.balanced", () => soll == haben);
Supervisor.Expect("mandant.unique", () => satz.MandantIds.Count == 1);
### 1.5 Was der Katalog bewusst nicht abdeckt
Ehrlichkeitshalber festgehalten: Ein Fehler, der plausibel aussieht, keine
Kennzahl bewegt, keine Ausnahme wirft und gegen keine formulierte Zusicherung
verstoesst, wird hier nicht gefunden. Der einzige Hebel dagegen sind mehr und
bessere Zusicherungen aus 1.4.
---
## 2. Befund-Format
Ein Befund ist keine Fehlermeldung. Er behauptet nicht, dass etwas kaputt ist,
sondern dass etwas ungewoehnlich ist.
POST /api/supervisor/v1/observe Scope: supervisor:report
```json
{
"project_slug": "myapp",
"instance": "kunde-4711/app-01",
"detector": "trend.monotonic",
"subject": "memory.working_set",
"observed": {"slope_per_hour": 18400000, "windows": 9},
"baseline": {"median": 412000000, "stddev": 9100000},
"severity_hint": "medium",
"build": "v1.4.2",
"commit_sha": "a21536f",
"environment": "production",
"first_seen_at": "2026-08-19T04:12:00Z",
"client_ref": "myapp/trend.monotonic/memory.working_set/v1.4.2"
}
```
`client_ref` ist bewusst aus Detektor, Gegenstand und Fassung gebaut und nicht
zufaellig: derselbe Befund darf beliebig oft gemeldet werden, ohne ein Duplikat
anzulegen. Die Deduplizierung in `BugRepo::findByClientRef()` greift dann
bereits.
`severity_hint` heisst Hinweis, nicht Schweregrad. Die Einstufung entscheidet
die empfangende Seite, die den Flottenblick hat.
---
## 3. Supervisor-Client im SDK
Ein schlanker, regelbasierter Laeufer. Kein Sprachmodell, keine
Netzabhaengigkeit fuer die Erkennung selbst.
### 3.1 Schnittstelle
```csharp
public interface IHealthProbe
{
string Name { get; } // z. B. "db.reachable"
TimeSpan Interval { get; }
Task<ProbeResult> RunAsync(CancellationToken ct);
}
```
Dazu die Meldewege, die eine Anwendung an ihren heiklen Stellen einstreut:
```csharp
Supervisor.Expect(name, bedingung); // invariant.violated
Supervisor.Swallowed(name, ex); // swallowed.exception
Supervisor.Fallback(name); // fallback.usage
Supervisor.Retry(name); // retry.storm
Supervisor.Beat(name); // Bezugspunkt fuer silence.periodic
```
Alle fuenf sind absichtlich einzeilig und ohne Rueckgabewert. Was Ueberlegung
kostet, wird nicht eingebaut.
### 3.2 Verhalten
- Zustand liegt lokal, damit `client_ref` ueber Neustarts stabil bleibt
- Zaehler werden gebuendelt gemeldet, nicht je Vorfall
- gemeldet wird nur bei Zustandswechsel oder nach Ablauf eines Ruhefensters
- `429 rate_limited` verdoppelt das Intervall (wie in der Prompt-Vorlage
beschrieben)
- faellt das Deploymentcenter aus, laeuft die Anwendung unveraendert weiter;
Befunde gehen dann verloren und werden nicht gepuffert
Der letzte Punkt ist Absicht. Ein Ueberwachungsteil, das die ueberwachte
Anwendung aufhalten oder ihren Speicher fuellen kann, ist schlimmer als keines.
### 3.3 Fassungen
- `.NET` in `client-dotnet/Deploymentcenter.Client/Supervisor.cs`
- `PHP` in `src/Modules/Supervisor/` fuer die PHP-Anwendungen
---
## 4. Serverseite
### 4.1 Aufnahme
`public/api/supervisor/v1/observe.php`, Modul `src/Modules/Supervisor/` mit
`FindingRepo`. Bestaetigte Befunde koennen in ein Bugtracker-Item uebergehen;
unbestaetigte bleiben Befund.
### 4.2 Die serverseitigen Detektoren
Sie haengen in `cli/tick.php` neben `Evaluator::run()` — minuetlicher Cron mit
Ueberlappungsschutz ist dort bereits geloest.
Wichtig fuer die Reihenfolge: **Diese Detektoren brauchen keinen neuen Client.**
`novel.error_signature`, `silence.heartbeat`, `deviation.self`,
`deviation.fleet` und `regression.build` arbeiten auf Daten, die heute schon
hereinkommen — Fehlerberichte, Watchdog-Pings, Kennzahlen. Sie liefern Nutzen,
bevor eine einzige Anwendung ein neues SDK bekommen hat.
---
## 5. Projektuebergreifend arbeiten
Einige Projekte sind stark verzahnt, das Deploymentcenter am staerksten. Ein
Agent, der das SDK in Projekt X integriert und dabei einen Fehler im
Deploymentcenter findet, muss ihn dort ablegen koennen.
### 5.1 Die heutige Sperre
`ApiAuth::enforceProject()` bricht mit `403 project_forbidden` ab, sobald ein
projektgebundenes Token ein fremdes Projekt anspricht. Die Prompt-Vorlage
fordert genau das aber ein ("Faellt dir ein Fehler im Deployment Center selbst
auf, melde ihn unter `deploymentcenter`"). Die Vorlage verspricht etwas, das die
API verweigert.
### 5.2 Loesung ueber den vorhandenen Token-Typ
Die Sperre wird nicht aufgeweicht — sie ist richtig, nur zu grob. Sie
unterscheidet nicht, **wo das Token liegt**:
- **Laufzeit-Token** (im ausgelieferten Client, auf Kundenhardware): streng
projektgebunden, nur melden, nie uebergreifend. Ein extrahiertes Kundentoken
darf nicht in fremde Tracker schreiben. Hier bleibt alles wie es ist.
- **Werkbank-Token** (Claude Code, MCP, eigene Maschine): darf ueber die
Projekte hinweg.
`TokenManager::OWNER_TYPES` kennt `dev_agent` bereits. Da die Instanz
ausschliesslich eigene Projekte fuehrt und es keine geteilte Instanz fuer
Fremde geben wird, genuegt das als Unterscheidung — kein neues Feld, keine
Mandantentrennung.
`enforceProject()` bekommt eine Ausnahme: `owner_type = 'dev_agent'` darf fremde
Projekte ansprechen. Lesend breit, schreibend eng — melden und kommentieren ja,
fremde Items aufloesen oder schliessen nein.
### 5.3 Das eigentliche Risiko ist Fehlleitung
Ein Agent in Projekt X kann kaum unterscheiden, ob das Deploymentcenter kaputt
ist oder ob X das SDK falsch benutzt. Die Voreinstellung eines hilfsbereiten
Modells ist im Zweifel "melde es".
Schlimmer als der Laerm ist der Folgeschaden: Fehlmeldungen vergiften die
Statistik, und die Statistik ist die Grundlage der Detektoren aus 1.1. Ein
Detektor, der auf Fehlmeldungen kalibriert, ist wertlos.
Drei Bremsen:
- **Begruendungspflicht.** Ein Bericht gegen ein fremdes Projekt verlangt
`evidence`: was wurde beobachtet, und warum liegt die Ursache dort und nicht
hier. Etwas ausformulieren zu muessen kostet einen Reflex fast immer den Kopf.
- **Eigener Eingang.** Fremdmeldungen landen mit `status = 'triage_external'`,
nicht im offenen Backlog, und zaehlen erst nach Bestaetigung in die Statistik.
- **Gedeckelter Schweregrad.** Fremde Melder duerfen `idea`, `wishlist`, `low`,
`medium`. `high` und `critical` vergibt die empfangende Seite.
### 5.4 Herkunft und Buendelung
Jeder uebergreifende Bericht traegt `reported_from`. Das ist keine Buchhaltung,
sondern das wertvollste Signal im Vorgang: Melden drei Projekte unabhaengig
dieselbe Sache gegen das Deploymentcenter, ist das kein "low" mehr. Statt drei
Items entsteht eines mit wachsender `affects`-Liste.
Die Buendelung laeuft ueber die Signatur, nicht ueber `client_ref` — der ist nur
je Melder eindeutig.
### 5.5 Beziehungen zwischen Projekten
`dc_projects` kennt heute Slug, Name und Notiz. Es fehlt die Beziehung.
Neue Tabelle `dc_project_links` mit `from_slug`, `to_slug`, `kind`
(`uses_sdk`, `depends_on`, `shares_code`) und `integrated_version`.
Das loest nebenbei ein Wartungsproblem: Die von Hand gepflegte Zeile
"Deploymentcenter-Anbindung: SDK 2.5.1" in jeder Projekt-`CLAUDE.md` wandert
dorthin, wo sie hingehoert. Der Agent meldet die Fassung ueber MCP, wenn er die
Anbindung anfasst. Damit ist zum ersten Mal beantwortbar: **Welche meiner
Projekte sind von diesem SDK-Fehler betroffen?**
### 5.6 Rueckkanal
Wird das Item in SDK 2.6.0 behoben, muessen die meldenden Projekte das
erfahren — sonst meldet Projekt X denselben Punkt in drei Monaten erneut und
baut seinen Workaround nie zurueck.
`GET /api/updateservice/v1/changelog?since=…` liefert bereits `action_items`.
Ergaenzt um "was du gemeldet hast, ist in 2.6.0 erledigt" schliesst sich der
Kreis.
---
## 6. MCP-Server
### 6.1 Was er bringt
Der offensichtliche Teil: Anmeldung einmal statt je Projekt, Schemata statt
Prosa, kein `curl` aus dem Gedaechtnis.
Der wertvollere: **`docs/AGENT_PROMPT_TEMPLATE.md` verschwindet fast
vollstaendig.** Die Datei ist heute in jedes Projekt einkopiert, samt der Zeile
"SDK 2.5.1, integriert am …". Das ist eine Wartungslast, die je Projekt waechst
und beim ersten Vergessen schweigend veraltet. Ein MCP-Server liefert immer die
aktuelle Form — die Frage "gegen welche Fassung habe ich integriert" stellt sich
nicht mehr, weil nichts mehr einkopiert ist.
### 6.2 Der Schnitt: nicht aus OpenAPI erzeugen
Naheliegend waere, die Werkzeuge aus `public/api/openapi.php` abzuleiten. Dort
stehen 24 Endpunkte — 24 Werkzeuge sind ein Kontextfresser, denn
Werkzeugdefinitionen liegen dauerhaft im Fenster, anders als eine Doku, die man
einmal liest. Ein aufgeblaehter MCP-Server ist schlechter als die 30 Zeilen
Prompt von heute.
Kuratiert, an der Arbeitsweise entlang statt an der API:
| Werkzeug | Zweck |
|---|---|
| `dc_projects` | Projekte mit Kurzbeschreibung **und Beziehung zum aktuellen Projekt** |
| `dc_next_task` | Item uebernehmen, `409 already_claimed` intern behandelt |
| `dc_get_item` | Details, Verlauf, Kommentare |
| `dc_search` | Items filtern: Projekt, Status, Schweregrad, Build |
| `dc_report` | Melden — mit `project_slug`, `reported_from`, bei fremdem Ziel `evidence` |
| `dc_comment` | Fortschritt mit `action_taken` |
| `dc_resolve` / `dc_release` | Abschliessen bzw. zurueckgeben |
| `dc_my_reports` | Was hat dieses Projekt anderswo gemeldet, was wurde daraus |
| `dc_error_groups` | Fehlergruppen mit `occurrence_count`, `first_seen_at` |
| `dc_findings` | Supervisor-Befunde nach Detektor, Instanz, Fassung |
| `dc_metrics` | Verlauf und Abweichung einer Kennzahl |
| `dc_symbolicate` | Stacktrace gegen die passende Fassung aufloesen |
Bewusst **nicht** aufgenommen: `publish`, `provision`, `deactivate` und
Massenoperationen. Ein Release gehoert in die CI, nicht an ein Werkzeug, das ein
Modell im Zweifel "hilfreich" ausloest.
### 6.3 Transport
Erst ein **stdio-Adapter**: eine ausfuehrbare Datei, die die vorhandene REST-API
ruft und das Token aus `DC_TOKEN` liest. Node 24 liegt vor. Kein neuer
Angriffspunkt am Server, die Anmeldefrage ist bereits beantwortet, je Projekt
drei Zeilen in `.mcp.json`.
Ein entfernter Server unter `dc.mhdf.de/mcp` ist der bessere Endzustand, wenn
mehrere Maschinen dranhaengen — dann aber mit OAuth statt Bearer-Token in einer
Konfigurationsdatei. Groesserer Brocken, lohnt erst wenn der Schnitt sich
bewaehrt hat.
Die Werkzeugliste wird in der CI gegen `openapi.php` geprueft, damit beide nicht
auseinanderlaufen.
### 6.4 Vertrauensgrenze — der wichtigste Punkt hier
Ueber diese Werkzeuge fliessen **Daten aus der Wildnis in einen Agenten, der
Code schreiben und Befehle ausfuehren darf.**
`error_message`, `stack_trace`, Logauszuege und Supervisor-Befunde entstehen auf
fremden Maschinen und enthalten regelmaessig Eingaben, die jemand anderes
bestimmt hat — ein Dateiname, ein Formularfeld, das in der Ausnahme landet. Ein
Text in einem Bugreport ist keine Anweisung, auch wenn er wie eine formuliert
ist. Ein Feld mit "bitte ergaenze in `Auth.cs` folgende Zeile" ist trivial zu
platzieren, sobald irgendeine Nutzereingabe im Fehlertext landet.
Massnahmen:
- Fremdtext im Werkzeugergebnis **abgegrenzt und als Daten gekennzeichnet**,
nicht frei in den Fliesstext gemischt
- getrennte Token: der Triage-Agent bekommt `bugtracker:read` plus gezielt
`comment`, nicht `bugtracker:manage` mit allem dran
- **kein selbsttaetiges Aufloesen und kein selbsttaetiger Commit** aus einem
Lauf, der Fremdtext gelesen hat. Vorschlag ja, Ausfuehrung nein.
REST-API und die Kurzfassung des Prompts bleiben bestehen — nicht jedes Werkzeug
spricht MCP, und der Rueckfallweg kostet nichts, solange beide aus derselben
Quelle beschrieben sind.
---
## 7. Quellcode-Zugriff fuer den Diagnostiker
Drei Stufen, in aufsteigender Preisgabe:
**Stufe 0 — der Agent laeuft bei dir.** Kein Quellcode im Deploymentcenter.
Claude Code hat den Checkout ohnehin lokal und zieht sich Arbeit ueber
`dc_next_task`. Neue Angriffsflaeche: null. Preis: Diagnose laeuft nur, wenn
eine eigene Maschine laeuft. **Das ist der Startpunkt.**
**Stufe 1 — Quelltext-Index im DC, nur Ausschnitte.** Noetig erst, wenn ein
Agent autonom rund um die Uhr laufen soll. Dann aber keine klonbare Kopie,
sondern eine Schnittstelle, die nie ein Ganzes herausgibt:
GET /api/source/v1/snippet?commit=a21536f&file=…&line=42&context=40
GET /api/source/v1/search?q=…&limit=20
Neuer Scope `source:read`, nicht an Client-Token vergeben, verschluesselt
abgelegt, Budget je Token und Tag, jeder Abruf im Pruefprotokoll. Der
Unterschied zwischen "darf nachschlagen" und "darf mitnehmen".
**Stufe 2 — nur Metadaten.** Signaturen, Aufrufgraph, Doc-Kommentare, ohne
Methodenkoerper. Reicht zur Triage, reicht nicht zum Nachbauen.
### Symbol-Upload
Unabhaengig von der Stufe: Der Packager laedt beim `publish` die PDB bzw.
Sourcemap mit hoch, verknuepft mit `commit_sha`. Ausgeliefert wird ein
gestripptes Binary. Der Kunde bekommt `at 0x4a2f`, du bekommst
`UserAuthService.cs:42`. Kein Quellcode verlaesst das Haus, die Berichte werden
trotzdem so gut wie mit Debug-Build. `dc_symbolicate` haengt daran.
### Was nicht funktioniert
- Deploy-Key oder Repo-Token im Client — dekompiliert in Minuten
- Quellcode verschluesselt mitliefern — der Client muss entschluesseln koennen,
also kann es der Kunde auch
- den Sprachmodell-Supervisor auf der Kundenmaschine — dann liegt der
API-Schluessel dort, und aller Kontext im Speicher fremder Hardware
- Obfuskation als Schutz — verzoegert, verhindert nicht
---
## 8. Datenbank
**Erledigt — `sql/migrations/013_cross_project_reporting.sql`** (Etappe 1):
- `dc_project_links` — `from_slug`, `to_slug`, `kind`, `integrated_version`
- `bugtracker_items` — neue Spalten `reported_from`, `evidence`
- `bugtracker_item_affects` — betroffene Projekte je Item
- `bugtracker_items.status` — Wert `triage_external` ergaenzt
**Erledigt — `sql/migrations/014_supervisor_findings.sql`** (Etappe 2):
- `supervisor_findings` — Befunde nach dem Format aus Abschnitt 2
- `watchdog_monitors.project_slug` — Kohortenbildung fuer den Flottenvergleich
- Taktgeber `supervisor_detectors` in `watchdog_cron_jobs`
Scopes in `TokenManager::KNOWN_SCOPES`, ergaenzt:
- `supervisor:report` — Befunde einliefern
- `supervisor:read` — Befunde lesen
- `supervisor:manage` — Befunde sichten und einstufen
- `source:read` — nur fuer Stufe 1, spaeter
`IMPLIED_SCOPES` entsprechend: `supervisor:report` und `supervisor:manage`
schliessen `supervisor:read` ein. Dass Melden und Einstufen getrennt sind, ist
Absicht: ein Beobachter auf Kundenhardware darf melden, aber nicht
entscheiden, dass seine eigene Meldung erledigt ist.
Etappe 1 brauchte keinen neuen Scope: die Unterscheidung laeuft ueber
`owner_type = 'dev_agent'`, den `TokenManager::OWNER_TYPES` bereits kannte.
---
## 9. Etappen
**Etappe 1 — MCP-Adapter ueber den Bestand. ERLEDIGT.**
Werkzeuge fuer alles, was heute schon existiert: `dc_projects`, `dc_next_task`,
`dc_get_item`, `dc_search`, `dc_report`, `dc_comment`, `dc_resolve`,
`dc_release`, `dc_error_groups` — in `mcp/deploymentcenter-mcp.mjs`, ohne
Fremdabhaengigkeiten. Dazu `dev_agent` in `enforceProject()`,
`dc_project_links`, `reported_from`, `evidence`, `triage_external`.
Kein Serverumbau, sofort nutzbar, ersetzt den Grossteil der Prompt-Vorlage.
Noch offen aus dieser Etappe: ein Werkzeug, mit dem der Agent
`integrated_version` fortschreibt (`ProjectLinks::record()` ist da, der
Endpunkt fehlt), und das Zurueckziehen der Prompt-Vorlage in den Projekten.
**Etappe 2 — Katalog und serverseitige Detektoren. ERLEDIGT.**
Katalog in `src/Modules/Supervisor/Catalog.php`, Ablage in `FindingRepo`,
Detektoren in `ServerDetectors` (haengen in `cli/tick.php`, laufen alle zehn
Minuten), Schnittstelle unter `/api/supervisor/v1/`, dazu `dc_findings`,
`dc_finding_summary` und `dc_metrics` im MCP-Adapter.
**Nutzen ohne eine einzige Client-Aenderung.**
Umgesetzt sind `novel.error_signature`, `silence.heartbeat`, `deviation.self`,
`deviation.fleet` und `regression.build`. Offen aus 1.1 bleibt
`novel.log_template` — dafuer muessten erst Logauszuege strukturiert
hereinkommen.
Eine Einschraenkung, die man kennen muss: `regression.build` vergleicht die
beiden hoechsten Fassungen im Feld im **Querschnitt**, also verschiedene
Maschinen zur selben Zeit — nicht dieselbe Maschine vor und nach dem Update.
Ein echter Laengsschnitt braeuchte eine Historie der `app_version` je Monitor;
das Feld wird heute ueberschrieben.
Voraussetzung fuer `deviation.fleet` und `regression.build`: Monitore brauchen
eine Projektzuordnung (`watchdog_monitors.project_slug`, neu in Migration 014).
Sie wird administrativ gesetzt, nicht vom Agenten gemeldet — sonst braeuchte
diese Etappe doch einen Rollout. Ohne Zuordnung laufen die uebrigen Detektoren
weiter; ihre Befunde landen unter `supervisor.default_project`.
**Etappe 3 — Supervisor-Client. ERLEDIGT.**
`.NET` in `client-dotnet/Deploymentcenter.Client/Supervisor*.cs`, PHP als
einzelne Datei in `client-php/DeploymentcenterSupervisor.php`, dazu
`docs/SUPERVISOR_INTEGRATION_GUIDE.md`.
Umgesetzt sind alle clientseitigen Detektoren aus 1.2 bis 1.4:
`trend.monotonic`, `trend.saturation`, `trend.latency`,
`swallowed.exception`, `fallback.usage`, `retry.storm`, `probe.failed`,
`probe.slow`, `silence.periodic`, `invariant.violated`.
Der PHP-Entwurf weicht notwendig ab: es gibt kein dauerhaft laufendes Programm,
in dem ein Beobachter mitlaufen koennte. Der Zustand liegt deshalb in einer
Datei unter Sperre, ausgewertet wird von der ersten Anfrage nach Ablauf des
Intervalls, gesendet wird nach `fastcgi_finish_request()`. Proben laufen
dadurch in einer echten Anfrage mit und muessen billig sein - das steht so in
der Anleitung.
Ausrollen ueber die Projekte, eines nach dem anderen.
**Etappe 4 — Symbol-Upload und `dc_symbolicate`.**
Packager erweitern, Symbolisierung serverseitig.
**Etappe 5 — Quelltext-Index Stufe 1.**
Nur falls ein autonomer Agent ohne laufende eigene Maschine gebraucht wird.
Bis dahin bewusst offen gelassen.
---
## 10. Offene Punkte
**Entschieden beim Umsetzen** (standen hier vorher als offen):
- Aufbewahrung der Befunde: 30 Tage in `FindingRepo::purge()`, aufgerufen am
Ende jedes Detektorlaufs. Deutlich kuerzer als bei Bugtracker-Items, weil
Befunde um Groessenordnungen haeufiger entstehen. Entfernt werden nur
erledigte, stummgeschaltete und uebernommene - ein offener Befund, der seit
Wochen dasteht, ist eine unbeantwortete Frage und soll unbequem bleiben.
- Kohortengroesse fuer `deviation.fleet`: fuenf, als `MIN_COHORT` in
`ServerDetectors`. Darunter ist die Streuung so grob geschaetzt, dass jeder
zweite Wert als Ausreisser durchginge.
- `probe.slow` ist ein eigener Detektor geblieben. Er vergleicht gegen das
gleitende Mittel der Probe selbst und nur ueber erfolgreiche Laeufe -
`deviation.self` haette dafuer den falschen Massstab.
**Weiterhin offen:**
- `novel.log_template` aus Abschnitt 1.1 - dafuer muessten erst Logauszuege
strukturiert hereinkommen.
- Ein Laengsschnitt fuer `regression.build`. Braucht eine Historie der
`app_version` je Monitor; das Feld wird heute ueberschrieben.
- Die Schwellen der clientseitigen Trenderkennung (zehn Prozent Zuwachs ueber
sechs Fenster) sind gesetzt, aber noch an keiner echten Installation
nachgeprueft.
> Wer hier etwas ergaenzt: Beim Umsetzen bitte auch wieder herausnehmen. Diese
> Liste stand einmal auf einem Stand, den der Code laengst ueberholt hatte, und
> hat prompt zu einer Fehlmeldung gefuehrt.