# G10-6 Scope-Bericht — GUI/Operator-Finalisierung **Phase:** G10-6 (GUI / Operator-Finalisierung) **Stand:** 2026-05-09 14:16 UTC **Status:** Scope/Planung — read-only, keine Implementierung **Vorbedingung erfüllt:** G10-CLEANUP-MINOR `3f5528d` ✓ approved **Boundaries:** kein Code, kein Worker, kein Apply, kein Clear, kein Bot-Restart, keine Orders, kein Mainnet, kein Push --- ## 1. Status-Quo (IST) ### Was schon da ist - `ApplyProfileService` mit allen drei Public-Methoden: - `createDryRunCommand` ✓ (G10-2) - `createApplyCommand` ✓ (G10-3.5) - `createClearCommand` ✓ (G10-5a) - `CommandTypeRegistry` registriert `apply_profile_testnet` + `clear_runtime_config` - `ProfileActionFactory::applyDryRunTestnetAction()` — Dry-run-Button funktional - `ProfileActionFactory::applyDisabledAction()` — Apply-Button permanent disabled mit Tooltip „available in G10" - `ConfigProfilePolicy::applyToTestnet` — Admin-only Gate - `CommandResource` listet Commands incl. `apply_profile_testnet` + `clear_runtime_config`, hat ViewCommand mit Audit-Timeline (rendert event_type + from→to + ctx) - Bot emittiert seit G10-1 alle 10 cycles `emit_active_config()` snapshot in `bot_statuses.metadata_json` (Quelle der Wahrheit für aktuellen Override-State) - **Backend-Layer komplett, nur GUI fehlt.** ### Was definitiv NICHT da ist - Apply-Button (real, dry_run=false) — bleibt permanent-disabled bis G10-6 - Clear-Button (`clear_runtime_config`) — keine Action existiert - Runtime-Config-Status-Anzeige (Widget/Page/Section) - Operator-Runbook das Apply + Clear in einem Flow zusammenführt --- ## 2. Vorgeschlagene Phasen-Aufteilung | Phase | Inhalt | Dateien (geschätzt) | Tests | Risiko | |---|---|---|---|---| | **G10-6.1** | Runtime-Config Status-Widget (read-only Dashboard-Komponente) | 1 Widget + 1 Service-Reader + 1 Test-Datei | ~12 | gering | | **G10-6.2** | Clear-Action (admin-only, requires-confirmation, ruft `createClearCommand`) | ProfileActionFactory + Policy + ViewConfigProfile + Tests | ~10 | gering | | **G10-6.3** | Real-Apply-Action (admin-only, hard confirmation, ruft `createApplyCommand`) | ProfileActionFactory + Policy-Erweiterung + ViewConfigProfile + Tests | ~12 | **mittel** (löst tatsächliche bot-side Apply aus, sobald Worker läuft) | | **G10-6.4** | Operator-Runbook + Memory + SAFETY_FILTERS-Update | docs/ops/g10_runtime_config_operator_runbook.md + g10_6_status.md + MEMORY.md | docs-only | trivial | **Empfehlung:** Phasen einzeln User-approven (analog G10-4.x Pattern). G10-6.1 zuerst (read-only, kein Risiko), dann G10-6.2 Clear, dann G10-6.3 Apply. G10-6.4 schließt Phase ab. --- ## 3. UI-Empfehlung pro Bereich ### 3.1 Filament-UI — Verortung | Funktion | Wo | Begründung | |---|---|---| | **Runtime-Config-Status-Widget** | Auf `MonitoringDashboard` Page (existiert) **+** als Section auf `ViewConfigProfile`-Page | Operator sieht den Override-Status sowohl global (Dashboard) als auch im Kontext eines Profils | | **Apply-Button (real)** | `ViewConfigProfile`-Page, Header-Action, **neben** Dry-run-Button — ersetzt den `applyDisabledAction()` | Nutzer sieht Dry-run zuerst (links), dann Apply (rechts) — Dry-run-zuerst-Empfehlung implizit per Anordnung | | **Clear-Action** | `ViewConfigProfile`-Page Header-Action **+** auf `MonitoringDashboard` Widget als Inline-Action | Profile-Page-Variante: Operator weiß welcher Override aktiv ist; Dashboard-Variante: Schnellzugriff für Notfall-Revert | ### 3.2 Confirmation-Modals | Action | Modal-Heading | Modal-Description | Submit-Label | Confirmation-Stärke | |---|---|---|---|---| | Dry-run | „Dry-run apply: {profile.name}" | bestehender Text (G10-2) | „Create dry-run command" | normal `requiresConfirmation()` | | **Apply (G10-6.3)** | „**APPLY to bot: {profile.name}**" | „This writes runtime_config.json. Bot picks up override on next cycle (no restart). NO orders. Reversible via Clear. Real-apply, NOT dry-run." | „Create apply command" | `requiresConfirmation()` + **Confirmation-Text-Eingabe** der Profile-Name (Filament `requireText`-Pattern) | | **Clear (G10-6.2)** | „Clear runtime_config (revert to .env)" | „Removes runtime_config.json. Bot falls back to Settings/.env defaults on next cycle (no restart). Backup written automatically." | „Create clear command" | `requiresConfirmation()` + zeigt aktuellen Override-Snapshot | **Disabled-states:** - Apply nur sichtbar wenn `applyToTestnet` policy passes (Admin + profile.environment=paper + status=ready + source≠bot_capture) - Clear nur sichtbar wenn Admin (siehe 3.3) - `applyDisabledAction()` wird **entfernt** sobald G10-6.3 landet — ersetzt durch real `applyAction` ### 3.3 Permissions | Action | Required Role | Begründung | |---|---|---| | Dry-run | Admin (existing) | unchanged | | **Apply** | Admin (mirror of dry-run) | sicher konsistent mit existing Pattern | | **Clear** | Admin | analog G10-5a CommandTypeRegistry-Entry (`UserRole::Admin`) | | Runtime-Status-Widget read | Viewer/Operator/Admin | read-only, keine Mutation, keine sensiblen Daten | ### 3.4 Dry-run-zuerst-Verpflichtung? **Empfehlung: NEIN, nicht hard-enforced. Stattdessen UX-Hint.** Reasoning: - Hard-Enforcement bräuchte DB-Lookup „gab es vor X Minuten einen Dry-run für diesen profile_id?" — komplexer State, Edge-Cases (replayed dry-run, expired dry-run, etc.) - UX-Hint reicht: Apply-Modal-Description sagt explizit „Recommended: run Dry-run first to validate the payload" - Apply-Service akzeptiert ohne vorigen Dry-run (already bewiesen via G10-4.2b) → **Q1 für User:** Dry-run als Pflicht oder als Empfehlung? Default-Empfehlung: nur Empfehlung. --- ## 4. Runtime-Config Statusanzeige — Datenquellen ### 4.1 Was anzeigen **Live-State (Bot-side):** `bot_statuses.metadata_json` letzte Zeile mit `active_config_snapshot`-Key (G10-1 emit_active_config). Enthält: - `has_runtime_override` (bool, gesamt) - `runtime_config_path` - pro Key: aktueller Wert + Quelle (override/settings/env) **Command-History (DB):** `commands` table letzte erfolgreiche apply / clear: - `command_id` - `created_at`, `finished_at`, `requested_by` - `result_json.sha256`, `result_json.canonical_hash`, `result_json.backup_path`, `result_json.no_effect` - `status` (succeeded/failed/rolled_back-Indikator via audit) **Audit-Anomalien:** `command_audit_log` mit `event_type LIKE '%failed' OR LIKE '%rolled_back'` aus den letzten N Tagen → Warning-Banner. ### 4.2 Widget-Layout (Empfehlung) ``` ┌─ Runtime Config ────────────────────────────────────┐ │ Status: ⚡ Override Active [view file](sha link) │ │ OR ✓ Settings/.env Defaults │ ├─────────────────────────────────────────────────────┤ │ Active values (3 of 9 effective overridden): │ │ max_open_positions 4 (override) │ │ max_risk_per_trade_pct 0.005 (override) │ │ max_total_exposure_pct 0.20 (override) │ │ cash_reserve_pct 0.05 (.env default) │ │ [+ 5 more, click to expand] │ ├─────────────────────────────────────────────────────┤ │ Last apply: acc8a14c-… by admin@… 12:52 UTC │ │ sha256 3980e003…44a9e │ │ backup runtime_config.20260509T125222Z │ │ Last clear: 2e6503df-… by admin@… 13:40 UTC │ ├─────────────────────────────────────────────────────┤ │ ⚠ 0 failed apply commands in last 7 days │ │ ⚠ 0 rolled-back applies in last 7 days │ ├─────────────────────────────────────────────────────┤ │ [Clear runtime config] (admin-only) [View audit] │ └─────────────────────────────────────────────────────┘ ``` **Auto-refresh:** alle 30s (Filament `pollingInterval`). ### 4.3 Reader-Service (neue Klasse) `gui/app/Services/RuntimeConfigStatusReader.php` (read-only): - `getActiveSnapshot(): ?array` — letzte `bot_statuses.metadata_json` Zeile mit active_config_snapshot - `getLastApplyCommand(): ?Command` — letzte succeeded apply_profile_testnet - `getLastClearCommand(): ?Command` — letzte succeeded clear_runtime_config - `countFailedAppliesInDays(int $days): int` - `countRolledBackAppliesInDays(int $days): int` Reine SELECT-Queries, keine Mutation. --- ## 5. Audit-Ansicht — was wird sichtbar gemacht `CommandResource::ViewCommand` rendert bereits `audit_timeline` mit context_json. **Kein Neubau, nur Verfeinerung:** - `apply_succeeded` / `clear_succeeded` Events: Badge grün - `apply_failed` / `clear_failed`: Badge rot - `apply_rolled_back` / `clear_rolled_back`: Badge orange + zusätzlich `manual_intervention_required` als prominentes Warn-Element - `result_json.backup_path`, `result_json.sha256`, `result_json.no_effect` — bereits via `JsonInfolistEntry::make('result_json')` sichtbar **Empfehlung:** event-type-Badging in `renderAuditTimeline()` ergänzen, sonst keine strukturelle Änderung. Optional ein dedizierter Block „Restore/Backup info" wenn `backup_path` im result_json ist. --- ## 6. Erwartete Dateien (Vorschlag) ### G10-6.1 Status-Widget - `gui/app/Filament/Widgets/RuntimeConfigStatusWidget.php` *(neu)* - `gui/app/Services/RuntimeConfigStatusReader.php` *(neu)* - `gui/resources/views/filament/widgets/runtime-config-status.blade.php` *(neu, custom-view)* - `gui/app/Filament/Pages/MonitoringDashboard.php` *(edit — Widget registrieren)* - `gui/app/Filament/Resources/ConfigProfileResource/Pages/ViewConfigProfile.php` *(edit — Section auf Profile-Page)* - `gui/tests/Feature/G10_6_1_RuntimeConfigStatusWidgetTest.php` *(neu)* ### G10-6.2 Clear-Action - `gui/app/Filament/Resources/ConfigProfileResource/Support/ProfileActionFactory.php` *(edit — `clearRuntimeConfigAction()` hinzufügen)* - `gui/app/Policies/ConfigProfilePolicy.php` *(edit — `clearRuntimeConfig` policy method)* - `gui/app/Filament/Resources/ConfigProfileResource/Pages/ViewConfigProfile.php` *(edit — Action wiren)* - `gui/tests/Feature/G10_6_2_ClearActionTest.php` *(neu)* ### G10-6.3 Real-Apply-Action - `gui/app/Filament/Resources/ConfigProfileResource/Support/ProfileActionFactory.php` *(edit — `applyToTestnetAction()` hinzufügen, `applyDisabledAction()` entfernen)* - `gui/app/Filament/Resources/ConfigProfileResource/Pages/ViewConfigProfile.php` *(edit — Action wiren)* - `gui/tests/Feature/G10_6_3_ApplyActionTest.php` *(neu)* ### G10-6.4 Docs - `docs/ops/g10_runtime_config_operator_runbook.md` *(neu — gemeinsamer Apply+Clear+Dry-run Operator-Flow)* - `trading/SAFETY_FILTERS.md` *(edit — kurzer G10-6 Verweis: „GUI Apply/Clear ist ab G10-6 sichtbar; Mainnet-Blocker bleibt durable per G10-4.0")* - Memory `g10_6_status.md` *(neu)* - Memory `MEMORY.md` *(edit)* **Keine Migration, keine Schema-Änderung.** Reine Filament-Layer. --- ## 7. Testplan ### G10-6.1 (Status-Widget) - Widget rendert ohne `bot_statuses` Daten → „no data yet" Placeholder - Widget rendert mit `has_runtime_override=true` Snapshot → „Override Active" Badge + Werte sichtbar - Widget rendert mit `has_runtime_override=false` → „Settings/.env Defaults" Badge - Last-Apply / Last-Clear-Section zeigt korrekte command_ids - Failed-counter zeigt 0 ohne failed events; n bei n failed events in Zeitraum - Reader-Service Methoden alle einzeln + idempotent - Widget admin-action visibility (Clear-Inline-Action nur admin) ### G10-6.2 (Clear) - Admin sieht Clear-Action, Operator/Viewer nicht - Action ruft `createClearCommand` mit `auth()->id()` und reason aus Confirm-Modal - Bei Service-Exception: Filament-Notification danger - Boundary-AST-Test: ProfileActionFactory enthält weder direct `runtime_config.json` write noch unlink/Path::delete - Reason optional: Modal lässt leer; service default „operator_clear" ### G10-6.3 (Apply) - Admin sieht Apply-Action neben Dry-run, Operator/Viewer nicht - Action ruft `createApplyCommand` mit profile + auth()->id() - Confirmation-Text-Eingabe blockiert Submit wenn nicht passend - Service-Exception → danger notification - `applyDisabledAction()` ist nicht mehr eingehängt (Boundary-AST oder Page-Level Test) - Mit profile.environment≠paper (oder status≠ready, oder source=bot_capture): Action nicht sichtbar - Boundary-AST-Test: keine direct runtime_config Mutation, kein bypass des CommandTypeRegistry ### G10-6.4 (Docs) - Markdown-link-Check (optional) - Memory-File frontmatter valid **Erwartete Total-Test-Anzahl G10-6: ~34 PHP-Feature-Tests + ~5 Boundary-AST-Tests = ~39.** --- ## 8. Worker-Daemon — Empfehlung **G10-6 aktiviert KEIN Worker-Daemon.** GUI generiert Commands; deren Verarbeitung erfordert separates `worker --once` (manueller Operator-Step) bis G10-7 / Deploy-Phase Daemon einschaltet. **UI-Hinweis im Modal:** „Bot worker must be invoked separately (`worker --once`). The command will sit pending until processed." → **Q2 für User:** Soll der Hinweis auf `worker --once` im Apply/Clear-Modal stehen? (Default-Empfehlung: ja, klein als Notiz unter dem Submit.) --- ## 9. Mainnet-Block — Reaffirmation G10-6 berührt **nichts** an der Mainnet-Block-Logik: - Apply-Service hardcoded `environment="testnet"` - Clear-Service hardcoded `environment="testnet"` - Bot-side 5-Layer-Guard unverändert (Worker ALLOWED_ENVIRONMENTS, payload.environment, profile.environment, exchange_connection_id, Settings.BINANCE_TESTNET) - Kein „Switch to Mainnet"-Button - Kein BINANCE_TESTNET=false Pfad **Optional Enhancement (Q3):** Banner auf MonitoringDashboard „Environment: TESTNET — Mainnet blocked by G10-4 Phase 1" sichtbar als ständige Erinnerung. --- ## 10. Open Questions — bitte vor Implementation entscheiden | ID | Frage | Default-Vorschlag | |---|---|---| | **Q1** | Dry-run zuerst verpflichtend oder Empfehlung? | Empfehlung (nicht hard-enforced) | | **Q2** | Modal-Hinweis auf `worker --once` Pflicht? | Ja, als kleine Notiz | | **Q3** | Permanent-Banner „Environment: TESTNET" auf Dashboard? | Ja, dezent | | **Q4** | Confirmation-Text-Eingabe für Apply (profile name eintippen)? | Ja (mirror „DELETE"-Pattern in destruktiven Actions) | | **Q5** | Clear reason Required oder Optional? | Optional (default „operator_clear") — konsistent mit G10-5a Backend | | **Q6** | Polling-Intervall des Status-Widgets? | 30s (Filament Standard) | | **Q7** | Inline-Clear-Button auch im Status-Widget oder nur auf Profile-Page? | beide (Schnellzugriff + Kontext) | | **Q8** | Phasen-Reihenfolge: 6.1 → 6.2 → 6.3 → 6.4 oder 6.1 → 6.3 → 6.2 → 6.4 (Apply vor Clear)? | 6.1 → 6.2 → 6.3 → 6.4 (Clear vor Apply, weil Clear sicherer ist und Test-Apply via dry-run schon möglich) | | **Q9** | Filament-Polling im Status-Widget bei 0 user-action — kostet DB-Queries; akzeptabel? | Ja, Reader nutzt indizierte Queries | | **Q10** | Soll `apply_profile_testnet` Apply-Modal in der Description die geplanten Override-Werte ausspielen (Diff zur aktuellen Settings/.env)? | Optional Phase G10-6.3 — gut zu haben, aber kein Blocker | --- ## 11. Boundaries für G10-6 (gesamt) ### Erlaubt - Filament Resources, Widgets, Pages, Actions - Service-Reader (read-only DB-SELECT) - Policies - Tests - Runbook-Docs - Memory-Updates ### Nicht erlaubt - Trading-Logik - Bot-Code-Änderung (`trading/`) - Worker-Daemon-Aktivierung - Bot-Restart - Direct `runtime_config.json` Read/Write/Delete aus PHP-Code (nur via Command-Bus) - DB-Schema-Änderung / Migration - `.env`-Mutation - `live_portfolio.json`-Mutation - ccxt-Calls - Mainnet-Pfade - BINANCE_TESTNET=false - Push --- ## 12. Empfohlene Implementations-Reihenfolge ``` G10-6.1 (Status-Widget) → User-Approve → GO ↓ G10-6.2 (Clear-Action) → User-Approve → GO ↓ G10-6.3 (Real-Apply-Action) → User-Approve → GO ← höchstes Risiko ↓ G10-6.4 (Runbook + Memory) → User-Approve → CLOSE ``` **Pro Phase:** eigener Lieferbericht, eigener Commit, eigene Test-Surface. --- ## 13. Lieferbericht — Scope **Vorgeschlagener G10-6 Scope:** 4 Phasen (Status-Widget → Clear → Apply → Docs), ~34+5 Tests, keine Trading-Logik, kein Migration, kein Worker-Daemon, kein Mainnet. **Empfohlene UI-Struktur:** - **Status-Widget** auf MonitoringDashboard + als Section auf ViewConfigProfile (read-only, polling 30s) - **Clear-Action** Header-Action auf ViewConfigProfile + Inline-Action im Status-Widget - **Apply-Action** Header-Action auf ViewConfigProfile, ersetzt `applyDisabledAction()`, Hard-Confirmation mit Profile-Name-Eingabe - **Audit-Sichtbarkeit** über bestehenden CommandResource ViewCommand (Audit-Timeline + result_json bereits vorhanden, nur Badging-Verfeinerung nötig) **Empfohlene Datei-Surface:** - 3 neue PHP-Klassen (Widget, Reader, custom-view-Blade) - 1 neuer Policy-Method - 2 edits in ProfileActionFactory (+ 1 entfernen: applyDisabledAction) - 2 edits in ViewConfigProfile-Page - 4 neue Test-Dateien - 2 neue Doc-Dateien (Runbook + Memory) + 2 edits (SAFETY_FILTERS + MEMORY.md) **Offen für User-Entscheidung:** Q1–Q10 (siehe §10). **Empfohlener Start:** **G10-6.1 Status-Widget** (read-only, kleinstes Risiko, sofort sichtbarer Mehrwert für Operator). **Boundaries (Scope-Phase):** read-only, kein Code, kein Worker, kein Apply, kein Clear, kein Bot-Restart, keine Orders, kein Mainnet, kein Push. **Eingehalten.** --- *Generated 2026-05-09 14:16 UTC — G10-6 Scope-Phase, kein Code, kein Live-Aktion. Vorbedingung G10-CLEANUP-MINOR `3f5528d` ✓ approved.*