Files
PolyTraderSharp/src/PolyTrader.App.Avalonia/Licensing/LicenseGate.cs
T
RichardandClaude Opus 5 6218a04fe4 Eine Roadmap statt fuenfzehn Plandokumente; Altbestand ins Archiv
Der Status des Projekts stand verstreut in elf Umsetzungsplaenen, drei
Konzepten, der Linux-Analyse und dem Projektstand - teils widersprechend, teils
wochenlang veraltet. Ab jetzt gibt es genau eine Statusquelle.

docs/ROADMAP.md (neu):
- Alle Vorhaben in vier Stufen A bis D, plus technische Schuld und Verlauf.
  Die Stufen sind eine Reihenfolge, keine Termine: jede schafft die
  Voraussetzung fuer die naechste.
- Statuszeichen: erledigt / offen / blockiert (mit Ursache) / bewusst
  zurueckgestellt / Idee, nicht beschlossen. Damit ist das, was wir NICHT bauen
  wollen, sichtbar vorgehalten statt unauffindbar in einem Plan zu schlummern.
- Inhaltlich getragen, nicht nur verlinkt: je Vorhaben Ziel, Phasen,
  Akzeptanzkriterien, offene Entscheidungen und Leitplanken aus den Quelldokumenten.
- Sichtbar gemacht, was vorher zwischen den Dokumenten verborgen lag:
  CopyTrading Phase 1 ist der Engpass der gesamten Roadmap (MarketMaking und
  BundleArbitrage haben harte Voraussetzungen darauf), und die Sniper-Metriken
  aus Phase 3.2 sind ein Spezialfall des StrategieDrift-Fingerprints - zusammen
  bauen statt doppelt.

Archiv (docs/archiv/):
- 15 Dokumente verschoben (11 Umsetzungsplaene, 3 Konzepte, ANALYSE-Linux-Portierung).
  Sie bleiben die Bauanleitungen mit Code-Bezuegen, Risikotabellen und
  Begruendungen - eingefroren ist nur ihr Status.
- archiv/README.md ordnet jedes Dokument seinem Roadmap-Punkt zu.

Verweise nachgezogen - der eigentliche Aufwand:
- 25 Markdown-Links repariert. 15 davon verschiebungsbedingt (eine Ebene
  tiefer), der Rest war schon vorher falsch: die Ideensammlung verlinkte
  Quellcode relativ zum Repo-Wurzelverzeichnis statt zu docs/.
- 12 Dateien ausserhalb von docs/ verwiesen in Kommentaren auf die Plaene
  (csproj, props, setup.json, sechs Quelldateien) - alle auf archiv/ umgebogen.
- Verweise auf Dateien, die der Fruehjahrsputz geloescht hat (Ui/,
  Program.cs, WindowMenuBar), zu Klartext entschaerft statt tote Links zu lassen.
- Gegenprobe: 85 Links geprueft, 0 kaputt. Build gruen, 476 Tests gruen.

PROJEKTSTAND.md entdoppelt: Abschnitt "Offen" verweist jetzt auf die Roadmap.
Arbeitsteilung ist damit klar - Projektstand sagt was IST, Roadmap was KOMMT.

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

106 lines
5.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using Deploymentcenter.Client;
using PolyTrader.Core;
using PolyTrader.Core.Configuration;
using PolyTrader.Core.Security;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
namespace PolyTrader.App.Avalonia.Licensing
{
/// <summary>
/// Lizenz-Torwächter für den Programmstart (D-2). Ersetzt den früheren, gegen
/// <c>LicenseLabrador.Client</c> gebauten <c>Licensing/LicenseGate.cs</c> im WinForms-Projekt
/// (siehe docs/archiv/umsetzungsplaene/UMSETZUNGSPLAN-Deploymentcenter-Integration.md) dort läuft
/// ohnehin keine Lizenzprüfung, weil Avalonia die tatsächlich benutzte Anwendung ist.
///
/// **Grundsatz unverändert:** kein <see cref="Environment.Exit(int)"/> bei ungültiger Lizenz. Ein
/// Trading-Bot mit offenen Positionen darf nicht mitten im Lauf abgeschossen werden. Bei
/// fehlender/ungültiger Lizenz startet die Core-Shell (Terminal, Einstellungen) ohne die
/// Trading-/Analyse-Module; der Schlüssel lässt sich jederzeit über den Button
/// „Lizenzschlüssel setzen …" im Einstellungsfenster nachtragen (wirkt ab dem nächsten Start
/// eine Laufzeit-Revalidierung gibt es bewusst nicht, siehe Entscheidung vom 31.07.2026).
///
/// **Bewusst kein eigenes <see cref="ILicensePrompt"/>:** Der interaktive Weg läuft über die
/// bereits vorhandene, erprobte Settings-UI (derselbe maskierte Prompt-Button wie beim
/// Watchdog-Token); ein zusätzlicher Dialog nur für die Lizenz wäre eine zweite, redundante
/// Eingabe-Oberfläche. Für den Kopflos-Betrieb (<c>--headless</c>, CLI-Schalter) genügt die vom
/// SDK mitgelieferte <see cref="ConsoleLicensePrompt"/> eine eigene Fassung böte keinen Mehrwert.
/// </summary>
public static class LicenseGate
{
/// <summary>
/// Prüft still (ohne Dialog), ob die gespeicherte Lizenz aktuell nutzbar ist. Entscheidet
/// darüber, ob die Trading-/Analyse-Module überhaupt registriert werden muss deshalb vor
/// dem Host-Aufbau laufen. Wirft nie: jeder Fehler in der Prüfung führt zum eingeschränkten
/// Modus, nicht zum Absturz.
/// </summary>
public static bool CheckUsable(DeploymentcenterOptions options, ServerSettings settings, TerminalLogger logger)
{
try
{
if (string.IsNullOrWhiteSpace(options.BaseUrl))
{
logger.Warning("⚠️ Keine Deploymentcenter-URL konfiguriert. Start im eingeschränkten Modus (nur Core-Shell).");
return false;
}
// Befund D-3 aus dem Umsetzungsplan: das SDK erzwingt selbst kein TLS. Ohne diese
// Prüfung ginge der Lizenzschlüssel im Klartext übers Netz, wenn die URL versehentlich
// auf http:// steht.
if (!Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out var parsed) ||
!(parsed.Scheme == Uri.UriSchemeHttps || (parsed.Scheme == Uri.UriSchemeHttp && parsed.IsLoopback)))
{
logger.Warning($"⚠️ Deploymentcenter-URL muss HTTPS verwenden (http:// nur für localhost): '{options.BaseUrl}'. " +
"Start im eingeschränkten Modus.");
return false;
}
string key;
try
{
// Erlaubt einen mit dem Master-Key verschlüsselten Schlüssel (enc:v1:…);
// Klartext wird unverändert durchgereicht.
key = SecretProtection.Unprotect(settings.LicenseKey);
}
catch (Exception ex)
{
logger.Warning($"⚠️ Gespeicherter Lizenzschlüssel nicht lesbar ({ex.Message}) behandle als 'keine Lizenz'.");
return false;
}
if (string.IsNullOrWhiteSpace(key))
{
logger.Warning("⚠️ Kein Lizenzschlüssel gesetzt. Start im eingeschränkten Modus (nur Core-Shell). " +
"Schlüssel über „Lizenzschlüssel setzen …\" im Einstellungsfenster eintragen.");
return false;
}
LicenseClient.DefaultAppVersion = BuildInfo.Version;
var client = new LicenseClient();
LicenseValidationResult result = client
.ValidateAsync(options.ProjectSlug, key, options.BaseUrl, BuildInfo.Version)
.GetAwaiter().GetResult();
if (result.IsValid)
{
logger.Info(result.IsCached
? $"🔑 Lizenz im Offline-Kulanzmodus gültig ({result.Status})."
: "🔑 Lizenz gültig (online geprüft).");
return true;
}
logger.Warning($"⚠️ Keine gültige Lizenz ({result.Status}: {result.Message}). " +
"Start im eingeschränkten Modus (nur Core-Shell, keine Module). " +
"Schlüssel über „Lizenzschlüssel setzen …\" im Einstellungsfenster prüfen.");
return false;
}
catch (Exception ex)
{
logger.Error($"❌ Lizenzprüfung unerwartet fehlgeschlagen: {ex.Message}. Start im eingeschränkten Modus.");
return false;
}
}
}
}