# Deploymentcenter-Integration > **Beschreibung der Verdrahtung** (Heartbeat, Fehler-Stream, Bugtracker, Updates). > Offene Punkte dazu stehen in der [Roadmap](Roadmap.md) 3.5, nicht hier. Stand: 2026-08-08, Deploymentcenter **2.1**. Ersetzt den früheren `Integrationsplan-WatchDog-LicenseLabrador.md`. ClawdDotNet spricht das [Deploymentcenter](../../Deploymentcenter/docs/README.md) als **eine** Gegenstelle an. Vorher waren es zwei Fremdprojekte mit je eigenem Server, eigenem Schlüssel und eigener Anleitung: | Vorher | Jetzt | |---|---| | WatchDog (`watchdog.mhdf.de`, `X-Watchdog-Key`) | Deploymentcenter-Modul Watchdog, `Authorization: Bearer` | | LicenseLabrador (`license.mhdf.de`, Ed25519-Public-Key) | Deploymentcenter-Modul Lizenz | | — | Update-Prüfung | | — | Fehler-Stream (ungefangene Ausnahmen) | | — | Bugtracker | Eine Adresse, ein Token. Beides steht in den Anwendungseinstellungen. --- ## 1. Was der Betreiber einzutragen hat | Ort | Wert | |---|---| | Einstellungen → Deploymentcenter → **Server-URL** | `https://dc.mhdf.de` (Vorgabe) | | Einstellungen → Deploymentcenter → **Token** | Master-Token mit `watchdog:ping` + `bugtracker:report` | | Einstellungen → Lizenz → **Lizenzschlüssel** | Der Schlüssel für das Projekt `clawddotnet` | | Worker-Tab → Dienst **Instanz-Watchdog** | einschalten, greift beim nächsten Start der Instanz | Das Token entsteht im WebUI unter **Token-Verwaltung → Master-Token erstellen**. Es wird verschlüsselt (DPAPI) in `Settings.json` abgelegt. > **Bis die Avalonia-Einstellungsansicht steht**, gibt es für diese Felder noch keine > Oberfläche — die Seite „Einstellungen" ist ein Platzhalter. Die Werte kommen > vorläufig von Hand in `Settings.json` (Ort steht beim Start im Protokoll: > `%APPDATA%\ClawdDotNet\Settings.json`, unter Linux `$XDG_CONFIG_HOME`) bzw. in die > `instance.json` der Instanz. Das Token wird beim ersten Speichern durch die Anwendung > verschlüsselt; im Klartext eingetragen funktioniert es ebenfalls, weil der > `SecretProtector` beide Richtungen verträgt. **Serverseitig ist eine Sache Pflicht**, sonst ist die Überwachung wertlos: der Evaluator-Cron. Ohne ihn ändert sich ein Monitor-Zustand nur beim Eintreffen eines Heartbeats — eine abgestürzte Instanz bliebe dauerhaft grün. ```bash * * * * * curl -fsS -H "Authorization: Bearer " https://dc.mhdf.de/api/watchdog/v1/evaluate > /dev/null ``` --- ## 2. Watchdog — ein Monitor je Instanz Das war die Vorgabe und ist jetzt sauber abgedeckt: Der Server führt Monitore über das Paar `source` + `instance` (`UNIQUE KEY uq_monitor (source, instance)` in `sql/schema.sql`). Alle Instanzen melden unter `source = "clawddotnet"` und tragen ihre eigene `instance`. Fällt eine von dreien aus, fällt genau deren Monitor — und nur der schlägt Alarm. `instance` ist standardmäßig die `InstanceId` (stabil, aber im Dashboard nichtssagend). In den Instanz-Einstellungen lässt sich stattdessen ein Name eintragen ([`WatchdogConfig.Instance`](../src/ClawdDotNet.Core/Config/WatchdogConfig.cs)); ein späterer Wechsel legt allerdings einen neuen Monitor an. **Eine Registrierung vorab gibt es nicht mehr.** Der Monitor entsteht beim ersten Heartbeat von selbst (`INSERT … ON DUPLICATE KEY UPDATE`). Der frühere Weg über `POST /api/register` hatte im Deploymentcenter nie ein Gegenstück — die alte Anbindung lief in dieser Form also gegen einen Endpunkt, den es nicht gibt. ### Was der Heartbeat trägt ``` POST /api/watchdog/v1/ping Authorization: Bearer ``` | Feld | Inhalt | |---|---| | `source` / `instance` | `clawddotnet` / InstanceId bzw. eingestellter Name | | `status` | `ok`, `warning`, `error` — beim Beenden `stopped` | | `interval` | 60 s (Vorgabe). Daraus leitet der Evaluator ab: 2× → `warning`, 4× → `down` | | `message` | Instanzname + Kurzbegründung | | `os` | Betriebssystem + .NET-Version | | `version` | Produktversion (2.1). Landet in `watchdog_monitors.app_version` — bei mehreren Instanzen der Unterschied zwischen „läuft" und „läuft noch auf der alten Fassung" | | `checks` | `agents`, `scheduler`, `budget` — siehe unten | | `metrics` | `agentCount`, `runningChats`, `todayCostUsd`, `todayTokens` | ### `checks` — der eigentliche Gewinn Ein Heartbeat beweist nur, dass ein Faden läuft. Deshalb geht der selbst ermittelte Zustand je Teilbereich mit; schlägt eine Prüfung fehl, stuft der Server einen als `ok` gemeldeten Beat auf `warning` herab und nennt in der Antwort die betroffene. | Prüfung | Fehlschlag bedeutet | |---|---| | `agents` | Kein OpenRouter-Key — die Instanz läuft, arbeitet aber nichts ab | | `scheduler` | Die Taktschleife des Aufgaben-Scanners ist ausgestiegen | | `budget` | Tagesgrenze für Kosten oder Token erreicht | „Scanner noch nicht gestartet" gilt **nicht** als Fehlschlag: Er läuft erst nach der Startabgleichung an, der erste Heartbeat geht sofort raus. Sonst gäbe es bei jedem Start ein `warning_raised` und kurz darauf ein `recovered` — zwei Einträge im Ereignisprotokoll für einen Normalvorgang. ### Metriken sind nur Zahlen Der Server legt numerische Werte mit Zeitstempel ab (14 Tage) und vergleicht den aktuellen Wert mit dem Sieben-Tage-Schnitt desselben Monitors. Nicht-numerische Werte verwirft er dabei stillschweigend — `instanceId`, `instanceName` und `buildVersion` standen früher in den Metriken und waren dort wirkungslos. Beschreibendes steht jetzt in `message` und `os`. ### Angekündigtes Ende Beim Herunterfahren geht ein Heartbeat mit `status: "stopped"` raus, danach das Ereignis `stopped_graceful`. Der Evaluator lässt einen so gemeldeten Monitor in Ruhe. Ohne das erzeugte jedes geplante Beenden wenige Minuten später einen Fehlalarm. Nebenbei korrigiert: Die alte Anbindung schickte die Ereignisarten `start` und `stop` — beide stehen nicht auf der Liste des Servers und landeten stillschweigend als `started`. Jetzt sind es `started` und `stopped_graceful`. ### Token je Instanz Beim ersten Start tauscht die Instanz das anwendungsweite Token über `POST /api/tokens/v1/provision` gegen ein eigenes, auf `watchdog:ping` und `bugtracker:report` beschränktes Sub-Token und legt es verschlüsselt in der Instanzkonfiguration ab. Danach liegt auf der Instanz nicht mehr das Master-Token, und ein einzelner Zugang lässt sich widerrufen, ohne die anderen mitzunehmen. Das ist derselbe Zweck, den die frühere Selbstregistrierung hatte. Scheitert es (etwa weil das hinterlegte Token selbst ein Sub-Token ist und keine weiteren ausstellen darf), wird mit dem hinterlegten Token gemeldet — Monitoring, das nur bei perfekter Rechtelage läuft, ist genau dann still, wenn man es braucht. --- ## 3. Lizenz Startprüfung in [`LicenseGate`](../src/ClawdDotNet.App/Services/LicenseGate.cs). Die Offline-Gnadenfrist steckt im SDK: Es legt nach jeder erfolgreichen Prüfung einen mit AES-GCM verschlüsselten, an die Hardware gebundenen Zwischenspeicher an (`LLS2`, seit 2.1 Schema 3) und trägt damit über Ausfälle hinweg. ### Urteil und Fehlversuch sind zwei verschiedene Dinge Das ist der Kern der 2.1-Anpassung. `LicenseValidationResult.IsTransient` unterscheidet: | | Statuswerte | Folge | |---|---|---| | **Urteil des Servers** | `revoked`, `expired`, `not_found`, `activation_limit`, `suspended`, `clock_rollback` | Anwendung startet nicht bzw. beendet sich | | **Kein Urteil erhalten** | `server_unavailable`, `cache_expired` | Warnung, Betrieb läuft weiter | Nur das Urteil sperrt. Ein Serverausfall darf nicht jede Installation gleichzeitig aussperren — und eine Drosselung (`429`) oder ein `500` sind Aussagen über den Server, nicht über die Lizenz. Das gilt an beiden Stellen gleich: Startprüfung und laufende Nachprüfung fragen dasselbe Merkmal ab. > **Bewusst in Kauf genommen:** Ein Rechner, der die Gegenstelle nie erreicht, läuft > damit auf Dauer mit Warnung weiter — auch nach Ablauf der Gnadenfrist > (`cache_expired` ist laut SDK-Vertrag vorübergehend). Wer das anders will, prüft in > [`LicenseGate`](../src/ClawdDotNet.App/Services/LicenseGate.cs) zusätzlich auf > `cache_expired` und behandelt es als Urteil. Es sollte eine Entscheidung sein, nicht > ein Nebeneffekt. ### Offline-Gnadenfrist ist echt begrenzt Seit 2.1 wertet der Client `cache_ttl_hours` des Projekts aus (Vorgabe 168 h). Vorher galt faktisch das Ablaufdatum der Lizenz — bei einer Lizenz bis 2040 also unbegrenzt. Der verbleibende Rest steht in `CacheExpiresAt` und wird beim Start angezeigt, wenn die Prüfung aus dem Zwischenspeicher kam. `state.dat` steigt auf Schema 3; Schema 2 wird weiter gelesen. Ein Rückschritt auf ein älteres SDK verwirft den Zwischenspeicher — dann ist einmal eine Online-Prüfung nötig. ### Kein Public-Key mehr Die frühere Fassung führte einen Ed25519-Public-Key als „Vertrauensanker". Im Deploymentcenter gibt es dazu keine Gegenseite — der Client liest ausschließlich das Feld `status`. Ein Schlüssel, der nichts prüft, ist schlimmer als keiner: Er lässt Schutz vermuten, wo keiner ist. Details in [Deploymentcenter-Anbindung-Review](archiv/Deploymentcenter-Anbindung-Review.md), Abschnitt 2.1. ### Der Projekt-Slug ist `clawddotnet` [`LicenseInfo.ProductSlug`](../src/ClawdDotNet.App/Services/LicenseInfo.cs) gilt für **alle** Module — Lizenz, Bugtracker, Fehler-Stream und Update-Prüfung greifen auf dieselbe Tabelle `dc_projects` zu. Bis zur Umstellung stand hier `clawd`, der Name aus dem LicenseLabrador-Backend. Das ist eine Falle mit langer Zündschnur: Der Server beantwortet ein unbekanntes Projekt mit demselben `not_found` wie einen unbekannten Schlüssel — der Unterschied steht ausschließlich in `message` (`"Project not found"` gegen `"Invalid license key"`). Wer den Text nicht durchreicht, sucht den Fehler beim Lizenzschlüssel, während das Projekt gar nicht existiert. Der Torwächter gibt die Serverantwort deshalb mit aus und schreibt sie ins Protokoll. ### Deaktivieren läuft über das WebUI `POST /api/license/v1/deactivate` verlangt den `shared_key` des Servers. Der gehört nicht in eine ausgelieferte Anwendung, deshalb ist der Weg die Hardware-Liste im WebUI (Schaltfläche „Freigeben"). ### Laufende Nachprüfung [`LicenseWatch`](../src/ClawdDotNet.App/Services/LicenseWatch.cs) prüft alle zwölf Stunden nach — dieselbe Unterscheidung wie oben. Ohne das wirkt ein Widerruf erst beim nächsten Start, bei einem wochenlang laufenden Dienst also praktisch nie. --- ## 4. Version und Updates ### Eine Stelle für die Version `` in [Directory.Build.props](../Directory.Build.props) ist die Wahrheit. `Deploymentcenter.BuildInfo.targets` (seit 2.1 einbindbar) erzeugt daraus zur Übersetzungszeit `ClawdDotNet.App.ReleaseInfo` mit `Version`, `GitCommit`, `GitCommitShort`, `BuildDateUtc`, `Channel` und `Summary`. Der Wert geht an vier Stellen nach draußen, die vorher alle geraten haben: | Stelle | Vorher | |---|---| | Aktivierungsliste (`app_version`) | fest `"1.0.0"` im SDK — jede Installation gleich | | Heartbeat (`version`) | gab es nicht | | Fehlermeldungen (`build`) | — | | Versionsvergleich der Update-Prüfung | `0.0.`, behelfsweise | Die Klasse heißt bewusst `ReleaseInfo`, nicht `BuildInfo`: Diesen Namen trägt in `ClawdDotNet.Core` schon ein von Hand geführter Zähler mit Änderungstext. Zwei gleichnamige Klassen mit verschiedener Bedeutung wären eine Falle. Umgestellt über `DeploymentcenterBuildInfoClass` in der csproj. ### Prüfung Einmalig beim Start gegen `GET /api/updateservice/v1/check`, über `Deploymentcenter.Client.UpdateClient`. Läuft nebenher und blockiert nichts; liegt eine neuere Version vor, erscheint ein Hinweis mit Changelog. Ob und wann aktualisiert wird, entscheidet der Benutzer — eine Anwendung, die sich beim Start selbst beendet, um sich zu erneuern, ist genau dann im Weg, wenn man sie braucht. Seit 2.1 liefern beide Wege vollständige Daten: die statische `latest.json` in camelCase, die API in snake_case, jeweils über ein eigenes Modell (`VersionInfo` bzw. `ApiReleaseInfo`). Vorher kam über den API-Zweig außer der Versionsnummer nichts an — und der ist genau der Rückfall, wenn die `latest.json` fehlt. Die Download-Adresse wird mitgeführt (`UpdateAvailability.DownloadUrl`), damit der `update-agent` später ohne weitere Änderung anschließen kann. Der `update-agent` ist noch **nicht** eingebunden, und solange kein Release über `pack-and-deploy` veröffentlicht wird, hat die Prüfung nichts zu finden. --- ## 5. Fehler-Stream Ungefangene Ausnahmen gehen an `POST /api/errors/v1/report`. Verdrahtet in [`App.axaml.cs`](../src/ClawdDotNet.Desktop/App.axaml.cs) an drei Stellen: `AppDomain.UnhandledException`, `TaskScheduler.UnobservedTaskException` und `Dispatcher.UIThread.UnhandledException`. Erst nach dem Aufbau verdrahtet, nicht in `Main`: Vorher gibt es weder Einstellungen noch Token. Die Kehrseite ist bewusst in Kauf genommen — ein Absturz *während* des Starts erreicht das Deploymentcenter nicht, steht aber im Protokoll. **Eigene Drosselung** in [`ErrorReporter`](../src/ClawdDotNet.Core/Deploymentcenter/ErrorReporter.cs): Derselbe Fehler (Typ + oberste Stelle im Stacktrace) geht höchstens einmal alle fünf Minuten raus. Der Server drosselt auch, aber erst, nachdem die Anfragen über die Leitung waren. Die Fehlermeldung selbst gehört nicht zum Kennzeichen — sie enthält oft wechselnde Werte, und dann wäre jeder Aufruf ein neuer Fehler. Bekannte, harmlose Fehler lassen sich serverseitig unter **Bugtracker → Ignore-Regeln** stummschalten. Sie werden weiter gezählt; der Zähler ist der Zweck: Dass ein bekannter Fehler auftritt, ist normal — dass er plötzlich hundertmal so oft auftritt, bedeutet, dass sich etwas geändert hat. --- ## 6. Bugtracker [`BugtrackerClient`](../src/ClawdDotNet.Core/Deploymentcenter/BugtrackerClient.cs) für bewusst formulierte Einträge (Fehler, Wunsch, Idee) mit Titel und Beschreibung, gegen `POST /api/bugtracker/v1/report`. Der Absender wird serverseitig aus dem Token abgeleitet und lässt sich nicht frei wählen. Der Client ist da und über `AppHost.Deploymentcenter.Bugtracker` erreichbar; **eine Oberfläche dafür fehlt noch** („Fehler melden"-Schaltfläche). Ein Agenten-Tool wäre der nächste sinnvolle Schritt — Agenten könnten dann selbst Wünsche und Fehler eintragen, und der Agenten-Workflow des Deploymentcenters (Claim/Lease über `manage?action=next`) würde sie abarbeiten. --- ## 7. Wo was liegt | Datei | Inhalt | |---|---| | [`Deploymentcenter/DeploymentcenterApi.cs`](../src/ClawdDotNet.Core/Deploymentcenter/DeploymentcenterApi.cs) | Gemeinsamer Unterbau: Bearer-Header, HTTPS-Pflicht, Umschlag auspacken, `DeploymentcenterException` mit stabilem `Code` | | [`Deploymentcenter/Watchdog/`](../src/ClawdDotNet.Core/Deploymentcenter/Watchdog) | Heartbeat-Client, Zustandsermittlung, Takt-Dienst | | [`Deploymentcenter/ErrorReporter.cs`](../src/ClawdDotNet.Core/Deploymentcenter/ErrorReporter.cs) | Fehler-Stream mit Drosselung | | [`Deploymentcenter/BugtrackerClient.cs`](../src/ClawdDotNet.Core/Deploymentcenter/BugtrackerClient.cs) | Bugtracker-Einträge | | [`Deploymentcenter/TokenProvisioner.cs`](../src/ClawdDotNet.Core/Deploymentcenter/TokenProvisioner.cs) | Sub-Token je Instanz | | [`Services/DeploymentcenterService.cs`](../src/ClawdDotNet.App/Services/DeploymentcenterService.cs) | Verdrahtung: Token beschaffen, Heartbeat starten, Update prüfen | | [`Services/LicenseGate.cs`](../src/ClawdDotNet.App/Services/LicenseGate.cs) | Startprüfung | | [`Services/LicenseWatch.cs`](../src/ClawdDotNet.App/Services/LicenseWatch.cs) | Laufende Nachprüfung | Die Lizenz läuft bewusst **nicht** über `DeploymentcenterApi`: Sie hat ein eigenes Antwortformat (kein `status`/`error`-Umschlag — `status` trägt dort den Lizenzzustand), einen eigenen Zwischenspeicher und muss vor allem anderen laufen. Watchdog, Fehler-Stream und Bugtracker deckt das SDK `Deploymentcenter.Client` nicht ab; dafür ist der eigene Unterbau da. Hardware-ID v2, Lizenz-Zwischenspeicher und Update-Prüfung kommen aus dem SDK — die nachzubauen wäre Verdopplung. --- ## 8. Tests [`tests/ClawdDotNet.Core.Tests/Deploymentcenter/`](../tests/ClawdDotNet.Core.Tests/Deploymentcenter): Bearer-Header, Fehlerumschlag → Ausnahme mit Code (auch bei HTTP 200), HTTPS-Pflicht mit Localhost-Ausnahme, Heartbeat-Pfad und -Rumpf, zwei Instanzen → zwei Monitore, Checks und Metriken, Antwort-Auswertung, Drosselung des Fehler-Streams, Sub-Token-Bezug. --- ## 9. Offen 1. **Oberfläche für den Bugtracker** — Client vorhanden, Schaltfläche fehlt. 2. **Agenten-Tool für den Bugtracker** — würde den Agenten-Workflow des Deploymentcenters nutzbar machen. 3. **Release-Strecke** — `pack-and-deploy` aufrufen und `` dabei mitgeben, danach `update-agent` einbinden. Die Versionsnummer selbst ist mit 2.1 erledigt. 4. **SDK als Git-Submodul** unter `external/` statt Cross-Repo-Pfad. 5. **Hierarchie** (`parent_source`): Läuft die Instanz auf einem Host, der selbst als Monitor geführt wird, sollte sie ihn als übergeordnete Entität eingetragen bekommen — sonst erzeugt ein Hostausfall eine Meldung je Instanz. Das ist im WebUI zu pflegen, nicht im Client (siehe aber Anmerkung 2 in den Rückmeldungen).