Files
PolyTraderSharp/docs/ANALYSE-Linux-Portierung.md
T
RichardandClaude Opus 5 dd8da3fd3f Arbeitsstand gesichert: Deploymentcenter-Integration (D-0 bis D-5) und Einfenster-Shell
Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt,
welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen.

Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json
ueberschneiden und darum gemeinsam abgelegt werden:

Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig):
- Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert
- DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli
- WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt
  (version, os, checks, metrics, status stopped)
- Security: MasterKeyResolver, SecretRedactor, FilePermissions
- Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin)
- deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben
  ueber .gitignore aussen vor
- setup.json fuer die Erstinstallation
- UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md
  verweist auf den neuen Plan

Einfenster-Shell (UI-Redesign):
- ShellWindow + ShellNavModel als Seitenleisten-Shell
- WindowMenuBar und WindowMenuModel entfallen
- Modul- und Kernfenster auf die Shell-Einbettung angepasst

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

565 lines
45 KiB
Markdown
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.
# Analyse & Umsetzungsrahmen: Linux-Fähigkeit von PolyTrader
**Stand:** 13.08.2026 · **Revision 5** (Avalonia-Portierung P5P8 durch; offen sind Deploymentcenter, CI, systemd/Feldtest, WinForms-Ausbau)
**Basis:** 8 Projekte auf `net10.0` (`PolyTrader.App` als `net10.0-windows` noch als Fallback), 450 Tests
> **Als Nächstes:** die Integration des überarbeiteten **Deploymentcenters** (P3c). Sie ersetzt
> **Lizenzverwaltung, Watchdog und Error-Reporting** grundlegend. Alles, was in diesem Dokument zu
> LicenseLabrador oder zum Watchdog-Heartbeat steht, ist damit Übergangszustand — dort nichts mehr
> ausbauen.
>
> **P3c hat seit dem 14.08.2026 einen eigenen Plan:**
> **[UMSETZUNGSPLAN-Deploymentcenter-Integration.md](./umsetzungsplaene/UMSETZUNGSPLAN-Deploymentcenter-Integration.md)**
> — er ist ab jetzt maßgeblich und korrigiert zwei Aussagen dieses Dokuments:
> §5.2 unterschätzt die Watchdog-Umstellung („nur Header und URL"; tatsächlich kommen `version`,
> `os`, `checks`, `metrics` und `status: "stopped"` dazu), und §5.2 wie Risiko **R6** stützen die
> headless-Lizenzprüfung auf `ILicensePrompt` aus dem SDK — diese Schnittstelle wird dort jedoch
> **nirgends benutzt** und trägt nichts. Der Umfang liegt bei **47 PT** statt 24 PT, weil
> Error-Reporting, Auslieferung und Erstinstallation hier noch nicht enthalten waren.
---
## Entscheidungen (06.08.2026)
| # | Thema | Entscheidung | Auswirkung |
|---|---|---|---|
| 1 | Kultur-Bug | **Sofort gefixt** ✅ | Erledigt, siehe [Abschnitt 4.1](#41-erledigt--der-aktive-bug-ist-behoben) |
| 2 | Designer-Regel | **Neue Formulierung übernommen:** Layout deklarativ in `.axaml`, nicht zur Laufzeit im Code | ersetzt die WinForms-Designer-Regel |
| 3 | Windows-UI | **Variante B**: Modul-UI **entfernt** ✅, Core-UI folgt; Fallback ist der Tag `winforms-final` | umgesetzt 06.08.2026 |
| 4 | `PropertyGrid` | wird durch **normale Steuerelemente** ersetzt | +5 PT ggü. Fremdbibliothek, dafür bessere UX |
| 5 | Lizenz/Watchdog | laufen jetzt über das **Deploymentcenter**, nicht mehr über Einzeldienste | **Linux-Blocker entfällt**, siehe [5.2](#52-lizenz--watchdog--jetzt-über-das-deploymentcenter) |
| 6 | Zeitzone | **einmalig bei der Installation** festgelegt, nicht im laufenden Betrieb gewechselt | vereinfacht die Umsetzung, siehe [3.3](#33-anforderung-konfigurierbare-zeitzone-entscheidung-6) |
| 7 | Threema | **entfernt** ✅; Nachfolger: **RocketChat + Telegram** | umgesetzt am 06.08.2026, siehe [5.3](#53-threema--entfernt-) |
| 8 | Charts | **LiveCharts2** bestätigt (nach Klarstellung, dass die Integration nicht komplex ist) | +1 PT ggü. ScottPlot.Avalonia |
**Netto-Effekt auf den Gesamtaufwand:** damals auf **4161 PT** geschätzt. **Überholt — Stand 13.08.2026 sind noch 713 PT offen**, weil die Avalonia-Portierung (P5P8) inzwischen durch ist; siehe [Abschnitt 7](#7-fahrplan).
---
## 0. Kurzfassung
| Frage | Antwort |
|---|---|
| Ist es machbar? | Ja, ohne architektonische Sackgassen. |
| Was ist der Löwenanteil? | Die UI. ~8.300 LOC (34 % des Produktivcodes) hängen an WinForms. |
| Was ist überraschend gut? | Die **gesamte Trading-Kernlogik ist bereits portabel**: kein einziger `DllImport`, keine Registry, keine WMI, keine DPAPI, keine `SpecialFolder`, saubere `Path.Combine`-Nutzung. Nethereum, Pomelo/EF, `HttpClient`, `ClientWebSocket`, AES-GCM laufen unverändert. |
| Was war der teuerste Einzelpunkt? | Der **`PropertyGrid`-Ersatz** — erledigt, ersetzt durch den kategorisierten `SettingsEditor`. |
| Gesamtaufwand | **~713 Personentage offen** (Stand 13.08.2026). Die UI-Portierung ist inhaltlich durch (P5P8); offen sind nur noch **P3c Deploymentcenter**, **CI**, **systemd/Feldtest**, der **WinForms-Ausbau** und die Abnahme A5. |
| Wichtigste Erkenntnis | Galt: „Für ~15 PT bekommst du 80 % des Nutzens." **Eingetreten und überholt** — die UI wurde nicht als Fallback eingefroren, sondern vollständig nach Avalonia portiert. Der Rückfallstand liegt im Tag `winforms-final`. Siehe [Abschnitt 7](#7-fahrplan). |
---
## 1. Bestandsaufnahme
### 1.1 Projektstruktur und Zielframeworks
| Projekt | TargetFramework | `UseWindowsForms` | Warum WinForms? |
|---|---|---|---|
| `PolyTrader.App` | `net8.0-windows7.0` (`WinExe`) | ja | Shell, Launcher, 4 Core-Views |
| `PolyTrader.Core` | `net8.0-windows` | ja | **nur** UI-Contract (`ModuleUi.cs`, `WindowMenu.cs`) |
| `Modules.CopyTrading` | `net8.0-windows` | ja | 5 Views + `TradeRowColoring` (`System.Drawing.Color`) |
| `Modules.ResolutionFarming` | `net8.0-windows` | ja | 1 Fenster |
| `Modules.Supervisor` | `net8.0-windows` | ja | 1 Fenster |
| `Modules.Accounting` | `net8.0-windows` | ja | 1 Fenster |
| `PolyTrader.Tests` | `net8.0-windows` | ja | nur weil die Referenzen `-windows` sind |
| ~~`IcgSoftware.Threema.CoreMsgApi`~~ (lib) | — | — | **entfernt am 06.08.2026** (Entscheidung 7) |
Alle verbleibenden Projekte sind `-windows` **aber die Bindung ist dünn**. Core und Module brauchen WinForms ausschließlich für den UI-Contract; die Services darunter sind plattformneutral.
### 1.2 Wo sitzt der Windows-Code?
```
WinForms/System.Drawing berührt 41 Dateien davon:
├─ 33 reine UI-Dateien (Ui/-Ordner) → müssen ohnehin neu
└─ 5 „Ausreißer" außerhalb der Ui-Ordner → das ist die eigentliche Kopplung
├─ src/PolyTrader.Core/Modularity/ModuleUi.cs (Func<Form>, Image)
├─ src/PolyTrader.Core/Modularity/WindowMenu.cs (MenuStrip, Font)
├─ src/…CopyTrading/Logic/TradeRowColoring.cs (Color)
├─ Models/DashboardRow.cs (using System.Drawing)
└─ Program.cs (ApplicationConfiguration.Initialize)
```
**Nur 5 Dateien** stehen zwischen „Core/Module sind plattformneutral" und dem Ist-Zustand.
### 1.3 Code-Verteilung
| Kategorie | LOC | Anteil |
|---|---:|---:|
| UI (Designer + Code-Behind), App + Module | ~8.300 | 34 % |
| davon Designer-generiert (entfällt ersatzlos) | ~4.500 | 19 % |
| Services / Logik / Persistenz / Modelle | ~15.950 | 66 % |
| Tests (448 Testfälle, 48 Dateien) | ~5.300 | — |
| EF-Migrationen (unverändert übernehmbar) | 6.891 | — |
**Nur 1 von 48 Testdateien** ist UI-abhängig (`WindowMenuTests.cs`).
---
## 2. Die UI-Portierung
### 2.1 Control-Inventar und die neue Layout-Regel
16 Fenster/UserControls, ~8.300 LOC. Aus den Designer-Dateien:
| WinForms-Control | Anzahl | Avalonia-Ersatz | Bewertung |
|---|---:|---|---|
| `DataGridViewTextBoxColumn` | 69 | `DataGridTextColumn` | mechanisch, viel Fleißarbeit |
| `Label` | 49 | `TextBlock` | trivial |
| `ToolStripButton` | 37 | `Button` in `StackPanel` | Layout neu |
| `TabPage` / `TabControl` | 22 / 7 | `TabItem` / `TabControl` | trivial |
| `DataGridView` | 20 | `DataGrid` | siehe unten |
| `ToolStrip` | 16 | `StackPanel`/`DockPanel` | Layout neu |
| `Panel` / `Button` | 16 / 16 | direkt | trivial |
| `TextBox` / `ComboBox` | 9 / 9 | direkt | trivial |
| **`PropertyGrid`** | **6** | **handgebaute Formulare** (Entscheidung 4) | **teuerster Punkt, siehe 2.2** |
| `DateTimePicker` | 5 | `CalendarDatePicker` + `TimePicker` (getrennt!) | kleine Nacharbeit |
| `GroupBox` | 4 | `HeaderedContentControl`/`Border` | trivial |
| **`RichTextBox`** | **3** | kein Äquivalent, siehe 2.3 | mittel |
| `PictureBox` | 3 | `CartesianChart` (LiveCharts2) | siehe 2.4 |
| `SplitContainer` | 2 | `GridSplitter` | Layout neu |
| `MenuStrip` / `StatusStrip` | 1 / 1 | `Menu` / `DockPanel` | `ToolStripItemAlignment.Right` gibt es nicht → `DockPanel` |
**Entwarnung bei den DataGrids:** Die Nutzung ist erfreulich schlicht 25× `DataSource`-Binding, 35× `DefaultCellStyle`, 8× `AutoGenerateColumns`, nur 4 `SelectionChanged`, 3 `CellContentClick`, 1 `CellDoubleClick`. **Kein Virtual Mode, kein Custom Painting, keine `CellFormatting`.** Direkt auf `ItemsSource` + `Binding StringFormat` abbildbar.
Die Zeilenfärbung (`ClosedTradesView.cs`, `OpenTradesView.cs` über `DataBindingComplete``row.DefaultCellStyle.BackColor`) wird zu einem `IValueConverter` auf `DataGrid.LoadingRow` sauberer als heute.
> ### 📐 Die neue Layout-Regel (Entscheidung 2)
>
> Die bisherige Vorgabe „alle WinForms-Controls designerfähig (partial + `.Designer.cs`), nie code-only" wird ersetzt durch:
>
> **„Jede View besteht aus `View.axaml` (vollständiges Layout, deklarativ) und `View.axaml.cs` (nur Verdrahtung und Datenlogik). Steuerelemente und Layout werden NICHT zur Laufzeit im Code erzeugt."**
>
> Nutzen bleibt identisch: Layout ist vollständig inspizierbar, diffbar und an einer Stelle. Ein WYSIWYG-Designer existiert in Avalonia nicht stattdessen der **XAML-Previewer** (VS-/Rider-Extension) mit Live-Vorschau beim Tippen.
>
> **Praktische Konsequenz für die Portierung:** `WindowMenu.Populate()` baut die Menüleiste heute komplett zur Laufzeit auf (`menu.Items.Clear()` + dynamisch erzeugte `ToolStripMenuItem`). Das verstößt gegen die neue Regel und wird zu einem deklarativen `Menu` mit `ItemsSource`-Bindung an eine `ObservableCollection<WindowMenuEntry>` Layout im XAML, nur die Daten kommen aus dem Code. Gleiches gilt für die dynamisch angehängte Menüleiste in `ShellUiHost.AttachWindowMenu()`.
### 2.2 `PropertyGrid` → normale Steuerelemente (Entscheidung 4)
Betroffen sind **4 Fenster mit 6 Grid-Instanzen**:
| Datei | Instanz | Gebundenes Objekt |
|---|---|---|
| `Ui/Views/SettingsView.Designer.cs:21,33` | `propertyGrid`, `pgAccount` | `ServerSettings`, Account-Daten |
| `…CopyTrading/Ui/AccountSettingsView.Designer.cs:25` | `pgSettings` | `CopyTradingAccountSettings` |
| `…CopyTrading/Ui/MasterTradersView.Designer.cs:41` | `pgDetail` | `TrackedTrader` |
| `…ResolutionFarming/Ui/ResolutionFarmingMainForm.Designer.cs:38` | `pgSettings` | Farming-Settings |
**Vorarbeit, die sich jetzt auszahlt:** Die Modelle tragen bereits `[Description]`-Attribute als Inline-Erklärungen (`CopyTradingAccountSettings.cs:11`, `TrackedTrader.cs:10`) und `[Browsable(false)]` zum Ausblenden von Geheimnissen (`ServerSettings.cs:107,146` Watchdog-Token und Lizenzschlüssel). **Diese Attribute sind die Spezifikation der neuen Formulare** Beschriftung, Tooltip und Sichtbarkeit jedes Feldes stehen schon im Code. Das reduziert die Denkarbeit auf Layout und Validierung.
**Was die handgebauten Formulare besser können als das `PropertyGrid`:**
- **Eingabevalidierung vor dem Speichern** statt erst beim Commit der Zelle (bei Risiko-Limits und Preisgrenzen relevant)
- **Einheiten direkt am Feld** (USDC, %, bps, Minuten) statt im Beschreibungstext
- **Fachliche Gruppierung** statt alphabetischer Reflection-Reihenfolge
- **Abhängige Felder ausgrauen** (z. B. Ladder-Parameter nur bei aktivierter Sell-Ladder)
**Aufwand:** ~810 PT statt ~2 PT mit Fremdbibliothek. Der Aufschlag ist bewusst und kauft dauerhafte Wartbarkeit ohne Fremdabhängigkeit.
### 2.3 `RichTextBox` → Terminal-Fenster
`TerminalView` (378 + 195 LOC) und `SupervisorMainForm` (Chat) nutzen `RichTextBox` mit **Farbausgabe pro Log-Zeile**. Avalonia hat kein `RichTextBox`.
**Empfehlung:** `ItemsControl` über eine `ObservableCollection<LogLine>` mit Farb-Converter virtualisiert, deklarativ (passt zur neuen Layout-Regel), und schneller als heute. `TerminalView.cs:186` implementiert bereits ein Auto-Clear bei RAM-Limit; der Performance-Druck ist bekannt, ein virtualisiertes `ItemsControl` löst ihn an der Wurzel.
### 2.4 Charts: ScottPlot → LiveCharts2 (Entscheidung 8)
Nur **3 Charts**, alle in `DashboardView.RenderCharts()` (Equity-Kurve, PnL je Modul, PnL je Tag). Die Auswertungslogik liegt bereits pur in `TradeAnalytics` und bleibt **komplett unverändert**.
Der heutige Weg (`plot.GetImage(w,h).GetImageBytes()``System.Drawing.Bitmap``PictureBox`, `DashboardView.cs:207-227`) entfällt und wird zu drei deklarativen `CartesianChart`-Elementen im XAML was der neuen Layout-Regel ohnehin besser entspricht als das heutige Rendern im Code.
> **Zur Einordnung deiner Sorge:** Der Unterschied ist kleiner als befürchtet. `ScottPlot.Avalonia` wäre ~1 PT günstiger gewesen, nicht mehr bei **drei** Charts fällt der Integrationsvorteil kaum ins Gewicht. LiveCharts2 bringt dafür Animationen, Tooltips und Zoom out of the box, die du bei ScottPlot nachbauen müsstest. **Die Entscheidung für LiveCharts2 ist bei dieser Chart-Anzahl gut begründet** du zahlst dafür keinen nennenswerten Aufpreis.
### 2.5 `System.Drawing.Common` muss raus
Seit **.NET 7 ist `System.Drawing.Common` Windows-only** und wirft auf Linux `PlatformNotSupportedException`. Betroffen:
- `ModuleUi.cs:32` `System.Drawing.Image? Icon``Avalonia.Media.IImage`
- `TradeRowColoring.cs:11-13` `Color.FromArgb(...)` → eigenes Farbtupel oder `Avalonia.Media.Color`
- `WindowMenu.cs` `Font`, `FontStyle.Bold`
- `Models/DashboardRow.cs` `using System.Drawing` (vermutlich verwaist, prüfen)
- `DashboardView.cs:222-225` entfällt mit LiveCharts2
`TradeRowColoringTests.cs` testet die Farben direkt → zieht mit (klein).
### 2.6 PDF-Export: `PDFsharp-MigraDoc-GDI` → Core-Build
`Modules.Accounting` referenziert `PDFsharp-MigraDoc-GDI` 6.2.4 der **GDI+-Build ist Windows-only**. Ersatz: `PDFsharp-MigraDoc` (Core-Build), API-kompatibel.
**Aber:** Der Core-Build findet auf Linux keine Systemschriften. `PdfExporter.cs:34` setzt `style.Font.Name = "Segoe UI"` existiert auf Linux nicht. Nötig ist ein eigener **`IFontResolver`** plus eine als `EmbeddedResource` mitgelieferte Schrift (DejaVu Sans oder Open Sans, beide frei lizenziert). Einziger nennenswerter Umbau am Accounting-Modul.
`AccountingReportTests.cs:131` rendert PDFs headless im Test → zieht mit.
---
## 3. Zeitzonen ⏰
### 3.1 Der harte Punkt
`Ui/Views/TerminalView.cs:150`:
```csharp
TimeZoneInfo berlinTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
```
Eine **Windows-Zeitzonen-ID**. Seit .NET 6 mappt `FindSystemTimeZoneById` Windows-IDs auf Linux über ICU funktioniert also *meistens*. Es bricht, sobald `InvariantGlobalization=true` gesetzt ist oder die Distro kein vollständiges ICU mitbringt (Alpine ohne `icu-libs`). Dann fliegt beim Öffnen des Terminal-Fensters eine `TimeZoneNotFoundException`.
Mit Entscheidung 6 verschwindet diese Stelle ohnehin die Zeitzone kommt künftig aus der Konfiguration, nicht mehr hartcodiert.
### 3.2 Die Now/UtcNow-Mischung
**122× `DateTime.UtcNow` gegen 23× `DateTime.Now`.** Konsistent, solange der Rechner auf `Europe/Berlin` steht. **Linux-Server laufen praktisch immer auf UTC.** Betroffene Stellen:
| Stelle | Auswirkung bei Server-TZ = UTC |
|---|---|
| `TerminalLogger.cs:23` `Timestamp = DateTime.Now` | Log-Zeitstempel 12 h versetzt |
| `TerminalLogger.cs:122`, `LauncherWidgetsPanel.cs:112` Logdatei `{DateTime.Now:yyyy-MM-dd}.jsonl` | **Tagesgrenze der Logdateien verschiebt sich** |
| `DossierService.cs:81`, `SupervisorTools.cs:144` suchen Logdateien per Datum | müssen dieselbe Konvention nutzen, sonst leere Dossiers |
| `DailyReportService.cs:61` | Tagesbericht schneidet an anderer Stelle |
| `AccountingMainForm.cs:57` Periodenvorbelegung | **steuerlich relevant** Monats-/Quartalsgrenzen |
| `PdfExporter.cs:55` „Erstellt: …" | Belegdatum im Steuer-PDF |
| `MasterTraderAnalyticsJob.cs` | mischt `Now` (Anzeige) und `UtcNow` (Cutoffs) funktioniert, ist aber verwirrend |
### 3.3 Anforderung: konfigurierbare Zeitzone (Entscheidung 6)
> **Ziel (präzisiert):** Die Zeitzone wird **einmalig bei der Installation** festgelegt — wie in einem Setup — und im laufenden Betrieb normalerweise nicht mehr gewechselt.
**Vorgeschlagene Umsetzung:**
1. **Neue Einstellung** `ServerSettings.ApplicationTimeZoneId` (Default `"Europe/Berlin"`). IANA-IDs verwenden .NET akzeptiert sie seit Version 6 auf **beiden** Plattformen, Windows-IDs dagegen nur eingeschränkt auf Linux.
2. **Zentraler Zugriffspunkt** statt verstreuter `DateTime.Now`-Aufrufe:
```
IAppClock
├─ DateTime UtcNow → für alle Domänen-/Persistenzwerte
├─ DateTime Local → für Anzeige und Logdatei-Namen
└─ TimeZoneInfo TimeZone → aufgelöst aus ApplicationTimeZoneId, mit Fallback auf UTC + Warnung
```
Damit ist die Zeitzone an **einer** Stelle wirksam statt an 23.
3. **Festlegung bei der Erstinstallation**, danach schreibgeschützt-mit-Bestätigung:
- Beim **ersten Start ohne gesetzte Zeitzone** wird sie abgefragt (UI-Dialog bzw. CLI-Schalter `--set-timezone` für den headless Betrieb) und in `server_settings.xml` festgeschrieben.
- Eine **spätere Änderung** ist möglich, aber bewusst unbequem: Bestätigungsdialog mit Klartext-Folgen („Logdatei-Tagesgrenzen verschieben sich um X Stunden. Bereits geschriebene Logs und Buchungen bleiben unverändert und sind danach in der neuen Zeitzone zu lesen.") und **Wirksamkeit erst nach Neustart**.
- **Protokolleintrag** mit alter und neuer Zeitzone, damit die Verschiebung später in den Logs nachvollziehbar ist.
Weil der Regelfall die einmalige Festlegung ist, entfällt jede Komplexität für einen Wechsel *zur Laufzeit* — kein Neu-Verdrahten laufender Jobs, kein Umschalten offener Fenster. Das ist der eigentliche Gewinn dieser Entscheidung.
4. **Zusätzlich** `TZ`-Umgebungsvariable in der systemd-Unit setzen, damit auch die Ausgaben *außerhalb* der Anwendung (Logrotate, Cron, Systemlogs) zusammenpassen.
**Offene fachliche Frage:** Soll die Zeitzone im Accounting **mitwandern** oder dort fix auf der steuerlich maßgeblichen Zeitzone stehen bleiben? Ein Wechsel des Betriebsstandorts ändert nicht zwingend den steuerlichen Bezugsrahmen. Empfehlung: **zwei getrennte Einstellungen** `ApplicationTimeZoneId` (Anzeige, Logs, Jobs) und `AccountingTimeZoneId` (Buchungsperioden, Steuerbelege), Default identisch. Kostet fast nichts, verhindert aber, dass ein Standortwechsel rückwirkend die Buchungsperioden verschiebt.
**Aufwand:** ~2 PT (Einstellung, `IAppClock`, 23 Aufrufstellen umziehen, Erstinstallations-Abfrage, Tests). Durch die Beschränkung auf die Installationszeit ~1 PT günstiger als in Revision 2 angesetzt.
### 3.4 MySQL
Pomelo speichert `DateTime` als `DATETIME` ohne Zeitzoneninformation. Bestehende Daten ändern sich beim Plattformwechsel nicht **aber die Interpretation neuer Werte** ändert sich. Prüfen: `SELECT @@global.time_zone, @@session.time_zone;` auf dem Ziel-Server. Bei gemischt geschriebenen Daten entstehen im Accounting stumme Lücken/Überlappungen an der Tagesgrenze. Der `IAppClock` aus 3.3 löst das, indem er die Persistenz konsequent auf UTC festlegt.
---
## 4. String- und Zahlenformatierung
### 4.1 ERLEDIGT der aktive Bug ist behoben ✅
`TraderMonitorService.cs` parste API-Preise kulturabhängig. **Empirisch verifiziert:**
```
de-DE "0.53" -> 53 "1234.5" -> 12345
Invariant "0.53" -> 0,53 "1234.5" -> 1234,5
```
Ein **Faktor-100-Fehler** im Einstandspreis der Master-Trader-Auswertung.
**Behoben am 06.08.2026:**
- `TraderMonitorService.cs:1276-1280` nutzt jetzt den bereits vorhandenen, korrekten Helper `ParseDecimal` statt duplizierter Inline-Logik. Der Helper ist `internal static` und dokumentiert.
- `PolymarketClobClient.cs:770, 784, 796, 820, 822, 827` alle `TryParse` auf `InvariantCulture` (Regex-Gruppen sind zwar ziffernrein und waren damit faktisch sicher, aber die Klasse Fehler soll nirgends mehr im Code stehen).
- `MasterTraderAnalyticsJob.cs:140` Unix-Timestamp-String invariant.
- **Neuer Regressionstest** `tests/PolyTrader.Tests/ApiNumberParsingTests.cs` 10 Fälle, die unter erzwungener **de-DE-Kultur** laufen und fehlschlagen, sobald jemand die Invariant-Angabe entfernt.
**Verifikation:** Build fehlerfrei, **448 Tests grün** (438 vorher + 10 neue).
### 4.2 Ausgabeformatierung (offen)
35× `DefaultCellStyle.Format`, 19× `ToString("…")`, dazu zahlreiche `:N2`/`:F2`/`:F3` in Log- und UI-Strings alle nach `CultureInfo.CurrentCulture`.
Auf Windows ist das `de-DE`. Auf Linux ist es **`LANG`/`LC_ALL`** unter systemd oft **gar nicht gesetzt** → .NET fällt auf **Invariant** zurück → aus `1.234,56 USDC` wird `1,234.56 USDC`. Kein Datenfehler, aber sofort sichtbar.
**Empfehlung:**
1. **Anzeige-Kultur beim Start explizit pinnen**: `CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("de-DE")` in `Program.Main`. Damit ist die Darstellung unabhängig von `LANG`. (Sinnvollerweise ebenfalls konfigurierbar, analog zur Zeitzone dieselbe Begründung: Betrieb im Ausland.)
2. **Globalisierungs-Analyzer aktivieren** (`CA1304`, `CA1305`, `CA1307`, `CA1310`). Sie hätten den Bug aus 4.1 gefunden.
### 4.3 Weitere Kulturthemen
- **ICU vs. NLS:** .NET nutzt seit Version 5 auf beiden Plattformen ICU Sortier- und Vergleichssemantik bleibt gleich. Voraussetzung: **kein `InvariantGlobalization=true`** im Publish-Profil, `libicu` auf dem Zielsystem (Debian/Ubuntu Standard, Alpine nachinstallieren).
- **String-Vergleiche:** durchgängig `StringComparison.OrdinalIgnoreCase` vorbildlich, keine Nacharbeit.
---
## 5. Weitere plattformspezifische Punkte
### 5.1 Dateisystem erfreulich sauber
| Prüfpunkt | Befund |
|---|---|
| `Path.Combine` | **17 von 17** korrekt, durchgängig über `AppContext.BaseDirectory` |
| `Environment.SpecialFolder` | **0 Treffer** |
| Harte Windows-Pfade | **genau 1**: `ServerSettings.cs:73` `MullvadCliPath` (konfigurierbar, nur der Default ist Windows) |
| `DllImport` / `LibraryImport` | **0 Treffer** |
| Registry / WMI / DPAPI / EventLog | **0 Treffer** im eigenen Code |
**Groß-/Kleinschreibung:** Linux ist case-sensitive. Kritisch: `server_settings.xml`, `appsettings.Local.json`, `master.key`, `openrouter.key`, `Logs/`, `favicon.ico`, die 24 PNGs in `Resources/`. Im Code konsistent trotzdem beim ersten Linux-Build gezielt prüfen, ebenso die `\`-Pfade in den `.csproj` (MSBuild normalisiert sie, aber es ist eine bekannte Stolperstelle).
**Dateirechte:** `master.key` und `openrouter.key` liegen als Klartext neben der Anwendung (`SettingsView.cs:301,356`, `Program.cs:393`, `OpenRouterClient.cs:46`). Auf Linux erzeugt `File.WriteAllText` mit Standard-umask **`644` world-readable**. Nach jedem Schreiben `File.SetUnixFileMode(path, UserRead | UserWrite)` (.NET 7+). Ergänzt Befund F1 des Sicherheitskonzepts.
> Das Deploymentcenter macht es beim Lizenz-Cache bereits richtig (`chmod 600 state.dat`, siehe LICENSE_INTEGRATION_GUIDE Abschnitt 3) dieselbe Konvention für die eigenen Schlüsseldateien übernehmen.
**Zeilenenden:** `Environment.NewLine` wechselt von `\r\n` auf `\n`. `CsvExporter.cs` nutzt bereits explizites `'\n'` korrekt.
### 5.2 Lizenz & Watchdog jetzt über das Deploymentcenter (Entscheidung 5)
**Das war in Revision 1 ein Blocker (R4) er ist gelöst.** Aus `J:\Softwareprojekte\Deploymentcenter\docs`:
| Thema | Alt (`LicenseLabrador.Client` 1.0.0) | Neu (Deploymentcenter, HWID v2) |
|---|---|---|
| Hardware-ID | Windows: Registry `MachineGuid`; Linux: `/etc/machine-id`**plattformabhängig verschieden** | `2:<plattform>:<sha256>` mit dokumentierter Quellen-Priorisierung je Plattform |
| Container | jede Instanz eine neue ID → Lizenz invalidiert bei jedem Neustart | **`DEPLOYMENTCENTER_HWID`-Override** gewinnt plattformweit |
| Rechnername im Hash | — | **bewusst nicht enthalten**, Umbenennen kostet keinen Aktivierungsplatz |
| Migration | — | Client sendet `hardware_id` (v2) **und** `legacy_hardware_id` (v1); Server zieht die Aktivierung lautlos um, **ohne zusätzlichen Platz** |
| Headless | modaler Dialog blockiert den Start | CLI-Schalter `--license-status`, `--license-set-key`, `--license-deactivate` **plus** `ILicensePrompt`/`ConsoleLicensePrompt` als UI-Abstraktion |
| Cache | — | `LLS2`-Envelope, AES-256-GCM, HKDF aus HWID+Slug, Linux `0600`, kein Klartext-Rückfall |
| SDK | `netstandard2.0` | `Deploymentcenter.Client`, Multi-Targeting `netstandard2.0;net8.0` |
**Damit entfallen zwei Risiken vollständig:** die Neubindung der Lizenz beim Plattformwechsel (Migration v1→v2 ist serverseitig gelöst) und der Container-Neustart-Effekt.
**Verbleibende Arbeit** ein Umbau, kein Blocker:
1. **Lizenz:** `Licensing/LicenseGate.cs` und `Ui/LicenseDialog.cs` von `LicenseLabrador.Client` auf `Deploymentcenter.Client` umstellen. Die API unterscheidet sich: `LicenseResult`/`LicenseState`/`GetHardwareId()``LicenseValidationResult` (`IsValid`, `Status`, `IsCached`, `Message`) / `HardwareId.GetHardwareId(slug)` / `ValidateAsync(slug, key, serverUrl)`. **`ILicensePrompt` implementieren** statt den Dialog direkt aufzurufen das ist genau die Naht, die den headless-Betrieb und später die Avalonia-UI trägt. Das lokale NuGet-Paket in `lib/nuget/` wird ersetzt.
2. **Watchdog:** `WatchdogHeartbeatService` sendet bereits `source`, `instance`, `interval` **das Payload-Schema passt schon**. Zu ändern sind nur Header (`X-Watchdog-Key``X-Agent-Token`) und Ziel-URL (`watchdog.mhdf.de``dc.mhdf.de/api/watchdog/v1/ping`). Ergänzen: `os`- und `group`-Feld, sowie der im Guide empfohlene Abschluss-Ping beim geordneten Herunterfahren.
**Aufwand:** ~23 PT. **Empfehlung: in Stufe L1 mitnehmen**, nicht später der headless-Betrieb (L2) hängt an `ILicensePrompt`.
> **Sicherheitshinweis am Rande:** `server_settings.xml` enthält den Watchdog-Token und den Lizenzschlüssel im Klartext (`WatchdogToken`, `LicenseKey`). Beim Umbau auf das Deploymentcenter ist der passende Moment, beide über die `SecretProtection` laufen zu lassen oder auf Umgebungsvariablen umzustellen passend zur bereits offenen Auflage, die Watchdog-Secrets zu rotieren.
### 5.3 Threema entfernt ✅ (Entscheidung 7)
**Am 06.08.2026 umgesetzt.** Nachfolgekanäle werden **RocketChat und Telegram**.
**Was entfernt wurde:**
| Entfernt | Umfang |
|---|---|
| `src/PolyTrader.Core/Services/ThreemaService.cs` | 1 Datei (`BackgroundService` + Webhook-Listener) |
| `libs/Threema-MsgApi-Net-Core` (vendorte Fremdbibliothek) | 35 Dateien, aus Solution und Git entfernt (History bleibt) |
| Projektreferenz in `PolyTrader.Core.csproj` | 1 |
| Threema-Block in `ServerSettings` (7 Properties, `[Category("Threema Notifications")]`) | 36 Zeilen |
| DI-Registrierungen in `Program.cs` (Singleton + HostedService + View-Injektion) | 3 |
| `ThreemaService`-Verdrahtung in `SettingsView` | 3 Stellen |
**Was an die Stelle getreten ist — `INotificationSink`:**
```
PolyTrader.Core/Notifications/
├─ INotificationSink.cs Schnittstelle + NotificationSeverity (Info/Warning/Critical)
└─ LogNotificationSink.cs Übergangs-Implementierung: schreibt ins Terminal-Log
```
Die drei Fachdienste kennen jetzt nur noch die Schnittstelle:
| Dienst | Meldung | Severity |
|---|---|---|
| `MasterTraderAnalyticsJob` | Auto-Pause eines Master-Traders | `Warning` |
| `SellLadderService` | SELL-Floor ohne Fill erreicht, Position wird gehalten | `Critical` |
| `DailyReportService` | täglicher Supervisor-Kurzbericht | `Info` |
**Zwei Entwurfsentscheidungen, die sich später auszahlen:**
1. **No-Throw-Vertrag.** `SendAsync` wirft laut Schnittstellen-Doku **niemals** — Kanalfehler werden intern abgefangen und protokolliert. Das ist genau die Best-Practice aus dem Watchdog-Guide („Fange Netzwerkfehler stets stumm ab, damit der Ausfall des Monitoring-Servers niemals den Hauptanwendungsfluss unterbricht"). Die Aufrufer verzichten deshalb bewusst auf eigene `try/catch`-Blöcke — der Code wurde dabei kürzer, nicht länger. **Bindend für die kommenden RocketChat- und Telegram-Implementierungen.**
2. **`NotificationSeverity`** ist von Anfang an dabei, obwohl die Log-Implementierung sie kaum nutzt. Bei zwei Kanälen (RocketChat für alles, Telegram als Fallback für Kritisches — oder umgekehrt) ist die Einstufung die natürliche Routing-Information. Sie nachträglich einzuziehen hätte alle Aufrufstellen erneut angefasst.
**Nebeneffekte — vier Linux-Themen sind mit weggefallen:**
| Entfallen | War |
|---|---|
| `libsodium` 1.0.16 (Baujahr 2018) | Risiko R7: `.so` gegen alte glibc gelinkt, Ladefehler auf Ubuntu 24.04 möglich |
| `Microsoft.Win32.Registry` (`E2EHelper.cs`, `#if CoreWinOnly`) | **die einzige Registry-Nutzung im gesamten Build** |
| `HttpListener` auf `http://*:{port}/` (Webhook) | Port-Bindung + `netsh`-Fehlermeldung mit Windows-Semantik |
| `System.Web.HttpUtility` | — |
**Verifikation:** Clean-Build fehlerfrei, **448 Tests grün**, Compiler-Warnungen von **17 auf 9** gesunken (die 8 Warnungen der Threema-Bibliothek — 4× `SYSLIB0014`, 3× `CA1416`, 1× `CS0472` — sind weg).
**Testverbesserung nebenbei:** Die drei Tests, die bisher `new ThreemaService(logger, new JobManager())` als Wegwerf-Instanz injizierten, nutzen jetzt `FakeNotificationSink` — damit ist **prüfbar, ob und mit welcher Severity gemeldet wurde**. Vorher war das nicht beobachtbar.
**Offen für später:** `RocketChatNotificationSink` und `TelegramNotificationSink` implementieren und in `Program.cs` registrieren — eine Klasse je Kanal, keine Änderung an der Fachlogik. Bei mehreren aktiven Kanälen bietet sich ein `CompositeNotificationSink` an (leitet an alle weiter, jeder Kanal fängt seine Fehler selbst ab).
### 5.4 Verbleibende Netzwerkdienste
- **`HttpListener`** nach dem Threema-Ausbau nur noch der MCP-Light-Server (`127.0.0.1:{port}/mcp`). Läuft auf Linux über die managed Implementierung, kein `netsh` nötig. Unkritisch.
- **`MullvadVpnService`**: Der CLI-Aufruf ist portabel (`UseShellExecute = false`, sauber). Auf Linux heißt die CLI `mullvad` (typisch `/usr/bin/mullvad`). **Achtung:** `HealthCheckAsync()` prüft `status.Contains("Connected")` der Wortlaut der Linux-CLI weicht ab. **Muss gegen die echte CLI verifiziert werden**, sonst reconnectet der Dienst dauerhaft ins Leere und meldet nach 3 Fehlversuchen „unrecoverable".
- **Nethereum 6.1.0, Pomelo/EF Core 8, `HttpClient`, `ClientWebSocket`, `System.Text.Json`, AES-GCM** (OpenSSL): plattformneutral, **keine Anpassung nötig**. Das ist der gesamte Trading-Pfad.
### 5.5 Windows-UI als Fallback: Variante 2 (Entscheidung 3)
Heute laufen Generic Host und `Application.Run` im **selben Prozess**; darunter **19 `BackgroundService`-Instanzen**. Auf Linux gibt es kein „WinExe" eine Avalonia-App braucht X11/Wayland, ein Trading-Server hat typischerweise kein Display.
**Entschieden: Variante 2 eingefrorener Stand.** Die WinForms-UI bleibt auf dem heutigen Stand und wird nicht weiterentwickelt; später verschwindet sie **vollständig aus dem Build**, damit keine Altlast mitgeschleppt wird.
**Was das konkret heißt:**
| | |
|---|---|
| **Einfrieren** | Vor Beginn von L3 einen Git-Tag setzen (Vorschlag: `winforms-final`) und im Repo dokumentieren. Ab da keine Feature-Arbeit mehr an `Ui/` und den `Ui/`-Ordnern der Module. |
| **Fallback im Ernstfall** | Tag auschecken, bauen, starten. Greift auf dieselbe MySQL zu — Anzeige und Einstellungen bleiben voll brauchbar. |
| **Weiterentwicklung** | Läuft unverändert weiter — nur eben im **Trading-Kern**, der ab L2 headless auf Linux läuft. Eingefroren ist die *Oberfläche*, nicht die Funktion. |
| **Endgültiger Ausbau** | Nach Abschluss von L4: `Ui/`-Ordner, `PolyTrader.App` als `WinExe`, alle `.Designer.cs`/`.resx`, `UseWindowsForms`, `favicon.ico`-Einbindung und `ApplicationConfiguration.Initialize()` entfernen. **~4.500 LOC generierter Designer-Code fallen ersatzlos weg.** |
**Wichtig für die Reihenfolge:** Damit der eingefrorene Stand überhaupt lauffähig bleibt, darf **P1 die WinForms-UI nicht kaputtmachen**. Beim Entkoppeln von Core und Modulen (Zielframework `net8.0`, `System.Drawing` raus) muss der WinForms-Zweig bis zum Tag baubar bleiben — praktisch heisst das: **erst taggen, dann P1 durchziehen**. Sonst ist der Fallback genau in dem Moment weg, in dem man ihn braucht.
> **Konkreter Vorschlag zur Absicherung:** Vor P1 einmal `dotnet publish -c Release` des heutigen Stands erzeugen und das Ergebnis zusammen mit dem Tag archivieren. Ein fertig gebautes Verzeichnis ist im Ernstfall schneller einsatzbereit als ein Checkout, der erst wieder kompiliert werden muss — und beweist nebenbei, dass der eingefrorene Stand tatsächlich läuft.
**Unabhängig davon nötig:** headless-fähiger Einstiegspunkt für den Linux-Betrieb, systemd-Unit mit `Restart=always` und `Environment=TZ=…`, Master-Key über `EnvironmentFile` mit `chmod 600`, Logrotate für `Logs/*.jsonl`.
### 5.6 Tests und CI
- Testprojekt auf `net8.0` (fällt mit den Referenzen).
- `WindowMenuTests.cs` einziger UI-Test, neu schreiben (~0,5 PT).
- `TradeRowColoringTests.cs` zieht mit dem Farbtyp-Wechsel mit (~0,2 PT).
- `AccountingReportTests.cs:131` braucht den neuen FontResolver.
- **`--smoke-ui`** (`Program.cs:500-570`): konstruiert alle Views headless und hat laut Kommentar bereits Designer-Regressionen gefangen. **Unbedingt erhalten** Avalonia bietet `Avalonia.Headless`, das Muster überträgt sich gut.
- **Es gibt keine CI.** Die Portierung ist der richtige Anlass, `dotnet build` + `dotnet test` auf einem Linux-Runner einzurichten sonst schleicht sich die Windows-Abhängigkeit unbemerkt zurück.
---
## 6. Aufwandsschätzung (Revision 2)
| # | Paket | Änderung ggü. Rev. 1 | Aufwand |
|---|---|---|---:|
| ~~L0~~ | ~~Kultur-Bug~~ | **erledigt** ✅ | ~~0,5 PT~~ |
| **P0** | Zielbild, Avalonia-Spike an einer echten View, Layout-Konventionen festzurren | — | **23 PT** |
| **P1** | Core/Module von WinForms entkoppeln, `TargetFramework` auf `net8.0`, `System.Drawing` raus | — | **23 PT** |
| **P2** | Kultur & Zeit: `IAppClock`, Zeitzone bei Installation festlegen, Kultur pinnen, Analyzer | **1 PT** (Entscheidung 6 vereinfacht) | **23 PT** |
| **P3** | Plattform-Kleinkram: Mullvad-Pfad + Status-Parsing, Unix-Dateirechte | **0,5 PT** (Threema entfällt) | **1 PT** |
| ~~**P3b**~~ | ~~Threema-Ausbau + `INotificationSink`~~ | **erledigt 06.08.2026** ✅ | ~~2 PT~~ |
| **P3c** | **Lizenz/Watchdog auf Deploymentcenter umstellen** | **neu** (Entscheidung 5) | **23 PT** |
| **P4** | PDF-Export: PDFsharp Core + `IFontResolver` + eingebettete Schrift | — | **12 PT** |
| ~~**P5**~~ | ~~Charts auf LiveCharts2~~ | — | ✅ **erledigt** (`0b8728b`) |
| ~~**P6**~~ | ~~UI-Shell: Launcher, Fenstermenü (deklarativ!), `ShellUiHost`, Shutdown-Dialog, Widgets~~ | — | ✅ **erledigt** (`cd59e5c`, `7f0b05e`). Lizenzdialog **bewusst ausgenommen** → kommt mit P3c |
| ~~**P7**~~ | ~~Core-Views: Dashboard, Settings, Jobs, Terminal~~ | — | ✅ **erledigt** (`0b8728b`, `4ba8149`, `929fa20`) PropertyGrid durch kategorisierten `SettingsEditor` ersetzt |
| ~~**P8**~~ | ~~Modul-Fenster: CopyTrading, ResolutionFarming, Supervisor, Accounting~~ | — | ✅ **erledigt** (`669676a`, `7f0b05e`) |
| **P9** | Tests ✅ (450 grün), `--smoke-ui` auf `Avalonia.Headless`, **Linux-CI fehlt vollständig** | — | **23 PT** |
| **P10** | Deployment: systemd, TZ ✅, Logrotate, Feldtest im Zielland | — | **23 PT** |
| **P11** | **WinForms endgültig ausbauen** (nach L4): `Ui/`, Designer-Dateien, `UseWindowsForms`, `WinExe` | — | **1 PT** |
| **P3c** | **Deploymentcenter**: Lizenzverwaltung, Watchdog, Error-Reporting neu | ⬅️ **als Nächstes** | **24 PT** |
| | **Summe (offen)** | | **713 PT** |
*Personentage entwickelnder Arbeit. Mit Agentenunterstützung sinkt die Kalenderzeit deutlich; der Review- und Feldtest-Anteil (P9, P10) bleibt weitgehend gleich bei einer Anwendung, die echtes Geld bewegt, ist das die richtige Reihenfolge.*
---
## 7. Fahrplan
### Die zentrale Erkenntnis — eingetreten, dann überholt
Ursprünglich galt: **P1P4 brauchen kein Avalonia**, und mit einem headless-Einstiegspunkt hätte man
für ~15 PT von rund 50 den eigentlichen Nutzen — Bot auf Linux, WinForms-UI als eingefrorener
Fallback.
**So ist es nicht gekommen, und das ist gut so.** Die Portierung lief weiter, bis die Oberfläche
vollständig unter Avalonia stand. Der Fallback wurde damit vom Dauerzustand zur reinen
Rückversicherung im Tag `winforms-final`. Der aktuelle Engpass ist nicht mehr die UI, sondern die
**Betriebsreife**: Deploymentcenter, CI, systemd und der Feldtest im Zielland.
| Stufe | Inhalt | Aufwand | Ergebnis |
|---|---|---:|---|
| ~~**L0**~~ | ~~Kultur-Bug~~ | — | ✅ **erledigt** Faktor-100-Preisfehler behoben, 10 Regressionstests |
| ~~**L0b**~~ | ~~Threema-Ausbau + `INotificationSink`~~ | — | ✅ **erledigt** 36 Dateien raus, 448 Tests grün, 8 Warnungen weniger |
| ~~**L1a**~~ | ~~Tag `winforms-final` + P1 (Core/Module entkoppeln) + P4 (PDF)~~ | — | ✅ **erledigt** Core, 4 Module und Testprojekt sind `net8.0`; alle sechs publishen für linux-x64 ohne Windows-Pakete; 442 Tests grün |
| **L1b** | P0 ✅ + P2 ✅ + P3 (Mullvad, Dateirechte) + **P3c (Deploymentcenter)** | 24 PT | P0/P2 erledigt (Spike wurde zur vollen Portierung; Zeitzone konfigurierbar). **Offen: P3c — steht als Nächstes an** und ersetzt Lizenzverwaltung, Watchdog und Error-Reporting grundlegend. |
| **L2** | ~~Headless-Einstiegspunkt~~ ✅ + systemd + Feldtest im Zielland | 24 PT | `--headless` existiert ([`App.Avalonia/Program.cs:41`](../src/PolyTrader.App.Avalonia/Program.cs)). Offen: systemd-Unit + Feldtest. **Trading läuft headless auf Linux.** ⭐ Meilenstein |
| ~~**L3**~~ | ~~P5 + P6 + P7 (Charts, Shell, Core-Views)~~ | — | ✅ **erledigt** Avalonia-Grundgerüst (`cd59e5c`), Dashboard mit LiveCharts2 (`0b8728b`), Settings (`4ba8149`), Terminal (`929fa20`), Dunkelmodus (`487ea44`) |
| **L4** | ~~P8 (Modul-Fenster)~~ ✅ + P9 (**CI**) | 24 PT | Modul-Fenster portiert (`669676a`), Restlücken A1A4 geschlossen (`7f0b05e`), 450 Tests grün. **Offen: es gibt keinerlei CI-Konfiguration** weder `.github/workflows` noch `.gitea/workflows`. |
| **L5** | P11 WinForms endgültig aus dem Build entfernen | 1 PT | Erst nach Abnahme (A5). Keine Altlast mehr; ~4.500 LOC Designer-Code fallen weg, die 23 Build-Warnungen verschwinden mit |
### Reihenfolge-Begründung
- **Der Tag `winforms-final` kommt VOR P1.** P1 entkoppelt Core und Module von WinForms — danach ist der alte Stand nicht mehr aus dem Hauptzweig baubar. Erst taggen (und einmal `dotnet publish` archivieren), dann umbauen. Sonst ist der Fallback genau dann weg, wenn man ihn braucht.
- **P3c (Deploymentcenter) gehört nach L1, nicht später** der headless-Betrieb in L2 hängt an der `ILicensePrompt`-Abstraktion. Ohne sie blockiert der Lizenzdialog den Start auf einem Server ohne Display.
- **P2 vor L2** die Zeitzone muss feststehen, bevor der erste Linux-Server Logs und Buchungen schreibt. Nachträglich umzustellen bedeutet gemischte Daten an der Tagesgrenze.
### Noch offene Punkte
1. **Accounting-Zeitzone getrennt von der Anwendungszeitzone?** ([3.3](#33-anforderung-konfigurierbare-zeitzone-entscheidung-6)) Empfehlung ja, verhindert rückwirkend verschobene Buchungsperioden bei einem Standortwechsel.
2. **Anzeige-Kultur ebenfalls bei der Installation festlegbar?** ([4.2](#42-ausgabeformatierung-offen)) naheliegend, da dieselbe Begründung wie bei der Zeitzone (Auslandsbetrieb).
3. **RocketChat und Telegram:** Wann sollen die echten `INotificationSink`-Implementierungen kommen? Bis dahin landen Auto-Pause- und Eskalationsmeldungen nur im Terminal-Log. Für den unbeaufsichtigten Linux-Betrieb (L2) wäre mindestens **ein** echter Kanal wichtig — sonst bemerkst du eine Auto-Pause erst beim nächsten Blick ins Log.
---
## 8. Risikoliste (Revision 3)
| # | Risiko | Status | Wirkung / Gegenmaßnahme |
|---|---|---|---|
| ~~R1~~ | Kulturabhängiger Parse, Faktor-100-Preisfehler | ✅ **behoben** | gefixt + 10 Regressionstests unter de-DE |
| ~~R4~~ | Lizenz-Hardware-ID ändert sich, in Containern instabil | ✅ **gelöst** | Deploymentcenter HWID v2 mit `DEPLOYMENTCENTER_HWID`-Override und v1→v2-Migration ohne Platzverlust |
| ~~R7~~ | libsodium 1.0.16 gegen moderne glibc | ✅ **entfallen** | mit dem Threema-Ausbau am 06.08.2026 |
| **R2** | Kein WYSIWYG-Designer; Layout-Regel ist neu | offen | Im Spike (P0) an einer echten View erproben, bevor 16 Views folgen |
| **R3** | `PropertyGrid`-Ersatz durch handgebaute Formulare | offen | Teuerster Posten (~10 PT). `[Description]`-Attribute liefern die Spezifikation |
| **R5** | Zeitzonen-Semantikwechsel trifft Logs, Tagesberichte, **Accounting-Perioden** | offen | P2 **vor** L2; `IAppClock`; Wirksamkeit erst nach Neustart |
| **R6** | Headless-Server ohne Display | offen | `ILicensePrompt`/`ConsoleLicensePrompt` aus dem Deploymentcenter-SDK; headless-Einstiegspunkt in L2 |
| **R8** | Anzeigeformate kippen auf Invariant (`LANG` nicht gesetzt) | offen | Kultur in `Program.Main` pinnen |
| **R9** | Keine CI Windows-Abhängigkeiten schleichen zurück | offen | Linux-Build in Gitea Actions ab L1 |
| **R10** | Mullvad-CLI meldet auf Linux anderen Statustext | offen | Gegen die echte CLI im Zielland verifizieren (5.4) |
| ~~R11~~ | Doppelpflege beider UIs | ✅ **entfällt** | Variante 2 gewählt (eingefrorener Stand) |
| ~~R12~~ | Benachrichtigungen fallen still | ✅ **entschärft** | `INotificationSink` eingezogen; Meldungen landen im Terminal-Log |
| **R13** | **Kein echter Benachrichtigungskanal** bis RocketChat/Telegram da sind | offen | Für den unbeaufsichtigten Betrieb (L2) mindestens einen Kanal implementieren — sonst bleibt eine Auto-Pause bis zum nächsten Log-Blick unbemerkt |
| **R14** | **Fallback-Stand nicht mehr baubar**, weil P1 vor dem Tag lief | offen | Tag `winforms-final` + archiviertes `dotnet publish` **vor** P1 (5.5) |
---
## 9. Was **nicht** angepasst werden muss
- **Der gesamte Trading-Pfad:** `PolymarketClobClient` (EIP-712 über Nethereum), `PolymarketApiService`, `PolymarketWssClient`, `AlchemyWssClient`, `CopyTradingEngine`, `SellLadderService`, `TraderMonitorService`
- **Persistenz:** EF Core 8 + Pomelo/MySQL, alle 15 Migrationen, `EncryptedStringConverter`
- **Sicherheit:** `SecretProtection` (AES-256-GCM läuft auf Linux über OpenSSL), Master-Key-Ableitung
- **Alle Analytics:** `TradeAnalytics`, `DossierBuilder`, Supervisor-Agent, OpenRouter-Client, MCP-Light-Server
- **Accounting-Logik:** Ingest, Klassifikation, BWA, CSV-Export (nur der PDF-Export braucht den FontResolver)
- **Watchdog-Payload-Schema** (`source`/`instance`/`interval` passen bereits zur Deploymentcenter-API), Modularitäts-Infrastruktur
- **438 von 448 Tests**
Das ist der Grund, warum die Schätzung trotz 24.000 LOC damals bei 4161 PT landete und nicht bei 150: **Zwei Drittel des Codes waren bereits plattformneutral, und die Trennung war sauber.** Rückblickend hat sich das bestätigt — die Portierung lief schneller als veranschlagt.
---
## 10. Anhang: verifizierte Fundstellen
### Behoben am 06.08.2026
| Thema | Datei:Zeile |
|---|---|
| Kulturabhängiger Preis-Parse (Faktor 100) | `…CopyTrading/Services/TraderMonitorService.cs:1276-1280` → nutzt jetzt `ParseDecimal` |
| `ParseDecimal` testbar + dokumentiert | `…CopyTrading/Services/TraderMonitorService.cs:1223-1234` |
| Kulturabhängige Parses (Regex-Gruppen) | `…Core/Services/PolymarketClobClient.cs:770, 784, 796, 820, 822, 827` |
| Kulturabhängiger Unix-Timestamp | `…CopyTrading/Services/MasterTraderAnalyticsJob.cs:140` |
| Neuer Regressionstest (10 Fälle, de-DE) | `tests/PolyTrader.Tests/ApiNumberParsingTests.cs` |
| Threema vollständig entfernt | `ThreemaService.cs`, `libs/Threema-MsgApi-Net-Core/` (35 Dateien), `ServerSettings` (7 Properties), `Program.cs`, `SettingsView.cs`, `PolyTrader.Core.csproj`, `PolyTraderSharp.sln` |
| Neutraler Benachrichtigungsausgang | `src/PolyTrader.Core/Notifications/INotificationSink.cs`, `LogNotificationSink.cs` |
| Fachdienste auf `INotificationSink` umgestellt | `MasterTraderAnalyticsJob.cs`, `SellLadderService.cs`, `DailyReportService.cs` |
| Testdouble mit Assertion-Möglichkeit | `tests/PolyTrader.Tests/Fakes/FakeNotificationSink.cs` |
### Offen
| Thema | Datei:Zeile |
|---|---|
| Windows-Zeitzonen-ID hartcodiert | `Ui/Views/TerminalView.cs:150` |
| Harter Windows-Pfad (Mullvad) | `src/PolyTrader.Core/Models/ServerSettings.cs:73` |
| Mullvad-Statusprüfung (Wortlaut) | `src/PolyTrader.Core/Services/MullvadVpnService.cs:HealthCheckAsync` |
| UI-Contract mit WinForms-Typen | `src/PolyTrader.Core/Modularity/ModuleUi.cs:32, 36` |
| Menü wird zur Laufzeit gebaut (neue Layout-Regel) | `src/PolyTrader.Core/Modularity/WindowMenu.cs:Populate`, `Ui/ShellUiHost.cs:AttachWindowMenu` |
| `System.Drawing.Color` in Modul-Logik | `…CopyTrading/Logic/TradeRowColoring.cs:11-13` |
| ScottPlot → `System.Drawing.Bitmap` | `Ui/Views/DashboardView.cs:207-227` |
| Windows-only Schrift im PDF | `…Accounting/Logic/PdfExporter.cs:34` |
| Schlüsseldateien ohne Rechteschutz | `Ui/Views/SettingsView.cs:301, 356`; `Program.cs:393` |
| Headless-Smoke-Test (erhaltenswert) | `Program.cs:500-570` |
| Lizenz-Gate mit modalem Dialog | `Licensing/LicenseGate.cs`, `Program.cs:98` |
| Secrets im Klartext | `server_settings.xml` (`WatchdogToken`, `LicenseKey`) |
### Referenzen
- `J:\Softwareprojekte\Deploymentcenter\docs\LICENSE_INTEGRATION_GUIDE.md` Hardware-ID v2, `LLS2`-Cache, CLI-Schalter, v1→v2-Migration
- `J:\Softwareprojekte\Deploymentcenter\docs\WATCHDOG_INTEGRATION_GUIDE.md` Ping-API, `X-Agent-Token`, Payload-Schema
- `J:\Softwareprojekte\Deploymentcenter\client-dotnet\Deploymentcenter.Client` SDK-Quellen (`LicenseClient`, `HardwareId`, `ILicensePrompt`, `StateStore`)