# G10-6.3 Real Apply Action — Lieferbericht **Phase:** G10-6.3 (GUI Operator Finalisierung — Real Apply Action) **Stand:** 2026-05-09 15:03 UTC **Commit:** `592bf37` **Vorbedingung erfüllt:** G10-6.2 `aaa03c2` ✓ approved **Boundaries:** kein Live-Apply, kein Worker, kein Bot-Restart, keine Orders, kein Mainnet, kein Push --- ## 1. Geänderte Dateien | Datei | Δ | Inhalt | |---|---|---| | `gui/app/Filament/Resources/ConfigProfileResource/Support/ProfileActionFactory.php` | +128 / -24 | NEU `applyToTestnetAction()`; gelöscht `applyDisabledAction()`; Dry-run Modal-Text bereinigt | | `gui/app/Filament/Resources/ConfigProfileResource/Pages/ViewConfigProfile.php` | +5 / -3 | `applyDisabledAction()` entfernt, `applyToTestnetAction()` eingehängt; Reihenfolge Dry-run → Apply → Clear | | `gui/tests/Feature/G10_6_3_ApplyActionTest.php` | NEU 381 | 21 Tests, 112 Assertions | | `gui/tests/Feature/G10_2_ApplyDryRunCommandTest.php` | +14 / -50 | 2 obsolete Tests entfernt; 1 umbenannt + body-slice gefixt | | `gui/tests/Feature/G10_3_5_ApplyCommandTest.php` | +8 / -19 | 2 obsolete Tests entfernt | | `gui/tests/Feature/G7_3_FilamentEditorTest.php` | +9 / -55 | 1 Test angepasst auf `applyToTestnet`; 3 obsolete entfernt | | `gui/tests/Feature/G9_3_RiskSettingsUITest.php` | +14 / -23 | 1 obsolete entfernt; 1 ersetzt durch positiv-formulierten Negativ-Test | **Total:** +542 / -163 Zeilen. --- ## 2. Action-Name und Position - **Action ID:** `applyToTestnet` - **Label:** „Apply to Bot" - **Icon:** `heroicon-o-rocket-launch` - **Color:** `danger` (Spec-Empfehlung) - **Position:** ViewConfigProfile Header, **zwischen** Dry-run und Clear - **Finale Reihenfolge:** Edit · Risk-Edit · Risk-Clear · Clone · Archive · Dry-run · **Apply to Bot** · Clear Runtime Config --- ## 3. Policy-Verwendung Bestehender Gate `ConfigProfilePolicy::applyToTestnet(User, ConfigProfile)` aus G10-2 — **keine neue Methode**. Gates: - Admin - profile.environment = paper - profile.status = ready - profile.source ≠ bot_capture Operator/Viewer sehen die Action nicht. Archived / non-paper / bot_capture / pending Profile blocken (alle 4 als eigenständige Tests verifiziert). --- ## 4. Confirmation-Text ``` Heading: APPLY to bot: {profile.name} Description (HTML): This creates a real apply_profile_testnet command with dry_run=false. It writes runtime_config.json when the worker processes it. The bot picks up the override on the next cycle without restart. NO orders are created by this action. This is reversible via Clear Runtime Config. Worker must be invoked separately with --once. Recommended: run Dry-run first. Submit: Create apply command ``` --- ## 5. Profilname-Validation ```php TextInput::make('confirm_profile_name') ->label('Type the profile name to confirm') ->placeholder($record->name) ->required() ->rules([ fn ($record) => function (string $attribute, mixed $value, Closure $fail) use ($record) { if ((string) $value !== (string) $record->name) { $fail('Profile name does not match. Apply cancelled.'); } }, ]), ``` Plus belt-and-braces re-check im Action-Callback. Sowohl mismatch als auch leere Eingabe blocken Submit (`assertHasActionErrors(['confirm_profile_name'])`); Service wird nicht erreicht; `commands` bleibt leer. --- ## 6. Service-Aufruf ```php app(ApplyProfileService::class)->createApplyCommand( profile: $record, userId: auth()->id(), ); ``` Single source of truth. Kein `Command::create`, kein `AuditEvent::create`, kein raw SQL aus der Action. Negative Tests verifizieren: `createDryRunCommand` und `createClearCommand` werden NICHT aufgerufen (Mockery `shouldNotReceive`). --- ## 7. Notifications | Pfad | Notification | |---|---| | Erfolg | `success`, Title „Apply command created", Body `"Command {command_id} created. Run worker --once to process it."` | | Service-Exception | `danger`, Title „Apply command failed", Body `$e->getMessage()`, Exception re-thrown | | Confirmation-Mismatch | `danger`, Title „Apply cancelled", Body „Profile name confirmation did not match." (defensive im action-callback; Schema-Rule sollte vorher greifen) | --- ## 8. Welche alten Tests ersetzt / entfernt wurden ### 8 obsolete Tests entfernt - `G10_2_ApplyDryRunCommandTest::test_real_apply_button_remains_disabled` - `G10_2_ApplyDryRunCommandTest::test_real_apply_callback_still_throws_logic_exception` - `G10_3_5_ApplyCommandTest::test_filament_apply_button_remains_disabled_after_g10_3_5` - `G10_3_5_ApplyCommandTest::test_filament_apply_callback_throw_still_present` - `G7_3_FilamentEditorTest::test_apply_button_is_disabled` - `G7_3_FilamentEditorTest::test_apply_button_carries_g10_tooltip` - `G7_3_FilamentEditorTest::test_apply_action_makes_no_command_or_config_mutation` - `G9_3_RiskSettingsUITest::test_apply_button_still_disabled_with_g10_tooltip` ### 3 angepasst / umbenannt - G10-2 `test_dry_run_modal_description_contains_no_real_apply_option` → `test_dry_run_action_body_has_no_real_apply_path` — body-slice gefixt damit Docblock der nächsten Methode nicht reinleckt; obsolete `applyDisabledAction`-Source-Greps entfernt. - G7-3 `test_admin_sees_edit_clone_archive_apply_on_view_page` — Assertion auf `apply` → `applyToTestnet`. - G9-3 `test_apply_button_does_not_create_a_command_row` → `test_edit_risk_settings_does_not_create_a_command_row` — gleiche Invariante (editRiskSettings erzeugt keinen Command), ohne disabled-Stub-Assertion. --- ## 9. Neue Tests + Ergebnisse **`G10_6_3_ApplyActionTest.php` — 21 Tests, 112 Assertions, alles grün, 1.6s Laufzeit** | Block | Tests | Inhalt | |---|---|---| | Visibility | 7 | admin sieht; operator/viewer nicht; archived / non-paper / bot_capture / pending blocken | | Hard Confirmation | 3 | mismatch + empty blocken submit, no command; exact match proceeds | | Modal-Text + Label/Color | 2 | source-grep auf alle Pflicht-Phrasen | | Action Flow | 3 | service called once mit profile+userId; audit_event `profile.apply.requested`; service-exception → danger | | Disabled-Stub-Removal | 2 | factory-method weg; ViewConfigProfile mountet sie nicht mehr | | Boundary AST | 4 | keine FS / shell / docker / Storage / raw DB / createDryRun / createClear / Worker / Process; createApplyCommand IS present; ViewConfigProfile order Dry-run < Apply < Clear, kein applyDisabledAction-Verweis | --- ## 10. Volle GUI-Suite Ergebnis **486 / 486 Tests, 1428 Assertions, alles grün, 22.9s.** Vorher (G10-6.2 baseline): 473. Jetzt: 486 = +21 G10-6.3 + 3 rename/replacement − 8 obsolete + 3 weitere Korrekturen = +13 net. --- ## 11. Boundary-Bestätigungen - ✅ runtime_config.json **NICHT existent** (G10-5b state unverändert) - ✅ Bot **PID 4246 stable** - ✅ `.env` mtime **1777991334 unverändert** - ✅ Kein Worker, kein Worker-Daemon, kein Bot-Restart - ✅ **Kein live Apply-Command erzeugt** — Tests ran in SQLite-memory; live Postgres `commands` mit `command_type='apply_profile_testnet' AND dry_run=false AND created_at > NOW() - 15 min` = **0** - ✅ Boundary-AST Tests verifizieren im Action-Body: - keine `file_get_contents` / `fopen` / `unlink` / `Storage::*` / `shell_exec` / `exec` / `proc_open` - keine `docker run` / `docker compose` / `Symfony\Component\Process` / `CommandWorker` / `python3 -m` / `subprocess` - keine raw DB-Inserts (`DB::insert` / `DB::table` / `Command::create` / `AuditEvent::create`) - ✅ Action ruft **nur** `createApplyCommand` — `createDryRunCommand` + `createClearCommand` AST-verboten - ✅ Kein Inline-Clear im Widget (deferred) - ✅ Kein Apply-Diff im Modal (Q10 deferred) - ✅ Kein Mainnet, kein Push --- ## 12. Empfehlung für G10-6.4 Runbook + Memory Closure **Bereit für GO.** G10-6.4 sollte enthalten: ### Operator-Runbook (NEU) Datei: `docs/ops/g10_runtime_config_operator_runbook.md` Inhalt — gemeinsamer Apply/Clear/Dry-run Operator-Flow: - Vorbedingungen (Admin-Login, `BINANCE_TESTNET=True`, Profile = paper/ready/non-bot_capture) - Apply Flow (Filament-Klickpfad → Dry-run zuerst empfohlen → Apply mit Profilname-Tippen → `worker --once` → Status-Widget verifizieren) - Clear Flow (Filament-Klickpfad → optional Reason → `worker --once` → Bot fällt auf `.env` zurück) - Worker `--once` exakter Befehl mit `docker exec` Beispiel - Backup-Verhalten (writer-side, automatisch) - Restore aus Backup (manueller `docker cp` Pfad) - Stop-Regeln (Bot-PID-Wechsel, Mainnet-Pfad, audit-failed/rolled-back) - No-effect Verhalten (idempotent apply/clear) - Mainnet-Blocker durable rule (5-Layer + B-FEE-FIX-Prerequisites) - Troubleshooting Apply-failed / Clear-failed / Snapshot-stale - Cross-Refs zu `g10_4_2_apply_runbook.md` + `g10_5_clear_runtime_config_runbook.md` (bestehen) ### SAFETY_FILTERS.md Edit 1 kurzer G10-6 Verweis: „GUI Apply/Clear ist ab G10-6 sichtbar; alle GUI-Aktionen gehen ausschließlich über den CommandBus (kein direkter Datei-/Worker-Zugriff aus PHP); Mainnet-Blocker bleibt durable per G10-4.0." ### Memory - `g10_6_status.md` (NEU) als zusammenfassender Eintrag für die 4 Phasen 6.1–6.4 mit Commit-Hashes (`d69ccf2`, `aaa03c2`, `592bf37`, plus G10-6.4 Closure-Commit) - 3 Index-Einträge in `MEMORY.md` ersetzen / konsolidieren ### Tests Reine Markdown — optional ein link-check Test, sonst keine Test-Surface nötig. GUI-Suite bleibt bei 486/486. ### Boundaries Docs-only. Keine Code-Änderung. Keine `.env`. Kein Bot-Restart. Kein Push. ### Risiko **Trivial.** Nach G10-6.4 ist die G10-Phase formal **CLOSED** — operativ nutzbar via GUI für Testnet-Apply / Clear / Dry-run. Mainnet bleibt durable blockiert. Worker-Daemon-Aktivierung ist separater Ops-Phase (G10-7 oder später). --- ## 13. Lieferbericht-Zusammenfassung | Kennzahl | Wert | |---|---| | Commit | `592bf37` | | Geänderte Dateien | 7 | | Neue Zeilen | +542 | | Entfernte Zeilen | -163 | | Neue Tests | 21 (G10-6.3) | | Entfernte obsolete Tests | 8 | | Umbenannt / ersetzt | 3 | | GUI-Suite | 486 / 486 grün | | Assertions | 1428 | | Live-Apply-Commands | 0 | | Bot-PID | 4246 stable | | runtime_config.json | absent (G10-5b state) | | Mainnet | blocked | **STOP.** Warte auf User-Review für G10-6.3, danach GO für G10-6.4 (Runbook + Memory Closure). --- *Generated 2026-05-09 15:03 UTC — G10-6.3 Closure, kein Code, kein Live-Aktion. Vorbedingung G10-6.2 `aaa03c2` ✓ approved.*