Protokoll #2228

ID2228
Zeitstempel2025-12-21 10:59:10.938971
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens14,464 (Input: 2,721, Output: 11,743)
Dauer97 ms
Request-Zeit2025-12-21 10:59:10.938971
Response-Zeit2025-12-21 10:59:11.036245

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "old_string": "## 10. Erfolgskriterien\n\n- [ ] Jedes Form-Element existiert nur 1x als Partial\n- [ ] Alle Views nutzen dieselben Partials\n- [ ] Einheitliche CSS-Klassen\n- [ ] Einheitliche Variablennamen\n- [ ] Collection-Labels überall deutsch\n- [ ] Modell-Dropdown überall mit Optgroups",
        "new_string": "## 10. Erfolgskriterien\n\n- [ ] Jedes Form-Element existiert nur 1x als Partial\n- [ ] Alle Views nutzen dieselben Partials\n- [ ] Einheitliche CSS-Klassen\n- [ ] Einheitliche Variablennamen\n- [ ] Collection-Labels überall deutsch\n- [ ] Modell-Dropdown überall mit Optgroups\n\n---\n\n## 11. Externe Supervision: Fragen und Antworten\n\n### 11.1 Grundannahmen und Systemgrenzen\n\n#### Q1.1: Single-Tenant vs. Multi-Tenant?\n\n**Status:** Single-Tenant (bewusste Entscheidung)\n\n| Aspekt | Entscheidung | Begründung |\n|--------|--------------|------------|\n| **Aktuell** | Single-Tenant | Ein Benutzer, ein System |\n| **user_id DEFAULT 1** | Zulässig | Placeholder für spätere Erweiterung |\n| **Migration zu Multi-Tenant** | Nicht geplant | YAGNI - kein absehbarer Business Case |\n\n**Risiko:** Falls Multi-Tenant später benötigt:\n- `user_id` ist bereits in Schema\n- Alle Queries müssten `WHERE user_id = ?` erhalten\n- Keine strukturelle Schuld, nur funktionale Erweiterung\n\n#### Q1.2: Laufzeitkritisch vs. Administrativ?\n\n**Fehlende Definition - ERGÄNZUNG ERFORDERLICH:**\n\n| Komponente | Klassifikation | Ausfallverhalten |\n|------------|----------------|------------------|\n| **Chat-Interface** | Laufzeitkritisch | Muss funktionieren |\n| **Content Studio** | Laufzeitkritisch | Muss funktionieren |\n| **Admin: \/ressourcen\/llm** | Administrativ | Kann temporär ausfallen |\n| **Admin: \/ressourcen\/collections** | Administrativ | Kann temporär ausfallen |\n| **Admin: \/ressourcen\/profil** | Administrativ | Fallback auf System-Defaults |\n| **Sync-Buttons** | Administrativ | Manueller Retry akzeptabel |\n\n**Empfehlung:** Diese Klassifikation in Operations-Dokumentation aufnehmen.\n\n#### Q1.3: Warum Qdrant Pflicht, Anthropic optional?\n\n| Dienst | Klassifikation | Begründung |\n|--------|----------------|------------|\n| **Qdrant** | Pflicht | RAG ist Kernfunktion, kein sinnvoller Fallback |\n| **Ollama** | Pflicht | Embeddings + lokale Modelle |\n| **Anthropic** | Optional | Fallback auf Ollama-Modelle möglich |\n\n**Klarstellung:** \"Optional\" bedeutet nicht \"unwichtig\", sondern \"degraded mode möglich\":\n\n```\nAnthropic down → Ollama-Modelle nutzbar (reduzierte Qualität)\nOllama down    → System nicht funktional\nQdrant down    → RAG nicht funktional, reiner Chat möglich\n```\n\n#### Q1.4: Fallback-Strategie (fehlt formal)\n\n**LÜCKE IDENTIFIZIERT - Neue Section erforderlich:**\n\n```php\n\/\/ Vorschlag: FallbackStrategy.php\nclass FallbackStrategy\n{\n    public const STRATEGIES = [\n        'anthropic_unavailable' => [\n            'action' => 'use_ollama',\n            'notify' => true,\n            'log_level' => 'warning',\n        ],\n        'ollama_unavailable' => [\n            'action' => 'fail_gracefully',\n            'notify' => true,\n            'log_level' => 'critical',\n        ],\n        'qdrant_unavailable' => [\n            'action' => 'disable_rag',\n            'notify' => true,\n            'log_level' => 'error',\n        ],\n    ];\n}\n```\n\n---\n\n### 11.2 LLM-Verwaltung und Modellannahmen\n\n#### Q2.1: Quelle für context_window und max_output_tokens?\n\n| Provider | Quelle | Zuverlässigkeit |\n|----------|--------|-----------------|\n| **Anthropic** | API Response `\/v1\/models` | Autoritativ |\n| **Ollama** | `ollama show <model>` | Lokal korrekt |\n| **Manuell** | Admin-Eingabe | Fehleranfällig |\n\n**Risiko:** Cloud-Modelle ändern sich ohne Sync.\n\n**Mitigation (ERGÄNZUNG):**\n```sql\nALTER TABLE llm_models\nADD COLUMN params_verified_at DATETIME,\nADD COLUMN params_source ENUM('api', 'manual', 'estimated') DEFAULT 'estimated';\n```\n\n#### Q2.2: Veraltete Modellparameter?\n\n**Problem:** Token-Kalkulation basiert auf `max_output_tokens`.\n\n**Lösung:**\n1. **Sync-Warnung:** Wenn `last_synced_at > 7 days` → UI-Hinweis\n2. **Laufzeit-Check:** API-Fehler `max_tokens exceeded` → Parameter korrigieren\n3. **Konservative Defaults:** Bei Unsicherheit 75% von max_output_tokens\n\n```php\n\/\/ TokenCalculator.php\npublic function getSafeMaxTokens(array $model): int\n{\n    $age = time() - strtotime($model['last_synced_at'] ?? '1970-01-01');\n    $confidence = $age < 86400 ? 1.0 : ($age < 604800 ? 0.9 : 0.75);\n\n    return (int) ($model['max_output_tokens'] * $confidence);\n}\n```\n\n#### Q2.3: Warum fehlende Modelle nicht deaktivieren?\n\n**Bewusste Entscheidung:**\n\n| Szenario | Verhalten | Begründung |\n|----------|-----------|------------|\n| API temporär down | Modell bleibt aktiv | Keine false negatives |\n| Modell deprecated | `last_synced_at` veraltet | Admin-Review erforderlich |\n| Modell entfernt | Bleibt in DB | Historische Referenzen erhalten |\n\n**Empfehlung:** Neues Feld `api_status`:\n```sql\nALTER TABLE llm_models\nADD COLUMN api_status ENUM('available', 'deprecated', 'removed', 'unknown') DEFAULT 'unknown';\n```\n\n#### Q2.4: model_id Eindeutigkeit?\n\n**Problem:** `claude-opus-4-5-20251101` vs. `anthropic:claude-opus-4-5-20251101`\n\n**Aktuelle Lösung:** UNIQUE KEY auf `(provider, model_id)` - ausreichend.\n\n**Langfristig:** Interne UUID nicht nötig, da provider+model_id stabil.\n\n#### Q2.5: Warum ENUM statt Lookup für provider?\n\n| Option | Pro | Contra |\n|--------|-----|--------|\n| ENUM | Schnell, typsicher | Schema-Änderung bei neuem Provider |\n| Lookup-Table | Flexibel | Overhead, JOIN erforderlich |\n\n**Entscheidung:** ENUM bleibt. Neue Provider = seltenes Event, Schema-Migration akzeptabel.\n\n#### Q2.6: Ollama Tag-Wechsel (latest)?\n\n**Problem:** `mistral:latest` kann morgen anderes Modell sein.\n\n**Lösung:**\n```php\n\/\/ OllamaSyncService.php\npublic function syncModels(): void\n{\n    $models = $this->ollama->list();\n\n    foreach ($models as $model) {\n        \/\/ Vollständigen Hash speichern\n        $this->repository->upsert([\n            'model_id' => $model['name'],           \/\/ \"mistral:latest\"\n            'model_digest' => $model['digest'],     \/\/ \"abc123...\"\n            'display_name' => $model['name'],\n        ]);\n    }\n}\n```\n\n**ERGÄNZUNG im Schema:**\n```sql\nALTER TABLE llm_models\nADD COLUMN model_digest VARCHAR(64) COMMENT 'SHA256 bei Ollama';\n```\n\n---\n\n### 11.3 Sync-Logik und Ausfallsicherheit\n\n#### Q3.1: Anthropic Sync Fehler?\n\n**Fehlerbehandlung (ERGÄNZUNG):**\n\n```php\npublic function syncAnthropic(): SyncResult\n{\n    try {\n        $models = $this->anthropicClient->listModels();\n        $this->processModels($models);\n\n        return SyncResult::success(count($models));\n    } catch (ApiException $e) {\n        $this->logger->error('Anthropic sync failed', ['error' => $e->getMessage()]);\n\n        return SyncResult::failed($e->getMessage());\n    }\n}\n\n\/\/ UI zeigt:\n\/\/ ✓ Sync erfolgreich: 5 Modelle aktualisiert\n\/\/ ✗ Sync fehlgeschlagen: API nicht erreichbar (letzte erfolgreiche Sync: vor 2 Tagen)\n```\n\n#### Q3.2: Logging von Metadaten-Änderungen?\n\n**LÜCKE - Audit-Trail erforderlich:**\n\n```sql\nCREATE TABLE llm_model_history (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    model_id INT NOT NULL,\n    field_name VARCHAR(50) NOT NULL,\n    old_value TEXT,\n    new_value TEXT,\n    changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    changed_by VARCHAR(50) DEFAULT 'sync',\n\n    INDEX idx_model (model_id),\n    INDEX idx_changed (changed_at)\n);\n```\n\n#### Q3.3: shell_exec('ollama list') sicher?\n\n**Status:** Ja, unter Bedingungen.\n\n| Prüfung | Status |\n|---------|--------|\n| Ollama installiert | ✓ Pflichtkomponente |\n| Keine User-Eingabe in Command | ✓ Hardcoded |\n| Output-Parsing sicher | ✓ Kein eval() |\n| Timeout | ⚠️ FEHLT |\n\n**ERGÄNZUNG:**\n```php\n\/\/ Statt shell_exec():\n$process = proc_open(['ollama', 'list'], $descriptors, $pipes);\nstream_set_timeout($pipes[1], 10); \/\/ 10s timeout\n```\n\n#### Q3.4: Sync vs. laufende Sessions?\n\n**Problem:** Session nutzt `model_id`, Sync ändert Metadaten.\n\n**Analyse:**\n- `model_id` selbst ändert sich nicht\n- `max_output_tokens` Änderung → nächster Request nutzt neue Werte\n- **Kein Konsistenzproblem**, da Session-Settings separat gespeichert\n\n---\n\n### 11.4 Dropdown-Partials und UI-Semantik\n\n#### Q4.1: Warum feste Optgroups?\n\n**Pragmatische Entscheidung:**\n\n```php\n\/\/ Aktuell:\n<optgroup label=\"Cloud\">\n<optgroup label=\"Lokal\">\n\n\/\/ Datengetrieben wäre:\n<optgroup label=\"<?= $providerLabels[$provider] ?>\">\n```\n\n**Entscheidung:** Hardcoded bleibt, weil:\n1. Nur 2 relevante Kategorien\n2. Sortierung Cloud→Lokal ist gewollt\n3. Kein Business Case für dynamische Gruppen\n\n#### Q4.2: Dritter Provider-Typ?\n\n**Bei Bedarf erweitern:**\n```php\nconst PROVIDER_GROUPS = [\n    'cloud' => ['anthropic', 'openai', 'google'],\n    'local' => ['ollama'],\n    'private' => ['custom'],  \/\/ Zukünftig\n];\n```\n\n#### Q4.3: Leeres $models?\n\n**Fehlerbehandlung (ERGÄNZUNG):**\n```php\n\/\/ model-select.php\n<?php if (empty($models)): ?>\n    <select disabled class=\"form-select form-select--error\">\n        <option>Keine Modelle verfügbar - Sync erforderlich<\/option>\n    <\/select>\n<?php else: ?>\n    \/\/ Normaler Select\n<?php endif; ?>\n```\n\n#### Q4.4: Warum id=\"model\" hardcoded?\n\n**Problem:** Mehrere Instanzen auf einer Seite → ID-Konflikt.\n\n**KORREKTUR:**\n```php\n$id = $id ?? 'model_' . uniqid();\n```\n\n---\n\n### 11.5 Collection-Verwaltung und RAG-Konsistenz\n\n#### Q5.1: Verantwortung für vector_size?\n\n**Verantwortlichkeiten:**\n\n| Aktion | Verantwortlich | Prüfung |\n|--------|----------------|---------|\n| Collection erstellen | Pipeline\/Admin | Muss Embedding-Modell angeben |\n| Embedding-Modell wechseln | Admin | Re-Embedding erforderlich |\n| vector_size validieren | Sync | Qdrant ist Source of Truth |\n\n#### Q5.2: Inkompatible Embeddings bei Multi-Select?\n\n**KRITISCHE LÜCKE:**\n\n```php\n\/\/ CollectionValidator.php (NEU)\npublic function validateSelection(array $collectionIds): ValidationResult\n{\n    $collections = $this->repository->findByIds($collectionIds);\n    $vectorSizes = array_unique(array_column($collections, 'vector_size'));\n\n    if (count($vectorSizes) > 1) {\n        return ValidationResult::error(\n            'Inkompatible Collections: Unterschiedliche Vektorgrößen'\n        );\n    }\n\n    return ValidationResult::ok();\n}\n```\n\n#### Q5.3: embedding_model nur dokumentativ?\n\n**Aktuell:** Ja, rein informativ.\n\n**ERGÄNZUNG:** Validierung bei Suche:\n```php\n\/\/ Q... [TRUNCATED-2147dee6fdfc0f68]"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/docs\/plan-form-components-refactoring.md",
        "oldString": "## 10. Erfolgskriterien\n\n- [ ] Jedes Form-Element existiert nur 1x als Partial\n- [ ] Alle Views nutzen dieselben Partials\n- [ ] Einheitliche CSS-Klassen\n- [ ] Einheitliche Variablennamen\n- [ ] Collection-Labels überall deutsch\n- [ ] Modell-Dropdown überall mit Optgroups",
        "newString": "## 10. Erfolgskriterien\n\n- [ ] Jedes Form-Element existiert nur 1x als Partial\n- [ ] Alle Views nutzen dieselben Partials\n- [ ] Einheitliche CSS-Klassen\n- [ ] Einheitliche Variablennamen\n- [ ] Collection-Labels überall deutsch\n- [ ] Modell-Dropdown überall mit Optgroups\n\n---\n\n## 11. Externe Supervision: Fragen und Antworten\n\n### 11.1 Grundannahmen und Systemgrenzen\n\n#### Q1.1: Single-Tenant vs. Multi-Tenant?\n\n**Status:** Single-Tenant (bewusste Entscheidung)\n\n| Aspekt | Entscheidung | Begründung |\n|--------|--------------|------------|\n| **Aktuell** | Single-Tenant | Ein Benutzer, ein System |\n| **user_id DEFAULT 1** | Zulässig | Placeholder für spätere Erweiterung |\n| **Migration zu Multi-Tenant** | Nicht geplant | YAGNI - kein absehbarer Business Case |\n\n**Risiko:** Falls Multi-Tenant später benötigt:\n- `user_id` ist bereits in Schema\n- Alle Queries müssten `WHERE user_id = ?` erhalten\n- Keine strukturelle Schuld, nur funktionale Erweiterung\n\n#### Q1.2: Laufzeitkritisch vs. Administrativ?\n\n**Fehlende Definition - ERGÄNZUNG ERFORDERLICH:**\n\n| Komponente | Klassifikation | Ausfallverhalten |\n|------------|----------------|------------------|\n| **Chat-Interface** | Laufzeitkritisch | Muss funktionieren |\n| **Content Studio** | Laufzeitkritisch | Muss funktionieren |\n| **Admin: \/ressourcen\/llm** | Administrativ | Kann temporär ausfallen |\n| **Admin: \/ressourcen\/collections** | Administrativ | Kann temporär ausfallen |\n| **Admin: \/ressourcen\/profil** | Administrativ | Fallback auf System-Defaults |\n| **Sync-Buttons** | Administrativ | Manueller Retry akzeptabel |\n\n**Empfehlung:** Diese Klassifikation in Operations-Dokumentation aufnehmen.\n\n#### Q1.3: Warum Qdrant Pflicht, Anthropic optional?\n\n| Dienst | Klassifikation | Begründung |\n|--------|----------------|------------|\n| **Qdrant** | Pflicht | RAG ist Kernfunktion, kein sinnvoller Fallback |\n| **Ollama** | Pflicht | Embeddings + lokale Modelle |\n| **Anthropic** | Optional | Fallback auf Ollama-Modelle möglich |\n\n**Klarstellung:** \"Optional\" bedeutet nicht \"unwichtig\", sondern \"degraded mode möglich\":\n\n```\nAnthropic down → Ollama-Modelle nutzbar (reduzierte Qualität)\nOllama down    → System nicht funktional\nQdrant down    → RAG nicht funktional, reiner Chat möglich\n```\n\n#### Q1.4: Fallback-Strategie (fehlt formal)\n\n**LÜCKE IDENTIFIZIERT - Neue Section erforderlich:**\n\n```php\n\/\/ Vorschlag: FallbackStrategy.php\nclass FallbackStrategy\n{\n    public const STRATEGIES = [\n        'anthropic_unavailable' => [\n            'action' => 'use_ollama',\n            'notify' => true,\n            'log_level' => 'warning',\n        ],\n        'ollama_unavailable' => [\n            'action' => 'fail_gracefully',\n            'notify' => true,\n            'log_level' => 'critical',\n        ],\n        'qdrant_unavailable' => [\n            'action' => 'disable_rag',\n            'notify' => true,\n            'log_level' => 'error',\n        ],\n    ];\n}\n```\n\n---\n\n### 11.2 LLM-Verwaltung und Modellannahmen\n\n#### Q2.1: Quelle für context_window und max_output_tokens?\n\n| Provider | Quelle | Zuverlässigkeit |\n|----------|--------|-----------------|\n| **Anthropic** | API Response `\/v1\/models` | Autoritativ |\n| **Ollama** | `ollama show <model>` | Lokal korrekt |\n| **Manuell** | Admin-Eingabe | Fehleranfällig |\n\n**Risiko:** Cloud-Modelle ändern sich ohne Sync.\n\n**Mitigation (ERGÄNZUNG):**\n```sql\nALTER TABLE llm_models\nADD COLUMN params_verified_at DATETIME,\nADD COLUMN params_source ENUM('api', 'manual', 'estimated') DEFAULT 'estimated';\n```\n\n#### Q2.2: Veraltete Modellparameter?\n\n**Problem:** Token-Kalkulation basiert auf `max_output_tokens`.\n\n**Lösung:**\n1. **Sync-Warnung:** Wenn `last_synced_at > 7 days` → UI-Hinweis\n2. **Laufzeit-Check:** API-Fehler `max_tokens exceeded` → Parameter korrigieren\n3. **Konservative Defaults:** Bei Unsicherheit 75% von max_output_tokens\n\n```php\n\/\/ TokenCalculator.php\npublic function getSafeMaxTokens(array $model): int\n{\n    $age = time() - strtotime($model['last_synced_at'] ?? '1970-01-01');\n    $confidence = $age < 86400 ? 1.0 : ($age < 604800 ? 0.9 : 0.75);\n\n    return (int) ($model['max_output_tokens'] * $confidence);\n}\n```\n\n#### Q2.3: Warum fehlende Modelle nicht deaktivieren?\n\n**Bewusste Entscheidung:**\n\n| Szenario | Verhalten | Begründung |\n|----------|-----------|------------|\n| API temporär down | Modell bleibt aktiv | Keine false negatives |\n| Modell deprecated | `last_synced_at` veraltet | Admin-Review erforderlich |\n| Modell entfernt | Bleibt in DB | Historische Referenzen erhalten |\n\n**Empfehlung:** Neues Feld `api_status`:\n```sql\nALTER TABLE llm_models\nADD COLUMN api_status ENUM('available', 'deprecated', 'removed', 'unknown') DEFAULT 'unknown';\n```\n\n#### Q2.4: model_id Eindeutigkeit?\n\n**Problem:** `claude-opus-4-5-20251101` vs. `anthropic:claude-opus-4-5-20251101`\n\n**Aktuelle Lösung:** UNIQUE KEY auf `(provider, model_id)` - ausreichend.\n\n**Langfristig:** Interne UUID nicht nötig, da provider+model_id stabil.\n\n#### Q2.5: Warum ENUM statt Lookup für provider?\n\n| Option | Pro | Contra |\n|--------|-----|--------|\n| ENUM | Schnell, typsicher | Schema-Änderung bei neuem Provider |\n| Lookup-Table | Flexibel | Overhead, JOIN erforderlich |\n\n**Entscheidung:** ENUM bleibt. Neue Provider = seltenes Event, Schema-Migration akzeptabel.\n\n#### Q2.6: Ollama Tag-Wechsel (latest)?\n\n**Problem:** `mistral:latest` kann morgen anderes Modell sein.\n\n**Lösung:**\n```php\n\/\/ OllamaSyncService.php\npublic function syncModels(): void\n{\n    $models = $this->ollama->list();\n\n    foreach ($models as $model) {\n        \/\/ Vollständigen Hash speichern\n        $this->repository->upsert([\n            'model_id' => $model['name'],           \/\/ \"mistral:latest\"\n            'model_digest' => $model['digest'],     \/\/ \"abc123...\"\n            'display_name' => $model['name'],\n        ]);\n    }\n}\n```\n\n**ERGÄNZUNG im Schema:**\n```sql\nALTER TABLE llm_models\nADD COLUMN model_digest VARCHAR(64) COMMENT 'SHA256 bei Ollama';\n```\n\n---\n\n### 11.3 Sync-Logik und Ausfallsicherheit\n\n#### Q3.1: Anthropic Sync Fehler?\n\n**Fehlerbehandlung (ERGÄNZUNG):**\n\n```php\npublic function syncAnthropic(): SyncResult\n{\n    try {\n        $models = $this->anthropicClient->listModels();\n        $this->processModels($models);\n\n        return SyncResult::success(count($models));\n    } catch (ApiException $e) {\n        $this->logger->error('Anthropic sync failed', ['error' => $e->getMessage()]);\n\n        return SyncResult::failed($e->getMessage());\n    }\n}\n\n\/\/ UI zeigt:\n\/\/ ✓ Sync erfolgreich: 5 Modelle aktualisiert\n\/\/ ✗ Sync fehlgeschlagen: API nicht erreichbar (letzte erfolgreiche Sync: vor 2 Tagen)\n```\n\n#### Q3.2: Logging von Metadaten-Änderungen?\n\n**LÜCKE - Audit-Trail erforderlich:**\n\n```sql\nCREATE TABLE llm_model_history (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    model_id INT NOT NULL,\n    field_name VARCHAR(50) NOT NULL,\n    old_value TEXT,\n    new_value TEXT,\n    changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    changed_by VARCHAR(50) DEFAULT 'sync',\n\n    INDEX idx_model (model_id),\n    INDEX idx_changed (changed_at)\n);\n```\n\n#### Q3.3: shell_exec('ollama list') sicher?\n\n**Status:** Ja, unter Bedingungen.\n\n| Prüfung | Status |\n|---------|--------|\n| Ollama installiert | ✓ Pflichtkomponente |\n| Keine User-Eingabe in Command | ✓ Hardcoded |\n| Output-Parsing sicher | ✓ Kein eval() |\n| Timeout | ⚠️ FEHLT |\n\n**ERGÄNZUNG:**\n```php\n\/\/ Statt shell_exec():\n$process = proc_open(['ollama', 'list'], $descriptors, $pipes);\nstream_set_timeout($pipes[1], 10); \/\/ 10s timeout\n```\n\n#### Q3.4: Sync vs. laufende Sessions?\n\n**Problem:** Session nutzt `model_id`, Sync ändert Metadaten.\n\n**Analyse:**\n- `model_id` selbst ändert sich nicht\n- `max_output_tokens` Änderung → nächster Request nutzt neue Werte\n- **Kein Konsistenzproblem**, da Session-Settings separat gespeichert\n\n---\n\n### 11.4 Dropdown-Partials und UI-Semantik\n\n#### Q4.1: Warum feste Optgroups?\n\n**Pragmatische Entscheidung:**\n\n```php\n\/\/ Aktuell:\n<optgroup label=\"Cloud\">\n<optgroup label=\"Lokal\">\n\n\/\/ Datengetrieben wäre:\n<optgroup label=\"<?= $providerLabels[$provider] ?>\">\n```\n\n**Entscheidung:** Hardcoded bleibt, weil:\n1. Nur 2 relevante Kategorien\n2. Sortierung Cloud→Lokal ist gewollt\n3. Kein Business Case für dynamische Gruppen\n\n#### Q4.2: Dritter Provider-Typ?\n\n**Bei Bedarf erweitern:**\n```php\nconst PROVIDER_GROUPS = [\n    'cloud' => ['anthropic', 'openai', 'google'],\n    'local' => ['ollama'],\n    'private' => ['custom'],  \/\/ Zukünftig\n];\n```\n\n#### Q4.3: Leeres $models?\n\n**Fehlerbehandlung (ERGÄNZUNG):**\n```php\n\/\/ model-select.php\n<?php if (empty($models)): ?>\n    <select disabled class=\"form-select form-select--error\">\n        <option>Keine Modelle verfügbar - Sync erforderlich<\/option>\n    <\/select>\n<?php else: ?>\n    \/\/ Normaler Select\n<?php endif; ?>\n```\n\n#### Q4.4: Warum id=\"model\" hardcoded?\n\n**Problem:** Mehrere Instanzen auf einer Seite → ID-Konflikt.\n\n**KORREKTUR:**\n```php\n$id = $id ?? 'model_' . uniqid();\n```\n\n---\n\n### 11.5 Collection-Verwaltung und RAG-Konsistenz\n\n#### Q5.1: Verantwortung für vector_size?\n\n**Verantwortlichkeiten:**\n\n| Aktion | Verantwortlich | Prüfung |\n|--------|----------------|---------|\n| Collection erstellen | Pipeline\/Admin | Muss Embedding-Modell angeben |\n| Embedding-Modell wechseln | Admin | Re-Embedding erforderlich |\n| vector_size validieren | Sync | Qdrant ist Source of Truth |\n\n#### Q5.2: Inkompatible Embeddings bei Multi-Select?\n\n**KRITISCHE LÜCKE:**\n\n```php\n\/\/ CollectionValidator.php (NEU)\npublic function validateSelection(array $collectionIds): ValidationResult\n{\n    $collections = $this->repository->findByIds($collectionIds);\n    $vectorSizes = array_unique(array_column($collections, 'vector_size'));\n\n    if (count($vectorSizes) > 1) {\n        return ValidationResult::error(\n            'Inkompatible Collections: Unterschiedliche Vektorgrößen'\n        );\n    }\n\n    return ValidationResult::ok();\n}\n```\n\n#### Q5.3: embedding_model nur dokumentativ?\n\n**Aktuell:** Ja, rein informativ.\n\n**ERGÄNZUNG:** Validierung bei Suche:\n```php\n\/\/ Q... [TRUNCATED-2147dee6fdfc0f68]",
        "originalFile": "# Plan: Einheitliche Form-Komponenten (Chat & Content Studio)\n\n## Status: PLANUNG\n**Erstellt:** 2025-12-21\n**Ziel:** DRY, KISS, SRP - Shared Partials für alle wiederverwendbaren Form-Elemente\n\n---\n\n## Projekt-Kontext (für externe Supervision)\n\n### Was ist das System?\n\n**Campus.systemische-tools.de** ist ein KI-gestütztes Content-Management-System mit zwei Hauptmodulen:\n\n| Modul | Beschreibung | Hauptfunktionen |\n|-------|--------------|-----------------|\n| **Chat-Interface** | RAG-basierte Konversation mit verschiedenen LLMs | Session-Management, Multi-Collection-Suche, Streaming-Responses |\n| **Content Studio** | Automatisierte Content-Generierung mit Qualitätssicherung | Briefing → Generierung → Kritik → Revision → Freigabe |\n\n### Warum dieses Refactoring?\n\n**Problem:**\n- Chat und Content Studio wurden unabhängig entwickelt\n- Gleiche UI-Elemente (Modell-Dropdown, Collections, etc.) existieren in unterschiedlichen Implementierungen\n- Inkonsistente UX: Modell-Dropdown mit\/ohne Optgroups, Collections als Multi-Select\/Checkboxes\/Single-Select\n\n**Symptome:**\n- Doppelter Code in Views\n- Unterschiedliche Variablennamen (`$profiles` vs `$authorProfiles`)\n- Keine zentrale Verwaltung für LLM-Modelle und Collections\n- Keine persistenten User-Präferenzen\n\n**Ziel:**\n- Einheitliche Partials für alle Form-Elemente\n- Zentrale Admin-Seiten für LLM-Modelle und Collections\n- User-Präferenzen mit 3-stufiger Werte-Hierarchie\n- Contracts und Structures in beiden Systemen nutzbar (Multi-Select)\n\n### Technologie-Stack\n\n| Schicht | Technologie | Details |\n|---------|-------------|---------|\n| **Backend** | PHP 8.2 | MVC-Pattern in `\/src\/` |\n| **Frontend** | HTML + HTMX | Reactive ohne SPA-Framework |\n| **Datenbank** | MariaDB 10.11 | Zwei Datenbanken (siehe unten) |\n| **Vector-DB** | Qdrant | RAG-Embeddings, Collections |\n| **LLM-APIs** | Anthropic Claude, Ollama | Cloud + lokal |\n| **Pipeline** | Python 3.11 | Content-Generierung, Embedding |\n| **Styling** | CSS Custom Properties | Theme-Support |\n\n### Datenbank-Architektur\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        MariaDB                                   │\n├─────────────────────────────┬───────────────────────────────────┤\n│         ki_dev              │           ki_content              │\n│   (Infrastruktur\/Config)    │        (Laufzeitdaten)            │\n├─────────────────────────────┼───────────────────────────────────┤\n│ • llm_models        [NEU]   │ • chat_sessions      [ERWEITERT]  │\n│ • llm_presets       [NEU]   │ • chat_messages                   │\n│ • rag_collections   [NEU]   │ • content_orders     [MIGRIERT]   │\n│ • user_preferences  [NEU]   │ • content_versions                │\n│ • content_config            │ • content_critiques               │\n│ • tasks                     │ • content_sources                 │\n│ • contracts                 │                                   │\n│ • dokumentation             │                                   │\n└─────────────────────────────┴───────────────────────────────────┘\n```\n\n### Externe Abhängigkeiten\n\n| Dienst | Verwendung | Ausfallverhalten |\n|--------|------------|------------------|\n| **Anthropic API** | Claude-Modelle, Chat-Completion | Fallback auf Ollama |\n| **Ollama (lokal)** | Lokale Modelle, Embeddings | Pflichtkomponente |\n| **Qdrant (lokal)** | Vektor-Suche, Collection-Metadaten | Pflichtkomponente |\n\n### Architektur-Prinzipien (angewendet)\n\n| Prinzip | Anwendung in diesem Plan |\n|---------|--------------------------|\n| **DRY** | Partials als Single Source für Form-Elemente |\n| **SRP** | Jedes Partial = eine Aufgabe |\n| **KISS** | Context Limit hardcoded, keine Over-Engineering |\n| **YAGNI** | Section 8 definiert klare Grenzen |\n| **MVC** | Controller → View (Partials) → Repository |\n\n### Wichtige Konventionen\n\n1. **MCP-Server:** Alle DB-Zugriffe via MCP-Tools (kein direktes SQL)\n2. **Kein Git:** Server ohne Versionskontrolle\n3. **Dev-First:** Entwicklung auf dev.campus, dann Sync zu prod\n4. **Quality-Gate:** `\/opt\/scripts\/php-check.sh` muss vor Sync bestehen\n\n---\n\n## 0. LLM-Verwaltung (Grundlage für Modell-Dropdown)\n\n### 0.1 Architektur-Entscheidung\n\n**Gewählt: Variante B - Dedizierte LLM-Verwaltung**\n\nStatt dynamischer API-Abfrage bei jedem Seitenaufruf:\n- Zentrale Admin-Seite `\/ressourcen\/llm`\n- Datenbank-Tabelle als Single Source of Truth\n- Sync-Buttons für Provider (Anthropic, Ollama, weitere)\n- Sprechende Namen und Zusatzmetadaten\n\n### 0.2 Datenbank-Tabelle `llm_models`\n\n```sql\nCREATE TABLE llm_models (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    provider ENUM('anthropic', 'ollama', 'openai', 'google', 'mistral', 'custom') NOT NULL,\n    model_id VARCHAR(100) NOT NULL,        -- API-ID: \"claude-opus-4-5-20251101\"\n    display_name VARCHAR(100) NOT NULL,    -- Anzeige: \"Claude Opus 4.5\"\n    description TEXT,                      -- Kurzbeschreibung\n    context_window INT,                    -- Max. Tokens Input: 200000\n    max_output_tokens INT,                 -- Max. Tokens Output: 8192\n    input_price_per_mtok DECIMAL(10,4),    -- Preis Input $\/MTok\n    output_price_per_mtok DECIMAL(10,4),   -- Preis Output $\/MTok\n    capabilities JSON,                     -- {\"vision\": true, \"function_calling\": true}\n    is_local BOOLEAN DEFAULT FALSE,        -- Lokal (Ollama) vs. Cloud\n    is_active BOOLEAN DEFAULT TRUE,        -- In Dropdowns anzeigen?\n    sort_order INT DEFAULT 0,              -- Reihenfolge in Dropdowns\n    last_synced_at DATETIME,               -- Letzte Synchronisation\n    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    UNIQUE KEY unique_provider_model (provider, model_id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n```\n\n### 0.3 Admin-Seite `\/ressourcen\/llm`\n\n**URL:** `\/ressourcen\/llm`\n**Controller:** `RessourcenController::llmIndex()`\n\n**Funktionen:**\n\n| Button | Aktion |\n|--------|--------|\n| \"Anthropic synchronisieren\" | `GET api.anthropic.com\/v1\/models` → DB aktualisieren |\n| \"Ollama synchronisieren\" | `ollama list` → DB aktualisieren |\n| \"Neuer Provider\" | Manuell weiteren Provider hinzufügen |\n\n**Tabellen-Ansicht:**\n\n| Provider | Model-ID | Display-Name | Context | Preis In\/Out | Lokal | Aktiv | Aktionen |\n|----------|----------|--------------|---------|--------------|-------|-------|----------|\n| Anthropic | claude-opus-4-5-20251101 | Claude Opus 4.5 | 200k | $15\/$75 | - | ✓ | Bearbeiten |\n| Ollama | mistral:latest | Mistral 7B | 32k | - | ✓ | ✓ | Bearbeiten |\n\n**Bearbeiten-Dialog:**\n- Display-Name ändern\n- Beschreibung hinzufügen\n- Context-Window \/ Max-Output korrigieren\n- Preise eintragen (für Kostenberechnung)\n- Aktivieren\/Deaktivieren\n- Sortierung ändern\n\n### 0.4 Sync-Logik\n\n**Anthropic Sync:**\n```php\n\/\/ GET https:\/\/api.anthropic.com\/v1\/models\n\/\/ Response: {\"data\": [{\"id\": \"claude-opus-4-5-20251101\", ...}]}\n\nforeach ($apiModels as $model) {\n    \/\/ INSERT ... ON DUPLICATE KEY UPDATE\n    \/\/ Neue Modelle: is_active = true, display_name = model_id (initial)\n    \/\/ Existierende: last_synced_at aktualisieren\n    \/\/ Fehlende: NICHT löschen, nur last_synced_at bleibt alt\n}\n```\n\n**Ollama Sync:**\n```php\n\/\/ $ ollama list\n\/\/ NAME              ID           SIZE    MODIFIED\n\/\/ mistral:latest    abc123...    4.1 GB  2 days ago\n\n$output = shell_exec('ollama list');\n\/\/ Parsen und in DB einfügen\n```\n\n### 0.5 Dropdown-Query\n\n```php\n\/\/ ModelService::getActiveModels()\npublic function getActiveModels(): array\n{\n    return $this->db->query(\"\n        SELECT model_id, display_name, provider, is_local, context_window\n        FROM llm_models\n        WHERE is_active = 1\n        ORDER BY is_local ASC, sort_order ASC, display_name ASC\n    \")->fetchAll();\n}\n```\n\n### 0.6 Partial nutzt DB-Daten\n\n```php\n\/\/ \/src\/View\/partials\/form\/model-select.php\n<?php\n$models = $models ?? [];\n$selected = $selected ?? '';\n$variant = $variant ?? 'default';\n$class = $variant === 'inline' ? 'form-select--inline' : 'form-select';\n?>\n<select name=\"model\" id=\"model\" class=\"<?= $class ?>\">\n    <optgroup label=\"Cloud\">\n        <?php foreach ($models as $m): ?>\n        <?php if (!$m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n    <optgroup label=\"Lokal\">\n        <?php foreach ($models as $m): ?>\n        <?php if ($m['is_local']): ?>\n        <option value=\"<?= $m['model_id'] ?>\" <?= $selected === $m['model_id'] ? 'selected' : '' ?>>\n            <?= htmlspecialchars($m['display_name']) ?>\n        <\/option>\n        <?php endif; ?>\n        <?php endforeach; ?>\n    <\/optgroup>\n<\/select>\n```\n\n### 0.7 Migrations-Schritte (LLM-Verwaltung)\n\n1. [ ] Tabelle `llm_models` in `ki_dev` erstellen\n2. [ ] `LlmModelRepository` erstellen\n3. [ ] `LlmSyncService` erstellen (Anthropic + Ollama)\n4. [ ] Route `\/ressourcen\/llm` anlegen\n5. [ ] `RessourcenController::llmIndex()` implementieren\n6. [ ] View `\/ressourcen\/llm\/index.php` erstellen\n7. [ ] Sync-Buttons implementieren\n8. [ ] Bearbeiten-Funktionalität\n9. [ ] Initial-Sync durchführen (bestehende Modelle importieren)\n10. [ ] `ModelConfig.php` durch `LlmModelRepository` ersetzen\n11. [ ] Partial `model-select.php` erstellen\n12. [ ] Chat und Content auf Partial umstellen\n\n---\n\n## 0b. Collection-Verwaltung (Grundlage für Collection-Dropdown)\n\n### 0b.1 Architektur-Entscheidung\n\n**Gewählt: DB-Tabelle analog zu LLMs**\n\n- Zentrale Admin-Seite `\/ressourcen\/collections`\n- Datenbank-Tabelle als Single Source of Truth\n- Sync mit Qdrant (Metadaten abrufen)\n- Sprechende Namen und Beschreibungen\n- **Multi-Select** als einheitliche Darstellung\n\n### 0b.2 Datenbank-Tabelle `rag_collections`\n\n```sql\nCREATE TABLE rag_collections (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    collection_id VARCHAR(100) NOT NULL UNIQUE,  -- Qdrant-Name: \"documents\"\n    display_name VARCHAR(100) NOT NULL,          -- ... [TRUNCATED-e33c117a2bed6b0d]",
        "structuredPatch": [
            {
                "oldStart": 2817,
                "oldLines": 3,
                "newStart": 2817,
                "newLines": 837,
                "lines": [
                    " - [ ] Einheitliche Variablennamen",
                    " - [ ] Collection-Labels überall deutsch",
                    " - [ ] Modell-Dropdown überall mit Optgroups",
                    "+",
                    "+---",
                    "+",
                    "+## 11. Externe Supervision: Fragen und Antworten",
                    "+",
                    "+### 11.1 Grundannahmen und Systemgrenzen",
                    "+",
                    "+#### Q1.1: Single-Tenant vs. Multi-Tenant?",
                    "+",
                    "+**Status:** Single-Tenant (bewusste Entscheidung)",
                    "+",
                    "+| Aspekt | Entscheidung | Begründung |",
                    "+|--------|--------------|------------|",
                    "+| **Aktuell** | Single-Tenant | Ein Benutzer, ein System |",
                    "+| **user_id DEFAULT 1** | Zulässig | Placeholder für spätere Erweiterung |",
                    "+| **Migration zu Multi-Tenant** | Nicht geplant | YAGNI - kein absehbarer Business Case |",
                    "+",
                    "+**Risiko:** Falls Multi-Tenant später benötigt:",
                    "+- `user_id` ist bereits in Schema",
                    "+- Alle Queries müssten `WHERE user_id = ?` erhalten",
                    "+- Keine strukturelle Schuld, nur funktionale Erweiterung",
                    "+",
                    "+#### Q1.2: Laufzeitkritisch vs. Administrativ?",
                    "+",
                    "+**Fehlende Definition - ERGÄNZUNG ERFORDERLICH:**",
                    "+",
                    "+| Komponente | Klassifikation | Ausfallverhalten |",
                    "+|------------|----------------|------------------|",
                    "+| **Chat-Interface** | Laufzeitkritisch | Muss funktionieren |",
                    "+| **Content Studio** | Laufzeitkritisch | Muss funktionieren |",
                    "+| **Admin: \/ressourcen\/llm** | Administrativ | Kann temporär ausfallen |",
                    "+| **Admin: \/ressourcen\/collections** | Administrativ | Kann temporär ausfallen |",
                    "+| **Admin: \/ressourcen\/profil** | Administrativ | Fallback auf System-Defaults |",
                    "+| **Sync-Buttons** | Administrativ | Manueller Retry akzeptabel |",
                    "+",
                    "+**Empfehlung:** Diese Klassifikation in Operations-Dokumentation aufnehmen.",
                    "+",
                    "+#### Q1.3: Warum Qdrant Pflicht, Anthropic optional?",
                    "+",
                    "+| Dienst | Klassifikation | Begründung |",
                    "+|--------|----------------|------------|",
                    "+| **Qdrant** | Pflicht | RAG ist Kernfunktion, kein sinnvoller Fallback |",
                    "+| **Ollama** | Pflicht | Embeddings + lokale Modelle |",
                    "+| **Anthropic** | Optional | Fallback auf Ollama-Modelle möglich |",
                    "+",
                    "+**Klarstellung:** \"Optional\" bedeutet nicht \"unwichtig\", sondern \"degraded mode möglich\":",
                    "+",
                    "+```",
                    "+Anthropic down → Ollama-Modelle nutzbar (reduzierte Qualität)",
                    "+Ollama down    → System nicht funktional",
                    "+Qdrant down    → RAG nicht funktional, reiner Chat möglich",
                    "+```",
                    "+",
                    "+#### Q1.4: Fallback-Strategie (fehlt formal)",
                    "+",
                    "+**LÜCKE IDENTIFIZIERT - Neue Section erforderlich:**",
                    "+",
                    "+```php",
                    "+\/\/ Vorschlag: FallbackStrategy.php",
                    "+class FallbackStrategy",
                    "+{",
                    "+    public const STRATEGIES = [",
                    "+        'anthropic_unavailable' => [",
                    "+            'action' => 'use_ollama',",
                    "+            'notify' => true,",
                    "+            'log_level' => 'warning',",
                    "+        ],",
                    "+        'ollama_unavailable' => [",
                    "+            'action' => 'fail_gracefully',",
                    "+            'notify' => true,",
                    "+            'log_level' => 'critical',",
                    "+        ],",
                    "+        'qdrant_unavailable' => [",
                    "+            'action' => 'disable_rag',",
                    "+            'notify' => true,",
                    "+            'log_level' => 'error',",
                    "+        ],",
                    "+    ];",
                    "+}",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.2 LLM-Verwaltung und Modellannahmen",
                    "+",
                    "+#### Q2.1: Quelle für context_window und max_output_tokens?",
                    "+",
                    "+| Provider | Quelle | Zuverlässigkeit |",
                    "+|----------|--------|-----------------|",
                    "+| **Anthropic** | API Response `\/v1\/models` | Autoritativ |",
                    "+| **Ollama** | `ollama show <model>` | Lokal korrekt |",
                    "+| **Manuell** | Admin-Eingabe | Fehleranfällig |",
                    "+",
                    "+**Risiko:** Cloud-Modelle ändern sich ohne Sync.",
                    "+",
                    "+**Mitigation (ERGÄNZUNG):**",
                    "+```sql",
                    "+ALTER TABLE llm_models",
                    "+ADD COLUMN params_verified_at DATETIME,",
                    "+ADD COLUMN params_source ENUM('api', 'manual', 'estimated') DEFAULT 'estimated';",
                    "+```",
                    "+",
                    "+#### Q2.2: Veraltete Modellparameter?",
                    "+",
                    "+**Problem:** Token-Kalkulation basiert auf `max_output_tokens`.",
                    "+",
                    "+**Lösung:**",
                    "+1. **Sync-Warnung:** Wenn `last_synced_at > 7 days` → UI-Hinweis",
                    "+2. **Laufzeit-Check:** API-Fehler `max_tokens exceeded` → Parameter korrigieren",
                    "+3. **Konservative Defaults:** Bei Unsicherheit 75% von max_output_tokens",
                    "+",
                    "+```php",
                    "+\/\/ TokenCalculator.php",
                    "+public function getSafeMaxTokens(array $model): int",
                    "+{",
                    "+    $age = time() - strtotime($model['last_synced_at'] ?? '1970-01-01');",
                    "+    $confidence = $age < 86400 ? 1.0 : ($age < 604800 ? 0.9 : 0.75);",
                    "+",
                    "+    return (int) ($model['max_output_tokens'] * $confidence);",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q2.3: Warum fehlende Modelle nicht deaktivieren?",
                    "+",
                    "+**Bewusste Entscheidung:**",
                    "+",
                    "+| Szenario | Verhalten | Begründung |",
                    "+|----------|-----------|------------|",
                    "+| API temporär down | Modell bleibt aktiv | Keine false negatives |",
                    "+| Modell deprecated | `last_synced_at` veraltet | Admin-Review erforderlich |",
                    "+| Modell entfernt | Bleibt in DB | Historische Referenzen erhalten |",
                    "+",
                    "+**Empfehlung:** Neues Feld `api_status`:",
                    "+```sql",
                    "+ALTER TABLE llm_models",
                    "+ADD COLUMN api_status ENUM('available', 'deprecated', 'removed', 'unknown') DEFAULT 'unknown';",
                    "+```",
                    "+",
                    "+#### Q2.4: model_id Eindeutigkeit?",
                    "+",
                    "+**Problem:** `claude-opus-4-5-20251101` vs. `anthropic:claude-opus-4-5-20251101`",
                    "+",
                    "+**Aktuelle Lösung:** UNIQUE KEY auf `(provider, model_id)` - ausreichend.",
                    "+",
                    "+**Langfristig:** Interne UUID nicht nötig, da provider+model_id stabil.",
                    "+",
                    "+#### Q2.5: Warum ENUM statt Lookup für provider?",
                    "+",
                    "+| Option | Pro | Contra |",
                    "+|--------|-----|--------|",
                    "+| ENUM | Schnell, typsicher | Schema-Änderung bei neuem Provider |",
                    "+| Lookup-Table | Flexibel | Overhead, JOIN erforderlich |",
                    "+",
                    "+**Entscheidung:** ENUM bleibt. Neue Provider = seltenes Event, Schema-Migration akzeptabel.",
                    "+",
                    "+#### Q2.6: Ollama Tag-Wechsel (latest)?",
                    "+",
                    "+**Problem:** `mistral:latest` kann morgen anderes Modell sein.",
                    "+",
                    "+**Lösung:**",
                    "+```php",
                    "+\/\/ OllamaSyncService.php",
                    "+public function syncModels(): void",
                    "+{",
                    "+    $models = $this->ollama->list();",
                    "+",
                    "+    foreach ($models as $model) {",
                    "+        \/\/ Vollständigen Hash speichern",
                    "+        $this->repository->upsert([",
                    "+            'model_id' => $model['name'],           \/\/ \"mistral:latest\"",
                    "+            'model_digest' => $model['digest'],     \/\/ \"abc123...\"",
                    "+            'display_name' => $model['name'],",
                    "+        ]);",
                    "+    }",
                    "+}",
                    "+```",
                    "+",
                    "+**ERGÄNZUNG im Schema:**",
                    "+```sql",
                    "+ALTER TABLE llm_models",
                    "+ADD COLUMN model_digest VARCHAR(64) COMMENT 'SHA256 bei Ollama';",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.3 Sync-Logik und Ausfallsicherheit",
                    "+",
                    "+#### Q3.1: Anthropic Sync Fehler?",
                    "+",
                    "+**Fehlerbehandlung (ERGÄNZUNG):**",
                    "+",
                    "+```php",
                    "+public function syncAnthropic(): SyncResult",
                    "+{",
                    "+    try {",
                    "+        $models = $this->anthropicClient->listModels();",
                    "+        $this->processModels($models);",
                    "+",
                    "+        return SyncResult::success(count($models));",
                    "+    } catch (ApiException $e) {",
                    "+        $this->logger->error('Anthropic sync failed', ['error' => $e->getMessage()]);",
                    "+",
                    "+        return SyncResult::failed($e->getMessage());",
                    "+    }",
                    "+}",
                    "+",
                    "+\/\/ UI zeigt:",
                    "+\/\/ ✓ Sync erfolgreich: 5 Modelle aktualisiert",
                    "+\/\/ ✗ Sync fehlgeschlagen: API nicht erreichbar (letzte erfolgreiche Sync: vor 2 Tagen)",
                    "+```",
                    "+",
                    "+#### Q3.2: Logging von Metadaten-Änderungen?",
                    "+",
                    "+**LÜCKE - Audit-Trail erforderlich:**",
                    "+",
                    "+```sql",
                    "+CREATE TABLE llm_model_history (",
                    "+    id INT AUTO_INCREMENT PRIMARY KEY,",
                    "+    model_id INT NOT NULL,",
                    "+    field_name VARCHAR(50) NOT NULL,",
                    "+    old_value TEXT,",
                    "+    new_value TEXT,",
                    "+    changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,",
                    "+    changed_by VARCHAR(50) DEFAULT 'sync',",
                    "+",
                    "+    INDEX idx_model (model_id),",
                    "+    INDEX idx_changed (changed_at)",
                    "+);",
                    "+```",
                    "+",
                    "+#### Q3.3: shell_exec('ollama list') sicher?",
                    "+",
                    "+**Status:** Ja, unter Bedingungen.",
                    "+",
                    "+| Prüfung | Status |",
                    "+|---------|--------|",
                    "+| Ollama installiert | ✓ Pflichtkomponente |",
                    "+| Keine User-Eingabe in Command | ✓ Hardcoded |",
                    "+| Output-Parsing sicher | ✓ Kein eval() |",
                    "+| Timeout | ⚠️ FEHLT |",
                    "+",
                    "+**ERGÄNZUNG:**",
                    "+```php",
                    "+\/\/ Statt shell_exec():",
                    "+$process = proc_open(['ollama', 'list'], $descriptors, $pipes);",
                    "+stream_set_timeout($pipes[1], 10); \/\/ 10s timeout",
                    "+```",
                    "+",
                    "+#### Q3.4: Sync vs. laufende Sessions?",
                    "+",
                    "+**Problem:** Session nutzt `model_id`, Sync ändert Metadaten.",
                    "+",
                    "+**Analyse:**",
                    "+- `model_id` selbst ändert sich nicht",
                    "+- `max_output_tokens` Änderung → nächster Request nutzt neue Werte",
                    "+- **Kein Konsistenzproblem**, da Session-Settings separat gespeichert",
                    "+",
                    "+---",
                    "+",
                    "+### 11.4 Dropdown-Partials und UI-Semantik",
                    "+",
                    "+#### Q4.1: Warum feste Optgroups?",
                    "+",
                    "+**Pragmatische Entscheidung:**",
                    "+",
                    "+```php",
                    "+\/\/ Aktuell:",
                    "+<optgroup label=\"Cloud\">",
                    "+<optgroup label=\"Lokal\">",
                    "+",
                    "+\/\/ Datengetrieben wäre:",
                    "+<optgroup label=\"<?= $providerLabels[$provider] ?>\">",
                    "+```",
                    "+",
                    "+**Entscheidung:** Hardcoded bleibt, weil:",
                    "+1. Nur 2 relevante Kategorien",
                    "+2. Sortierung Cloud→Lokal ist gewollt",
                    "+3. Kein Business Case für dynamische Gruppen",
                    "+",
                    "+#### Q4.2: Dritter Provider-Typ?",
                    "+",
                    "+**Bei Bedarf erweitern:**",
                    "+```php",
                    "+const PROVIDER_GROUPS = [",
                    "+    'cloud' => ['anthropic', 'openai', 'google'],",
                    "+    'local' => ['ollama'],",
                    "+    'private' => ['custom'],  \/\/ Zukünftig",
                    "+];",
                    "+```",
                    "+",
                    "+#### Q4.3: Leeres $models?",
                    "+",
                    "+**Fehlerbehandlung (ERGÄNZUNG):**",
                    "+```php",
                    "+\/\/ model-select.php",
                    "+<?php if (empty($models)): ?>",
                    "+    <select disabled class=\"form-select form-select--error\">",
                    "+        <option>Keine Modelle verfügbar - Sync erforderlich<\/option>",
                    "+    <\/select>",
                    "+<?php else: ?>",
                    "+    \/\/ Normaler Select",
                    "+<?php endif; ?>",
                    "+```",
                    "+",
                    "+#### Q4.4: Warum id=\"model\" hardcoded?",
                    "+",
                    "+**Problem:** Mehrere Instanzen auf einer Seite → ID-Konflikt.",
                    "+",
                    "+**KORREKTUR:**",
                    "+```php",
                    "+$id = $id ?? 'model_' . uniqid();",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.5 Collection-Verwaltung und RAG-Konsistenz",
                    "+",
                    "+#### Q5.1: Verantwortung für vector_size?",
                    "+",
                    "+**Verantwortlichkeiten:**",
                    "+",
                    "+| Aktion | Verantwortlich | Prüfung |",
                    "+|--------|----------------|---------|",
                    "+| Collection erstellen | Pipeline\/Admin | Muss Embedding-Modell angeben |",
                    "+| Embedding-Modell wechseln | Admin | Re-Embedding erforderlich |",
                    "+| vector_size validieren | Sync | Qdrant ist Source of Truth |",
                    "+",
                    "+#### Q5.2: Inkompatible Embeddings bei Multi-Select?",
                    "+",
                    "+**KRITISCHE LÜCKE:**",
                    "+",
                    "+```php",
                    "+\/\/ CollectionValidator.php (NEU)",
                    "+public function validateSelection(array $collectionIds): ValidationResult",
                    "+{",
                    "+    $collections = $this->repository->findByIds($collectionIds);",
                    "+    $vectorSizes = array_unique(array_column($collections, 'vector_size'));",
                    "+",
                    "+    if (count($vectorSizes) > 1) {",
                    "+        return ValidationResult::error(",
                    "+            'Inkompatible Collections: Unterschiedliche Vektorgrößen'",
                    "+        );",
                    "+    }",
                    "+",
                    "+    return ValidationResult::ok();",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q5.3: embedding_model nur dokumentativ?",
                    "+",
                    "+**Aktuell:** Ja, rein informativ.",
                    "+",
                    "+**ERGÄNZUNG:** Validierung bei Suche:",
                    "+```php",
                    "+\/\/ QdrantService::search()",
                    "+public function search(array $collectionIds, string $query): array",
                    "+{",
                    "+    $embedding = $this->embedder->embed($query);",
                    "+",
                    "+    \/\/ Prüfen ob Embedding-Dimension passt",
                    "+    foreach ($collectionIds as $cid) {",
                    "+        $col = $this->repository->find($cid);",
                    "+        if ($col['vector_size'] !== count($embedding)) {",
                    "+            throw new DimensionMismatchException($cid, $col['vector_size'], count($embedding));",
                    "+        }",
                    "+    }",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q5.4: Versteckte Qdrant-Collections?",
                    "+",
                    "+**Lösung über is_active:**",
                    "+```sql",
                    "+-- Qdrant hat 10 Collections, 7 sollen sichtbar sein",
                    "+UPDATE rag_collections SET is_active = FALSE WHERE collection_id IN ('internal_test', 'backup_2024');",
                    "+```",
                    "+",
                    "+#### Q5.5: System vs. Benutzer-Collections?",
                    "+",
                    "+**source_type ist ausreichend:**",
                    "+```",
                    "+source_type = 'system'   → Interne Collections (entities, taxonomy)",
                    "+source_type = 'manual'   → Benutzer-relevante",
                    "+source_type = 'nextcloud'→ Dokumente",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.6 Python-Pipeline-Kopplung",
                    "+",
                    "+#### Q6.1: Warum direkte DB in Python, aber MCP sonst Pflicht?",
                    "+",
                    "+**Klarstellung:**",
                    "+",
                    "+| Kontext | Zugriff | Begründung |",
                    "+|---------|---------|------------|",
                    "+| Claude Code (CLI) | MCP | Audit, Sicherheit |",
                    "+| PHP Application | PDO | Standard-Pattern |",
                    "+| Python Pipeline | PyMySQL | Batch-Performance |",
                    "+",
                    "+**MCP-Pflicht gilt für Claude Code, nicht für Anwendungscode.**",
                    "+",
                    "+#### Q6.2: Schema-Drift erkennen?",
                    "+",
                    "+**Lösung:**",
                    "+```python",
                    "+# schema_check.py",
                    "+EXPECTED_COLUMNS = {",
                    "+    'content_orders': ['id', 'contracts', 'structures', ...],",
                    "+}",
                    "+",
                    "+def verify_schema():",
                    "+    for table, columns in EXPECTED_COLUMNS.items():",
                    "+        actual = get_columns(table)",
                    "+        missing = set(columns) - set(actual)",
                    "+        if missing:",
                    "+            raise SchemaError(f\"Missing columns in {table}: {missing}\")",
                    "+```",
                    "+",
                    "+#### Q6.3: Concurrent Writes?",
                    "+",
                    "+**Aktuell:** Kein explizites Locking.",
                    "+",
                    "+**Analyse:** Unkritisch, weil:",
                    "+1. Admin-UI schreibt Metadaten (selten)",
                    "+2. Pipeline schreibt Content (häufig)",
                    "+3. Keine Überschneidung der Schreibziele",
                    "+",
                    "+**Bei Bedarf:** Row-Level Locking für kritische Updates.",
                    "+",
                    "+---",
                    "+",
                    "+### 11.7 User-Präferenzen und Werte-Hierarchie",
                    "+",
                    "+#### Q7.1: JSON-Validierung vor Speicherung?",
                    "+",
                    "+**LÜCKE - Validierung erforderlich:**",
                    "+",
                    "+```php",
                    "+public function update(int $userId, array $data): void",
                    "+{",
                    "+    \/\/ Validierung",
                    "+    if (isset($data['default_collections'])) {",
                    "+        $decoded = json_decode($data['default_collections'], true);",
                    "+        if (json_last_error() !== JSON_ERROR_NONE) {",
                    "+            throw new ValidationException('Invalid JSON for collections');",
                    "+        }",
                    "+        \/\/ Existenz prüfen",
                    "+        foreach ($decoded as $id) {",
                    "+            if (!$this->collectionRepository->exists($id)) {",
                    "+                throw new ValidationException(\"Collection not found: $id\");",
                    "+            }",
                    "+        }",
                    "+    }",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q7.2: Alte Sessions vs. neue Defaults?",
                    "+",
                    "+**Kein Problem durch Design:**",
                    "+```",
                    "+Session speichert eigene Werte → unabhängig von Defaults",
                    "+Defaults gelten nur für NEUE Sessions",
                    "+```",
                    "+",
                    "+#### Q7.3: Defaults pro Modul?",
                    "+",
                    "+**Aktuell:** Global (ein Satz Defaults für Chat + Content).",
                    "+",
                    "+**Zukunftsoption:**",
                    "+```sql",
                    "+ALTER TABLE user_preferences",
                    "+ADD COLUMN scope ENUM('global', 'chat', 'content') DEFAULT 'global';",
                    "+```",
                    "+",
                    "+#### Q7.4: Korrupter Preferences-Datensatz?",
                    "+",
                    "+**Fallback in Repository:**",
                    "+```php",
                    "+public function getForUser(int $userId): array",
                    "+{",
                    "+    $row = $this->db->fetch(\"SELECT * FROM user_preferences WHERE user_id = ?\", [$userId]);",
                    "+",
                    "+    return $row ?: $this->createDefaultPreferences($userId);",
                    "+}",
                    "+",
                    "+private function createDefaultPreferences(int $userId): array",
                    "+{",
                    "+    $defaults = [...]; \/\/ System-Defaults",
                    "+    $this->db->insert('user_preferences', ['user_id' => $userId] + $defaults);",
                    "+    return $defaults;",
                    "+}",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.8 Presets, Temperature und Tokens",
                    "+",
                    "+#### Q8.1: Temperature 0.0-1.0 Begrenzung?",
                    "+",
                    "+**Begründung:**",
                    "+- Anthropic: 0.0-1.0 (offiziell)",
                    "+- Ollama: modellabhängig, 1.0 sicherer Maximalwert",
                    "+- **Vereinheitlichung > Flexibilität**",
                    "+",
                    "+#### Q8.2: Token-Stufen prozentual vs. semantisch?",
                    "+",
                    "+**Prozentual gewählt weil:**",
                    "+- Modell-unabhängig",
                    "+- Keine Pflege semantischer Definitionen",
                    "+- User versteht \"50% vom Maximum\"",
                    "+",
                    "+**Alternative (nicht gewählt):**",
                    "+```",
                    "+Kurz:   ~500 Tokens  → \"Ein Absatz\"",
                    "+Mittel: ~2000 Tokens → \"Eine Seite\"",
                    "+Lang:   ~4000 Tokens → \"Mehrere Seiten\"",
                    "+```",
                    "+",
                    "+#### Q8.3: Abweichung von Preset?",
                    "+",
                    "+**preset_id wird auf 'custom' gesetzt:**",
                    "+```php",
                    "+\/\/ Wenn Temperature manuell geändert:",
                    "+if ($submittedTemp !== $preset['temperature']) {",
                    "+    $data['preset_id'] = null; \/\/ oder 'custom'",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q8.4: Parallelität default_preset_id + default_temperature?",
                    "+",
                    "+**INKONSISTENZ - Klarstellung erforderlich:**",
                    "+",
                    "+| Priorität | Quelle | Wann |",
                    "+|-----------|--------|------|",
                    "+| 1 | Session-Wert | Wenn vorhanden |",
                    "+| 2 | Preset → temperature | Wenn preset_id gesetzt |",
                    "+| 3 | default_temperature | Wenn kein Preset |",
                    "+",
                    "+**Empfehlung:** Nur `default_preset_id` speichern, Temperature daraus ableiten.",
                    "+",
                    "+---",
                    "+",
                    "+### 11.9 JavaScript-Steuerung",
                    "+",
                    "+#### Q9.1: slug vs. id für Presets?",
                    "+",
                    "+**Inkonsistenz bestätigt:**",
                    "+```js",
                    "+\/\/ Aktuell gemischt:",
                    "+data-preset-id=\"1\"",
                    "+data-preset-slug=\"precise\"",
                    "+```",
                    "+",
                    "+**KORREKTUR:** Nur `id` verwenden, `slug` nur für CSS-Klassen.",
                    "+",
                    "+#### Q9.2: API-Fehler bei Modellwechsel?",
                    "+",
                    "+**ERGÄNZUNG:**",
                    "+```js",
                    "+fetch(`\/api\/v1\/llm\/${modelId}`)",
                    "+    .then(r => {",
                    "+        if (!r.ok) throw new Error('API Error');",
                    "+        return r.json();",
                    "+    })",
                    "+    .catch(err => {",
                    "+        console.error('Model info not available');",
                    "+        \/\/ Fallback: Aktuelle Werte behalten",
                    "+        showToast('Modellinfo nicht verfügbar - Standardwerte werden verwendet');",
                    "+    });",
                    "+```",
                    "+",
                    "+#### Q9.3: Race Conditions?",
                    "+",
                    "+**ERGÄNZUNG - Debounce + State Lock:**",
                    "+```js",
                    "+let isUpdating = false;",
                    "+",
                    "+modelSelect.addEventListener('change', debounce(async function() {",
                    "+    if (isUpdating) return;",
                    "+    isUpdating = true;",
                    "+",
                    "+    try {",
                    "+        await updateTokenOptions(this.value);",
                    "+    } finally {",
                    "+        isUpdating = false;",
                    "+    }",
                    "+}, 300));",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.10 Contracts: Migration und Semantik",
                    "+",
                    "+#### Q10.1: AND-Verknüpfung ohne Konfliktauflösung?",
                    "+",
                    "+**Design-Entscheidung:**",
                    "+- Contracts sind **additiv** (alle müssen erfüllt sein)",
                    "+- Widersprüche = Admin-Fehler, nicht System-Problem",
                    "+",
                    "+**Empfehlung:** Validierung bei Speicherung:",
                    "+```php",
                    "+public function validateContractCombination(array $contractIds): bool",
                    "+{",
                    "+    \/\/ Prüfen auf bekannte Konflikte",
                    "+    \/\/ z.B. \"Kurz & prägnant\" + \"Ausführlich & detailliert\"",
                    "+    \/\/ → Warnung, aber kein Fehler",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q10.2: Widersprüchliche Contracts?",
                    "+",
                    "+**Keine technische Lösung, sondern Governance:**",
                    "+1. Admin-Schulung",
                    "+2. Contract-Beschreibungen klar formulieren",
                    "+3. Optionale Validierungsregeln in `content_config`",
                    "+",
                    "+#### Q10.3: Ausführungsreihenfolge?",
                    "+",
                    "+**Aktuell:** Keine definierte Reihenfolge.",
                    "+",
                    "+**Bei Bedarf:**",
                    "+```sql",
                    "+ALTER TABLE content_config",
                    "+ADD COLUMN priority INT DEFAULT 0;",
                    "+```",
                    "+",
                    "+#### Q10.4: Auto-Select nur bei Contracts?",
                    "+",
                    "+**Semantische Unterscheidung:**",
                    "+- **Contracts:** Qualitätsstandards → sollten immer gelten",
                    "+- **Structures:** Formatvorlagen → bewusste Wahl",
                    "+",
                    "+#### Q10.5: Migration Rollback?",
                    "+",
                    "+**Rollback-Script (ERGÄNZUNG):**",
                    "+```sql",
                    "+-- Rollback von JSON zu INT",
                    "+ALTER TABLE content_orders",
                    "+ADD COLUMN contract_id_old INT;",
                    "+",
                    "+UPDATE content_orders",
                    "+SET contract_id_old = JSON_EXTRACT(contracts, '$[0]')",
                    "+WHERE JSON_LENGTH(contracts) > 0;",
                    "+",
                    "+ALTER TABLE content_orders",
                    "+DROP COLUMN contracts;",
                    "+",
                    "+ALTER TABLE content_orders",
                    "+CHANGE contract_id_old contract_id INT;",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.11 Structures und Template-Fusion",
                    "+",
                    "+#### Q11.1: Was bedeutet Template-Fusion?",
                    "+",
                    "+**Definition:**",
                    "+```",
                    "+Structure A: \"Verwende Markdown-Überschriften\"",
                    "+Structure B: \"Füge Zusammenfassung am Anfang ein\"",
                    "+",
                    "+Fusion: Beide Anweisungen werden im Prompt kombiniert",
                    "+```",
                    "+",
                    "+**Kein intelligentes Merging, sondern Konkatenation.**",
                    "+",
                    "+#### Q11.2: Konfliktmodell?",
                    "+",
                    "+**Keines - bewusste Entscheidung:**",
                    "+- Structures sind Vorschläge, keine harten Regeln",
                    "+- LLM interpretiert kombinierte Anweisungen",
                    "+- Bei echten Konflikten: Admin korrigiert Auswahl",
                    "+",
                    "+#### Q11.3: Structures im Chat sinnvoll?",
                    "+",
                    "+**Ja, für:**",
                    "+- \"Antworte immer mit Bullet Points\"",
                    "+- \"Verwende Codeblöcke für Beispiele\"",
                    "+- \"Strukturiere Antworten mit Überschriften\"",
                    "+",
                    "+---",
                    "+",
                    "+### 11.12 Migration und Betrieb",
                    "+",
                    "+#### Q12.1: Migrationsfahrplan?",
                    "+",
                    "+**Phase-Dependencies (ERGÄNZUNG):**",
                    "+",
                    "+```",
                    "+Phase 1 (DB-Schema) ──────────┐",
                    "+                              ▼",
                    "+Phase 2 (Repositories) ───────┤",
                    "+                              ▼",
                    "+Phase 3 (Admin-Seiten) ◄──────┤ (kann parallel)",
                    "+                              ▼",
                    "+Phase 4 (Partials) ───────────┤",
                    "+                              ▼",
                    "+Phase 5 (Integration) ────────┤",
                    "+                              ▼",
                    "+Phase 6 (Testing) ────────────┘",
                    "+",
                    "+BLOCKING: Phase 1 muss vor allen anderen abgeschlossen sein",
                    "+PARALLEL: Phase 3 + 4 können gleichzeitig",
                    "+```",
                    "+",
                    "+#### Q12.2: Halb-migrierter Zustand erkennen?",
                    "+",
                    "+**ERGÄNZUNG - Migration-Status-Tabelle:**",
                    "+```sql",
                    "+CREATE TABLE migration_status (",
                    "+    id INT AUTO_INCREMENT PRIMARY KEY,",
                    "+    migration_name VARCHAR(100) NOT NULL UNIQUE,",
                    "+    phase INT NOT NULL,",
                    "+    status ENUM('pending', 'running', 'completed', 'failed') DEFAULT 'pending',",
                    "+    started_at DATETIME,",
                    "+    completed_at DATETIME,",
                    "+    error_message TEXT",
                    "+);",
                    "+```",
                    "+",
                    "+#### Q12.3: Wann alte Felder nicht mehr lesen?",
                    "+",
                    "+**Migrations-Contract:**",
                    "+```",
                    "+Phase 1.7-1.8: contract_id → contracts (DB)",
                    "+Phase 10.2:    ContentController liest NUR contracts",
                    "+Phase 12.15:   Test bestätigt: contract_id nicht mehr genutzt",
                    "+              → contract_id kann entfernt werden (optional)",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.13 Governance und Verträge",
                    "+",
                    "+#### Q13.1: Normative Definition von Contracts\/Presets\/Structures?",
                    "+",
                    "+**LÜCKE - Dokumentation erforderlich:**",
                    "+",
                    "+```markdown",
                    "+# \/docs\/governance\/content-config-types.md",
                    "+",
                    "+## Contract",
                    "+- **Zweck:** Verbindliche Qualitätsregeln für Content",
                    "+- **Enforcement:** Critique-Phase prüft, Chat verwendet als Guideline",
                    "+- **Beispiel:** \"DSGVO-konform\", \"Akademischer Stil\"",
                    "+",
                    "+## Structure",
                    "+- **Zweck:** Formatvorlage für Ausgabe",
                    "+- **Enforcement:** Template für Generierung, optional im Chat",
                    "+- **Beispiel:** \"Blog-Artikel\", \"FAQ-Format\"",
                    "+",
                    "+## Preset",
                    "+- **Zweck:** Vordefinierte LLM-Parameter-Kombination",
                    "+- **Enforcement:** Setzt Temperature + Token automatisch",
                    "+- **Beispiel:** \"Präzise\", \"Kreativ\"",
                    "+```",
                    "+",
                    "+#### Q13.2: Widersprüchliche Defaults verhindern?",
                    "+",
                    "+**Aktuell:** Keine Validierung.",
                    "+",
                    "+**Empfehlung:**",
                    "+```php",
                    "+\/\/ PreferencesValidator.php",
                    "+public function validate(array $prefs): ValidationResult",
                    "+{",
                    "+    $errors = [];",
                    "+",
                    "+    \/\/ Prüfen: Default-Modell aktiv?",
                    "+    if ($prefs['default_model_id']) {",
                    "+        $model = $this->modelRepo->find($prefs['default_model_id']);",
                    "+        if (!$model || !$model['is_active']) {",
                    "+            $errors[] = 'Default-Modell ist nicht aktiv';",
                    "+        }",
                    "+    }",
                    "+",
                    "+    \/\/ Prüfen: Default-Collections existieren?",
                    "+    $collections = json_decode($prefs['default_collections'], true);",
                    "+    foreach ($collections as $id) {",
                    "+        if (!$this->collectionRepo->isActive($id)) {",
                    "+            $errors[] = \"Collection '$id' nicht verfügbar\";",
                    "+        }",
                    "+    }",
                    "+",
                    "+    return $errors ? ValidationResult::failed($errors) : ValidationResult::ok();",
                    "+}",
                    "+```",
                    "+",
                    "+#### Q13.3: System-Divergenz langfristig verhindern?",
                    "+",
                    "+**Maßnahmen:**",
                    "+",
                    "+1. **Architektur-Contracts:** Dieses Dokument als Referenz",
                    "+2. **Automated Tests:** Partials werden von beiden Views verwendet",
                    "+3. **Lint-Rules:** Verbieten direkter SQL außerhalb Repositories",
                    "+4. **Review-Prozess:** Änderungen an Partials reviewen",
                    "+",
                    "+```php",
                    "+\/\/ tests\/ArchitectureTest.php",
                    "+public function test_all_views_use_partials(): void",
                    "+{",
                    "+    $views = glob(VIEW_PATH . '\/{chat,content}\/*.php', GLOB_BRACE);",
                    "+",
                    "+    foreach ($views as $view) {",
                    "+        $content = file_get_contents($view);",
                    "+",
                    "+        \/\/ Kein direktes <select name=\"model\">",
                    "+        $this->assertStringNotContainsString(",
                    "+            '<select name=\"model\"',",
                    "+            $content,",
                    "+            \"View $view should use model-select.php partial\"",
                    "+        );",
                    "+    }",
                    "+}",
                    "+```",
                    "+",
                    "+---",
                    "+",
                    "+### 11.14 Zusammenfassung: Offene Aktionen",
                    "+",
                    "+| # | Aktion | Priorität | Status |",
                    "+|---|--------|-----------|--------|",
                    "+| 1 | FallbackStrategy-Klasse erstellen | Hoch | TODO |",
                    "+| 2 | llm_models: params_source, model_digest hinzufügen | Mittel | TODO |",
                    "+| 3 | llm_model_history Audit-Table erstellen | Niedrig | TODO |",
                    "+| 4 | CollectionValidator für Embedding-Kompatibilität | Hoch | TODO |",
                    "+| 5 | JSON-Validierung in UserPreferencesRepository | Hoch | TODO |",
                    "+| 6 | Debounce + State Lock in JS | Mittel | TODO |",
                    "+| 7 | Migration-Status-Tabelle erstellen | Hoch | TODO |",
                    "+| 8 | Governance-Dokumentation erstellen | Mittel | TODO |",
                    "+| 9 | ArchitectureTest für Partial-Nutzung | Niedrig | TODO |",
                    "+| 10 | Preset-ID vs. Slug Inkonsistenz beheben | Niedrig | TODO |"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →