{
"tool_response": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"task\": {\n \"id\": 527,\n \"uuid\": \"4bbf81f5-b918-4344-af90-3b011e9cce35\",\n \"title\": \"Web-Chat ins KI-Protokoll integrieren\",\n \"description\": \"## Ziel\\nIntegration des Web-Chat-Moduls in das zentrale KI-Protokoll (`ki_dev.protokoll`), sodass alle LLM-Anfragen (Claude Code + Web-Chat) einheitlich protokolliert und über `\/protokoll` einsehbar sind.\\n\\n## Kontext\\n\\n### Aktueller Zustand\\n- **Claude Code**: Protokolliert via Python-Hooks nach `ki_dev.protokoll` (29.059 Einträge)\\n- **Web-Chat**: Speichert nur in `ki_content.chat_messages` (121 Einträge)\\n- **Problem**: Zwei getrennte Log-Systeme, keine einheitliche Übersicht\\n\\n### Gefundenes Fragment (nie implementiert)\\n```sql\\n-- ki_content.chat_messages hat Spalte:\\nllm_request_id INT NULL -- Immer NULL, war für Protokoll-Verknüpfung gedacht\\n```\\n\\n### Betroffene Tabellen\\n\\n**ki_dev.protokoll** (vollständiges Schema):\\n| Spalte | Typ | Null | Beschreibung |\\n|--------|-----|------|--------------|\\n| id | bigint | NO | PK, auto_increment |\\n| timestamp | datetime(6) | YES | Default current_timestamp |\\n| request_ip | varchar(45) | NO | Client-IP |\\n| client_name | varchar(255) | NO | Quelle (\\\"web-chat\\\", \\\"claude-code\\\") |\\n| request | text | NO | User-Nachricht |\\n| request_timestamp | datetime(6) | NO | Zeitpunkt Request |\\n| response | text | YES | LLM-Antwort |\\n| response_timestamp | datetime(6) | YES | Zeitpunkt Response |\\n| duration_ms | int unsigned | YES | Dauer (≥0, NULL bei Abbruch) |\\n| tokens_input | int unsigned | YES | Input-Tokens |\\n| tokens_output | int unsigned | YES | Output-Tokens |\\n| tokens_total | int unsigned | YES | Gesamt-Tokens |\\n| model_name | varchar(255) | YES | Verwendetes Modell |\\n| status | enum | YES | 'pending','completed','error' |\\n| error_message | text | YES | Fehlermeldung bei status='error' |\\n\\n---\\n\\n## SUPERVISION KORREKTUREN v2 (5 Abnahme-Blocker behoben)\\n\\n### Blocker 1: ALLE Logging-Methoden crash-sicher [KRITISCH]\\nNicht nur `logFailure`, sondern ALLE Service-Methoden müssen try-catch haben:\\n```php\\nfinal class KiProtokollService\\n{\\n public function logRequest(...): ?int \/\/ Nullable bei Fehler!\\n {\\n try {\\n return $this->repository->insert(...);\\n } catch (\\\\Throwable $e) {\\n error_log(\\\"KiProtokoll logRequest failed: \\\" . $e->getMessage());\\n return null; \/\/ Chat läuft weiter ohne Protokoll\\n }\\n }\\n \\n public function logSuccess(int $id, ...): void\\n {\\n try {\\n $this->repository->complete($id, ...);\\n } catch (\\\\Throwable $e) {\\n error_log(\\\"KiProtokoll logSuccess failed for ID {$id}\\\");\\n }\\n }\\n \\n public function logFailure(int $id, string $error): void\\n {\\n try {\\n $this->repository->fail($id, $error);\\n } catch (\\\\Throwable $e) {\\n error_log(\\\"KiProtokoll logFailure failed for ID {$id}\\\");\\n }\\n }\\n}\\n```\\n**UseCase-Anpassung**: `$protokollId` kann `null` sein → alle Folge-Calls prüfen.\\n\\n### Blocker 2: tokens_total Vertrag [EXPLIZIT]\\n```php\\n\/\/ Repository::complete() berechnet tokens_total intern:\\npublic function complete(\\n int $id,\\n string $response,\\n int $durationMs,\\n ?int $tokensInput,\\n ?int $tokensOutput\\n): void {\\n $tokensTotal = ($tokensInput ?? 0) + ($tokensOutput ?? 0);\\n \/\/ UPDATE ... SET tokens_total = :tokens_total ...\\n}\\n```\\n**Vertrag**: `tokens_total = tokens_input + tokens_output`, berechnet im Repository.\\n\\n### Blocker 3: Timestamps explizit gemappt [SCHEMA]\\n```php\\n\/\/ Repository::insert() setzt request_timestamp:\\nINSERT INTO protokoll (\\n timestamp, -- DEFAULT current_timestamp (Insert-Zeit)\\n request_timestamp, -- NOW(6) (Request-Zeit, identisch bei sync)\\n request_ip, client_name, request, model_name, status\\n) VALUES (\\n DEFAULT, NOW(6), :ip, :client, :request, :model, 'pending'\\n)\\n\\n\/\/ Repository::complete() setzt response_timestamp:\\nUPDATE protokoll SET\\n response = :response,\\n response_timestamp = NOW(6), -- Explizit!\\n duration_ms = :duration,\\n tokens_input = :ti, tokens_output = :to, tokens_total = :tt,\\n status = 'completed'\\nWHERE id = :id\\n```\\n\\n### Blocker 4: Orphan-Typologie & Cleanup [KONSISTENZ]\\n\\n**Typ 1**: `protokoll.status='pending'` ohne zugehörige `chat_messages`\\n- Ursache: Crash\/Timeout zwischen insert() und chat_messages.save()\\n- Cleanup: Cron-Job löscht\/markiert pending älter als 10 Minuten\\n\\n**Typ 2**: `chat_messages.llm_request_id` zeigt auf nicht-existenten `protokoll`-Eintrag\\n- Ursache: logRequest() silent fail → protokollId=null, aber trotzdem in chat_messages gespeichert mit NULL\\n- Lösung: llm_request_id bleibt NULL wenn Logging fehlschlägt (kein orphan, nur fehlende Referenz)\\n\\n```php\\n\/\/ UseCase:\\n$protokollId = $this->protokollService->logRequest(...); \/\/ kann null sein!\\n\/\/ ...\\n$this->messageRepo->save(..., llmRequestId: $protokollId); \/\/ NULL ist ok\\n```\\n\\n**Cleanup-Job** (Phase 5):\\n```sql\\n-- Markiere alte pending als 'error' (nicht löschen für Audit)\\nUPDATE protokoll \\nSET status = 'error', \\n error_message = 'Timeout: No response within 10 minutes'\\nWHERE status = 'pending' \\nAND request_timestamp < NOW() - INTERVAL 10 MINUTE\\n```\\n\\n### Blocker 5: Streaming-Abbruch Baseline [ROBUSTHEIT]\\n\\n**Primär**: Cleanup-Job (siehe Blocker 4) - deckt alle Fälle ab:\\n- Client-Abbruch\\n- Prozess-Exit\\n- Fatal Error\\n- Deploy-Restart\\n\\n**Sekundär** (optional, nicht im Scope): Shutdown-Handler als Ergänzung\\n- Nicht kritisch, da Cleanup-Job bereits abdeckt\\n- Kann später ergänzt werden für schnellere Erkennung\\n\\n### Blocker 5b: duration_ms Guard [EDGE-CASE]\\n```php\\n\/\/ In complete():\\n$durationMs = max(0, $durationMs); \/\/ Nie negativ\\n\/\/ Bei Abbruch: duration_ms = NULL (nicht 0)\\n```\\n\\n---\\n\\n## Finaler Implementierungsplan\\n\\n### Phase 1: Interface & Repository (30 min)\\n```php\\n\/\/ Domain\\\\Repository\\\\KiProtokollRepositoryInterface\\npublic function insert(\\n string $clientName,\\n string $request,\\n string $model,\\n string $requestIp\\n): int; \/\/ request_timestamp = NOW(6)\\n\\npublic function complete(\\n int $id,\\n string $response,\\n int $durationMs,\\n ?int $tokensInput,\\n ?int $tokensOutput\\n): void; \/\/ response_timestamp = NOW(6), tokens_total berechnet\\n\\npublic function fail(int $id, string $errorMessage): void;\\n\\npublic function cleanupStale(int $minutesOld = 10): int; \/\/ Returns affected rows\\n```\\n\\n### Phase 2: KiProtokollService (45 min)\\n```php\\n\/\/ Infrastructure\\\\Logging\\\\KiProtokollService\\nfinal class KiProtokollService\\n{\\n public function logRequest(...): ?int { try...catch, return null on fail }\\n public function logSuccess(...): void { try...catch, silent fail }\\n public function logFailure(...): void { try...catch, silent fail }\\n}\\n```\\n**Alle Methoden crash-sicher, Chat läuft immer weiter.**\\n\\n### Phase 3: UseCase Integration (45 min)\\n```php\\n\/\/ StreamingChatMessageUseCase\\n$protokollId = $this->protokollService->logRequest('web-chat', $message, $model, $ip);\\ntry {\\n \/\/ ... LLM Call ...\\n if ($protokollId !== null) {\\n $this->protokollService->logSuccess($protokollId, $answer, $duration, $tokens...);\\n }\\n} catch (\\\\Throwable $e) {\\n if ($protokollId !== null) {\\n $this->protokollService->logFailure($protokollId, $e->getMessage());\\n }\\n throw $e;\\n}\\n\/\/ chat_messages.save() mit llmRequestId (kann NULL sein)\\n```\\n\\n### Phase 4: DI & ChatMessageRepository (30 min)\\n- InfrastructureServiceProvider: KiProtokollService registrieren\\n- ChatServiceProvider: KiProtokollService in UseCase injizieren\\n- ChatMessageRepository::save(): `?int $llmRequestId = null`\\n\\n### Phase 5: Cleanup-Job & Tests (75 min)\\n```php\\n\/\/ Console Command oder Cron:\\n$affected = $this->protokollRepo->cleanupStale(10);\\n\/\/ Log: \\\"{$affected} stale protokoll entries marked as error\\\"\\n```\\n\\n**Tests:**\\n1. Unit: Repository insert\/complete\/fail\/cleanupStale\\n2. Unit: Service alle Methoden fangen Exceptions\\n3. **Integration: Repository wirft → UseCase liefert trotzdem Chat-Antwort**\\n4. E2E: Chat → \/protokoll mit client_name=\\\"web-chat\\\"\\n5. Edge: Timeout-Simulation → Cleanup markiert als error\\n\\n---\\n\\n## Finale Risiko-Analyse\\n\\n| Risiko | W | I | Mitigation |\\n|--------|---|---|------------|\\n| Performance (2 Writes) | M | M | Akzeptabel für Audit |\\n| Orphan Typ 1 (pending) | M | L | Cleanup-Job alle 5 min |\\n| Orphan Typ 2 (NULL ref) | L | L | Design: NULL ist valide |\\n| Logging crasht Chat | L | **H** | **try-catch in ALLEN Methoden** |\\n\\n---\\n\\n## Finale Abnahmekriterien\\n\\n- [ ] PHPStan Level 6: Keine Fehler\\n- [ ] Web-Chat erscheint in \/protokoll mit client_name=\\\"web-chat\\\"\\n- [ ] llm_request_id in chat_messages gesetzt (NULL wenn Logging fehlschlägt)\\n- [ ] **KiProtokollService: ALLE Methoden werfen niemals**\\n- [ ] **Integrationstest: Logging-Ausfall → Chat funktioniert trotzdem**\\n- [ ] tokens_total = tokens_input + tokens_output (im Repository berechnet)\\n- [ ] request_timestamp bei insert, response_timestamp bei complete\\n- [ ] Cleanup-Job für pending > 10 min\\n- [ ] Bestehende Funktionalität unverändert (Chat + Claude Code)\\n\\n---\\n\\n## Finaler Aufwand\\n\\n| Phase | Dauer |\\n|-------|-------|\\n| 1. Interface & Repository | 30 min |\\n| 2. KiProtokollService | 45 min |\\n| 3. UseCase Integration | 45 min |\\n| 4. DI & ChatMessageRepo | 30 min |\\n| 5. Cleanup-Job & Tests | 75 min |\\n| **Gesamt** | **4h** |\",\n \"type\": \"ai_task\",\n \"status\": \"completed\",\n \"created_by\": \"mcp-tasks\",\n \"created_by_type\": \"ai\",\n \"parent_task_id\": null,\n \"due_date\": null,\n \"created_at\": \"2025-12-29T08:32:13.037485\",\n \"updated_at\": \"2025-12-29T08:52:10.904668\",\n \"completed_at\": \"2025-12-29T08:52:10.904670\",\n \"metadata\": {}\n },\n \"message\": \"Task #527 status changed: in_progress -> completed\"\n}"
}
]
}