Files
ClawdDotNet/docs/archiv/Deploymentcenter-Anbindung-Review.md
RichardandClaude Opus 5 740649789e Eine Roadmap statt neun verteilter Listen
Der Stand lag ueber eine Bestandsaufnahme, drei Konzeptpapiere, vier
Umsetzungsplaene und zwei Deploymentcenter-Dokumente verteilt - jedes mit
eigener Reihenfolge, teils widersprueglich. Alle offenen Punkte daraus sind
in docs/Roadmap.md zusammengefuehrt.

Aufbau der neuen Roadmap
- Statusvokabular: erledigt / beschlossen-offen / Entscheidung noetig /
  zurueckgestellt / Idee. Damit steht das Geparkte sichtbar drin, statt in
  einem Konzeptpapier zu verschwinden.
- Herkunft-Spalte traegt das alte Kuerzel (S4, K2, B9, T4, F-A1, C1, DC1),
  damit die archivierten Papiere auffindbar bleiben, ohne sie zu lesen.
- Abschnitt 1 Reihenfolge, 2 offene Entscheidungen, 3 die Vorhaben nach
  Bereich, 4 Ideenspeicher, 5 Chronik des Erledigten, 6 Modell-Einstufung,
  7 Herkunftskarte.

Was dabei sichtbar wurde
- Neun Entscheidungen blockieren Arbeit, ohne dass sie Aufwand kosten -
  allen voran Matrix oder Rocket.Chat. Sie stehen jetzt gesammelt in
  Abschnitt 2 statt verstreut in den Diskussionsteilen der Konzepte.
- B8 (keine Tiefenbegrenzung bei AgentComm) ist unveraendert offen. Das ist
  keine Theorie: A haelt sein Gate, waehrend es auf B wartet - ruft B nun A,
  warten beide bis zum Timeout. Der Testfall A13 dafuer fehlt bis heute.
- Die vier Umsetzungsplaene vom 2026-08-05 sind alle unumgesetzt und waren
  in keiner Roadmap verzeichnet.

Archiv
Sechs Dokumente ziehen nach docs/archiv/: Bestandsaufnahme, Konzepte
Backup/Finanz/Analyse, Linux-Portierung-Analyse, Lizenz-HardwareId-v2
(gegenstandslos - LicenseLabrador ist ersetzt), Deploymentcenter-Review und
der 2.4-Integrationsplan. Sie bleiben als Begruendung lesbar, werden aber
nicht mehr fortgeschrieben; das README ordnet jedes einzeln ein und warnt,
dass ihre Quelltext-Verweise ins Leere gehen koennen.

Bauplan bleibt Bauplan
Taskboard, Audit, Staging, Memory, Agentenkommunikation, RocketChat und
Deploymentcenter-Integration bleiben in docs/ - sie sind die Detailvorgabe
fuer die Umsetzung, nicht Vorhabenlisten. Jedes bekommt oben eine Zeile,
die seine Rolle und den Umsetzungsstand nennt und auf die Roadmap zeigt.
Dasselbe fuer die vier Umsetzungsplaene: die Reihenfolge gilt in der
Roadmap, nicht im Plan.

Nebenbei repariert: Taskboard-Konzept und Teststrategie verwiesen auf
AgentScheduler und ToolJobScheduler, die seit der Scanner-Konsolidierung
geloescht sind. Alle Dokument-Verweise in docs/ sind geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 18:31:06 +02:00

17 KiB
Raw Permalink Blame History

Deploymentcenter-Anbindung — Durchsicht

Nachtrag 2026-08-08 — die Anbindung ist umgestellt, Server und SDK stehen auf 2.1. Abschnitt 4 und 5 sind abgearbeitet; wie es jetzt aussieht, steht in Deploymentcenter-Integration.

Mit SDK 2.1 erledigt (waren Befunde aus Abschnitt 3 bzw. aus der Durchsicht der 2.0-Anbindung):

  • HttpClient ohne Zeitgrenze → intern 15 s. Unsere Umgehung (eigener Client mit 8 s) ist zurückgebaut.
  • HTTP 429/5xx entzogen die Lizenz, ohne den Zwischenspeicher zu befragen → jeder Nicht-Erfolg führt jetzt in denselben Offline-Zweig, IsTransient macht den Unterschied sichtbar. Unsere Behelfsprüfung auf unknown_error ist entfernt.
  • cache_ttl_hours wurde ignoriert, die Gnadenfrist war faktisch unbegrenzt.
  • app_version fest "1.0.0" → kommt jetzt aus ReleaseInfo.Version.
  • BuildInfo.targets war nicht einbindbar (CS0433/CS0103) → erzeugt die Klasse im eigenen Namensraum, ist eingebunden.
  • UpdateClient: API-Zweig las snake_case in ein camelCase-Modell → eigenes Modell ApiReleaseInfo, is_critical von der obersten Ebene.
  • DeactivateAsync schickte den Shared Key zusätzlich als X-Watchdog-Key.
  • Kein CancellationToken in der Lizenz-API.

Weiterhin offen — betrifft das Deploymentcenter, nicht ClawdDotNet:

  • 2.1 (keine Signaturprüfung) — unverändert. LicenseInfo.PublicKeyBase64 ist gestrichen, damit nichts Totes stehenbleibt und niemand Schutz vermutet, wo keiner ist. Kommt die Signatur, kommt das Feld mit ihr zurück.
  • 2.2 (v1-Ersatzhash) und 2.3 (Klartext-Rückfall) — unverändert, beides im SDK zu beheben.
  • 3 (HW-ID bei jedem Aufruf neu) — clientseitig umgangen: einmal berechnet und behalten.
  • parent_source ist nur eine source, kein Paar — damit schließen sich „ein Monitor je Instanz" und instanzweise Alarmunterdrückung gegenseitig aus.

Stand: 2026-08-06. Geprüft: J:\Softwareprojekte\Deploymentcenter (Client, Server, Schema, beide Integrationsleitfäden) gegen den HW-ID-v2-Vorschlag und die Linux-Analyse.

Ergebnis vorweg: Die Lizenz blockiert den Linux-Umzug nicht mehr. Alles, was an Hardware-ID v2 plattformrelevant war, ist da und richtig. Was hier steht, sind Punkte aus derselben Durchsicht — drei davon würden beim Ausrollen wehtun.


1. Was erledigt ist

Punkt aus dem Vorschlag Umsetzung
Format 2:<plattform>:<hex> HardwareId.cs:125
Kein MachineName im Hash ComputeV2Hash, :138 — der wichtigste Punkt, sauber umgesetzt
Quellenkette Windows/Linux :42107, inklusive dmi-uuid
IsPlausibleMachineId (Länge, uninitialized, nur Nullen) :161
MAC-Filter über locally-administered-Bit :224
/sys/class/net/<name>/device-Prüfung :228
Erweiterte Stoppwortliste :23 — inkl. br-, virbr, cni, cali
machine.key mit 0600 :290, SetUnixPermissions mit #if NET8_0_OR_GREATER
Vorgabe per Umgebungsvariable LicenseConfig.HardwareIdOverride, beide Namen
XDG-Auflösungskette, nie leerer Pfad LicenseConfig.cs:28, mit ValidateNonEmpty
Mehrfachziel netstandard2.0;net8.0 csproj, BouncyCastle nur im netstandard-Zweig
LLS2-Hülle, AES-GCM, HKDF StateStore.cs:169 — Schlüssel aus HW-ID abgeleitet, bindet den Cache also echt an die Maschine
ILicensePrompt + Konsolenfassung vorhanden — genau das, was der kopflose Host braucht
Servermigration v1→v2 LicenseService.php:108, mit Prüfprotokolleintrag hwid_migrated
Schema hwid_version/hwid_source/platform sql/migrations/v2_hardware_id.sql, rückwärtskompatibel
Verwaltungsansicht zeigt Quelle/Plattform public/index.php:1227

OperatingSystemHelpers nutzt jetzt RuntimeInformation. Der Client hat auf dem Linux-Pfad keine Windows-Laufzeitabhängigkeit — ProtectedData wird nur unter IsWindows() aufgerufen.

Für die Portierung heißt das: Punkt 4 aus der Entscheidungsliste der Linux-Analyse („Erlaubt LicenseLabrador den Wechsel der Hardware-ID?") ist beantwortet. Der Aufwandsblock „Lizenz" schrumpft von 35 PT auf 23 PT — das ist jetzt reine Anschlussarbeit in ClawdDotNet, keine Konzeptarbeit mehr.


2. Drei Befunde, die vor dem Ausrollen geklärt sein sollten

2.1 Es wird nichts signiert — die Lizenzprüfung ist eine Vertrauensfrage an DNS

LicenseClient.cs:62:

string status = root.TryGetProperty("status", out var sProp) ? sProp.GetString() ?? "unknown" : "unknown";
if (status.Equals("valid", StringComparison.OrdinalIgnoreCase))
{
    // → gültig
}

Das ist die vollständige Prüfung. Es gibt im neuen Client kein Signature.cs, keinen hinterlegten öffentlichen Schlüssel, keine Hüllenprüfung — die Dateien Signature.cs, LicenseResult.cs und LicenseState.cs aus dem alten LicenseLabrador-SDK sind beim Umzug nicht mitgekommen.

Folge: Wer die HTTP-Anfrage umlenken kann, hat eine gültige Lizenz. Ein Eintrag in /etc/hosts, ein Proxy, ein eigener DNS — die Antwort {"status":"valid"} genügt. Auf einem Linux-Server, den der Betreiber ohnehin vollständig kontrolliert, ist das kein Kunststück.

Serverseitig sieht es passend dazu aus. public/index.php:45:

'signature' => 'ED25519_SIG_' . base64_encode(hash('sha256', $lic['license_key'] . 'DC_OFFLINE_SECRET', true))

Das ist ein SHA-256 über den Lizenzschlüssel plus eine fest verdrahtete Zeichenkette — keine Signatur, sondern ein Wert, der jeder erzeugen kann, der den Quelltext kennt. Und public/index.php:1993 im JavaScript:

"ED25519_SIG_" + btoa(key + hwId).substring(0, 32)

Base64 der Eingabe, abgeschnitten. Auch kein Hash.

Das ist erkennbar ein Platzhalter — nur trägt er einen Namen, der nach fertigem Verfahren klingt, und darauf verlässt sich LicenseGate mit seiner harten Startsperre. Es ist keine Portierungsfrage (unter Windows gilt heute dasselbe) und auch kein Grund, den Linux-Umzug aufzuhalten — aber es sollte eine bewusste Entscheidung sein und nicht in dem Glauben untergehen, die Signaturprüfung sei bereits da.

Wenn das Verfahren zurückkommen soll: Ed25519 über die kanonisch serialisierte Antwort, öffentlicher Schlüssel im Client einkompiliert, nonce aus der Anfrage in der signierten Nutzlast gegenprüfen (gegen Wiedereinspielung). Der alte Signer.php und Signature.cs sind im LicenseLabrador-Repo noch vorhanden und lassen sich als Vorlage nehmen.

2.2 Der v1-Ersatzhash trifft die alten Aktivierungen nicht

Der Migrationsweg ist auf beiden Seiten korrekt gebaut — er wird nur nie auslösen, weil der Client eine andere v1-ID berechnet als die, die in der Datenbank steht.

Alt (LicenseLabrador/HardwareId.cs:20):

rawBuilder.Append(machineId);                    // MachineGuid, sonst MAC
rawBuilder.Append(Environment.MachineName);      // direkt angehängt, kein Trenner
 sha256(machineGuid + machineName)

Neu (HardwareId.cs:149):

string raw = $"{Environment.MachineName}:{firstMac}";
 sha256(machineName + ":" + mac)

Andere Reihenfolge, anderer Trenner, und MAC statt MachineGuid. Auf jedem Windows-Rechner, auf dem MachineGuid lesbar war — also praktisch allen — stimmen die Hashes nicht überein. Der Server sucht die Altaktivierung, findet nichts und legt eine neue an: genau der Platzverbrauch, den die Migration verhindern sollte. Bei max_activations = 2 ist danach ein Platz für den Linux-Server weniger da.

Auch GetFirstPhysicalMacLegacy (:254) weicht ab: keine Stoppwortfilterung, keine Sortierung, erste Schnittstelle in Aufzählungsreihenfolge. Die alte Fassung nahm die alphabetisch erste gefilterte MAC.

Zu tun: GetLegacyHardwareId() muss den v1-Algorithmus zeichengenau nachbilden — inklusive der alten Stichwortliste (virtual, veth, docker, hyper-v, wsl, mullvad, wireguard, tap, tun, vpn, bluetooth, vmware, box, pseudo, loopback, npcap, pcap), OrderBy(…, Ordinal) und FirstOrDefault(). Der Code steht im LicenseLabrador-Repo noch da und kann weitgehend übernommen werden.

Am besten mit einem Test absichern, der einen bekannten Eingabewert gegen den erwarteten v1-Hash prüft — sonst fällt eine Abweichung erst auf, wenn die Aktivierungsplätze schon verbraucht sind.

2.3 Der Klartext-Rückfall ist noch da, nur woanders

StateStore.cs:79 — „Legacy Migration Check":

string legacyJson = Encoding.UTF8.GetString(payloadBytes);
var legacyData = JsonSerializer.Deserialize<LocalCacheData>(legacyJson);
if (legacyData != null)
{
    legacyData.SchemaVersion = 2;
    Save(productSlug, hardwareId, legacyData);
    return legacyData;
}

Der LLS2-Zweig darüber ist genau richtig — Entschlüsselung fehlgeschlagen heißt Cache-Fehltreffer, kein Klartext. Der Zweig darunter hebt das wieder auf: Jede Datei ohne LLS2-Kennung wird als JSON gelesen und, wenn sie sich deserialisieren lässt, übernommen und anschließend verschlüsselt neu geschrieben.

Durchgespielt: Eine von Hand angelegte state.dat mit

{"SchemaVersion":2,"Status":"valid","ExpiresAt":99999999999,"MaxSeenTime":0}

wird angenommen. In ValidateAsync greift bei fehlender Verbindung der Cache-Zweig (:110): Status == "valid" ✓, now < MaxSeenTime ✗, now > ExpiresAt ✗ → IsValid = true. Die Bindung an die Hardware, die DeriveKey(hardwareId, …) sonst herstellt, ist auf diesem Weg umgangen; die Datei ist zwischen Maschinen übertragbar.

Der Zweig hilft dabei nicht einmal beim eigentlichen Zweck. Die alte LocalCacheData hieß last_envelope, max_seen_time, endpoints, last_license_key; die neue SchemaVersion, Status, ExpiresAt, … Kein gemeinsames Feld, und JsonSerializer ist ohne PropertyNameCaseInsensitive/JsonPropertyName bei den Namen streng. Eine echte v1-Datei ergibt also ein Objekt mit lauter Vorgabewerten (Status = "invalid") und ist als Cache wertlos.

Empfehlung: den Zweig ersatzlos streichen. Er kostet Sicherheit und leistet nichts. Alte Cachedateien sollen verworfen werden — eine einmalige Online-Prüfung ist der ganze Preis.

Nebenbei: Checksum = hwInfo.HardwareId (LicenseClient.cs:79) ist keine Prüfsumme, sondern eine Kopie der HW-ID. Das Feld ist damit ohne Funktion — entweder mit einem HMAC über die übrigen Felder füllen oder entfernen, damit niemand später Schutz vermutet, wo keiner ist.


3. Kleinere Punkte

Fundstelle Sache
LicenseClient.cs:26 Eigener HttpClient je Instanz, nie freigegeben, ohne Zeitgrenze (Vorgabe 100 s). Der alte LicenseConfig.HttpTimeout war 6 s. In LicenseGate.RunStartupCheck bedeutet das bis zu 100 s Standbild beim Start, wenn der Server nicht antwortet.
:107 catch (Exception ex) um den gesamten Block: Auch ein Fehler beim Auswerten einer erfolgreichen Antwort landet im Offline-Zweig. Ein defekter Server gilt dann als „offline".
:47 app_version = "1.0.0" fest verdrahtet. ClawdDotNet hat BuildInfo.Build — sollte Parameter sein, sonst steht in der Verwaltungsansicht bei jeder Instanz dasselbe.
:32, :172 HardwareId.GetHardwareId() bei jedem Aufruf neu: liest unter Linux Dateien und zählt Netzwerkschnittstellen auf. Einmal berechnen und halten.
HardwareId.cs:205 MAC-Auswahl überspringt Schnittstellen, die nicht Up oder Unknown sind. Ein Kabel, das beim Start nicht steckt, ändert damit die Hardware-ID. Für die Ausweichlösung sollte der Betriebszustand keine Rolle spielen — sonst ist sie genau in dem Moment instabil, in dem sie gebraucht wird.
HardwareId.cs:231 /sys/class/net/<name>/device ist ein Symlink. Directory.Exists/File.Exists folgen ihm — funktioniert, ist aber Zufall und sollte kommentiert sein.

4. Watchdog: die Anbindung passt noch nicht

Kein Linux-Thema, fällt aber in dieselbe Umbauarbeit.

WatchdogClient.cs sendet an:

ClawdDotNet Deploymentcenter
POST /api/heartbeat POST /api/watchdog/v1/ping (nimmt auch /heartbeat)
POST /api/event POST /api/watchdog/v1/event
POST /api/register existiert nicht

Die Pfade sind also alle um /watchdog/v1 zu ergänzen. Der Kopfzeilenname passt: public/api/watchdog/v1/index.php:30 akzeptiert X-Watchdog-Key, Authorization und X-Agent-Token.

Der Selbstregistrierungsweg aus Program.cs:334 — mit dem Master-Token einen eigenen Agent-Token holen und in der Instanzkonfiguration zwischenspeichern — hat serverseitig kein Gegenstück mehr. Zu klären: Tokens künftig von Hand in der Verwaltung anlegen und in die Instanzkonfiguration eintragen, oder /register im Deploymentcenter nachziehen. Für den ersten Weg spricht, dass er den Master-Token gar nicht erst auf die Instanzen verteilt.

Die Feldnamen des Ping-Rumpfs (source, instance, type, status, message, interval, group, os) sind gegen InstanceHealthProvider abzugleichen.


5. Was in ClawdDotNet zu tun ist

Datei Was
ClawdDotNet.csproj Projektverweis von ..\LicenseLabrador\client-dotnet\… auf ..\Deploymentcenter\client-dotnet\Deploymentcenter.Client\… umhängen. Langfristig als Submodul unter external/ — der Kommentar dazu steht schon im csproj.
Services/LicenseGate.cs Neu gegen LicenseValidationResult schreiben. LicenseState gibt es nicht mehr, Status ist jetzt eine Zeichenkette — DescribeProblem (:125) muss auf revoked/expired/activation_limit/not_found umgestellt werden. MessageBox durch ILicensePrompt ersetzen; die Konsolenfassung bringt der Client mit.
Services/LicenseInfo.cs PublicKeyBase64 hat ohne Signaturprüfung keine Funktion mehr — entweder mit 2.1 zurückholen oder streichen, damit nichts Totes stehenbleibt.
Program.cs:112 Lizenzprüfung so verlagern, dass sie ohne Fenster auskommt (kopfloser Host).
Host (neu) --license-status, --license-set-key, --license-deactivate — der Client bringt alles Nötige mit.
WatchdogClient.cs Pfade auf /api/watchdog/v1/…; Registrierungsweg klären (Abschnitt 4).
docs/Integrationsplan-WatchDog-LicenseLabrador.md Abgelöst durch Deploymentcenter-Integration.

6. Antwort auf die Ausgangsfrage

Ja — Avalonia und Linux sind damit machbar. Die einzige Frage, die ich als möglicher Blocker außerhalb unserer Hand markiert hatte, ist geklärt: Der Client läuft auf beiden Plattformen, zielt auf net8.0 (von net10.0 problemlos verwendbar), löst seinen Ablageort auch ohne HOME auf, und die HW-ID ist container- und umbenennungsfest.

Der Lizenzblock in der Aufwandsschätzung fällt von 35 PT auf 23 PT. Die Gesamtspanne bleibt bei 5080 PT, weil die Lizenz nie der große Posten war — das sind PropertyGrid und Chat-Ansicht.

Zwei Dinge sollten aber vor dem Ausrollen erledigt sein, unabhängig von Linux:

  • 2.2 (v1-Ersatzhash) — klein, aber wenn es beim Ausrollen falsch ist, sind Aktivierungsplätze verbraucht und man bekommt sie nur einzeln über die Verwaltung zurück. Das ist der Punkt mit dem schlechtesten Verhältnis von Aufwand zu Schaden.
  • 2.3 (Klartext-Rückfall) — eine Zeile weniger Code, dafür wieder das Verhalten, das der LLS2-Umbau eigentlich herstellen sollte.

2.1 (keine Signaturprüfung) ist eine eigene Entscheidung mit eigenem Umfang und hält den Umzug nicht auf. Sie sollte nur getroffen und nicht übersehen werden — der Name ED25519_SIG_ im Serverquelltext legt sonst nahe, dass die Sache erledigt sei.