From 6c95f6ccb41bedff1a6046ab749036df9721d116 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 11 Jan 2026 13:07:11 +0100 Subject: [PATCH] concepts updated --- ...ation_ai-call-looping-architecture_done.md | 1514 +++++++++++++++++ .../20260111 doc_session_handling_analysis.md | 250 +++ ...doc_session_handling_summary_operations.md | 94 + .../doc_userregistration_ui_adaptations.md | 793 +++++++++ 4 files changed, 2651 insertions(+) create mode 100644 implementation/implementation_ai-call-looping-architecture_done.md create mode 100644 reviews/20260111 doc_session_handling_analysis.md create mode 100644 reviews/20260111 doc_session_handling_summary_operations.md create mode 100644 ui_nyla/feature-userregistration/doc_userregistration_ui_adaptations.md diff --git a/implementation/implementation_ai-call-looping-architecture_done.md b/implementation/implementation_ai-call-looping-architecture_done.md new file mode 100644 index 0000000..cfce36c --- /dev/null +++ b/implementation/implementation_ai-call-looping-architecture_done.md @@ -0,0 +1,1514 @@ +# AI-Call Looping Architektur + +## Übersicht + +Dieses Dokument beschreibt die zentrale Architektur für **AI-Calls mit Looping-Funktionalität** nach cut-response. Es dokumentiert die verschiedenen Use Cases, ihre Looping-Mechanismen, Continuation-Prompt-Strukturen und die Unterschiede zwischen ihnen. + +**Fokus**: AI-Loop-Call mit automatischen Iterationen nach unvollständigen JSON-Responses. + +**Ziel**: Übersichtliche Grundlage für das Aufräumen und Vereinheitlichen der Looping-Logik. + +--- + +## Use Cases Summary + +**Alle registrierten Use Cases**: +1. `section_content` - Section-Content-Generierung ✅ **Looping** +2. `chapter_structure` - Chapter-Struktur-Generierung ✅ **Looping** +3. `code_structure` - Code-Struktur-Generierung ✅ **Looping** +4. `code_content` - Code-Content-Generierung ✅ **Looping** + +**Vergleichstabelle**: + +| Use Case | Looping | Direct Return | Accumulation | Extraction | Prompt Builder | Template Konsistenz | Prompt Builder Parameter | +|----------|---------|---------------|--------------|------------|----------------|---------------------|------------------------| +| `section_content` | ✅ | ✅ | ❌ | ❌ | `buildSectionPromptWithContinuation()` | ✅ IDENTISCH | `section`, `contentParts`, `userPrompt`, `generationHint`, `allSections`, `sectionIndex`, `isAggregation`, `services` | +| `chapter_structure` | ✅ | ✅ | ❌ | ❌ | `buildChapterStructurePromptWithContinuation()` | ✅ IDENTISCH | `userPrompt`, `contentParts`, `outputFormat`, `services` | +| `code_structure` | ✅ | ✅ | ❌ | ❌ | ❌ **FEHLT** | ❌ PROBLEM | ❌ **FEHLT** | +| `code_content` | ✅ | ✅ | ✅ | ❌ | `buildCodeContentPromptWithContinuation()` | ✅ IDENTISCH | `filename`, `fileType`, `functions`, `classes`, `dependencies`, `metadata`, `userPrompt`, `contentParts`, `contextInfo`, `services` | + +**Hinweis zu Prompt-Builder-Parametern**: +- Alle Prompt-Builder akzeptieren zusätzlich: `continuationContext: Dict[str, Any]` (wird automatisch übergeben) +- Aktuell verwenden alle Builder `**kwargs` für Parameter-Extraktion +- **Ziel-Architektur**: Explizite Parameter statt `**kwargs` (siehe "Ziel-Architektur" Abschnitt) + +--- + +## Kern-System: `callAiWithLooping` + +**Location**: `gateway/modules/services/serviceAi/subAiCallLooping.py` + +**Zentrale Funktion**: `AiCallLooper.callAiWithLooping()` + +**Funktionsweise**: +1. **Erste Iteration**: Verwendet den initialen Prompt +2. **Weitere Iterationen (nach cut-response)**: + - Prüft, ob JSON vollständig ist (String-basierte und Parsing-basierte Checks) + - Wenn unvollständig: Baut Continuation-Prompt mit `promptBuilder` + - Merged JSON-Fragmente mit Overlap-Detection + - Wiederholt bis JSON vollständig oder kein Overlap gefunden + +**Loop-Bedingungen**: +- **Stoppt wenn**: JSON vollständig, kein Overlap gefunden, Fehler, max Iterationen erreicht +- **Weiter wenn**: JSON unvollständig, Overlap gefunden, Parsing fehlgeschlagen + +--- + +## Ziel-Architektur: Harmonisiertes AI-Looping-System + +### 1. Continuation-Context-Building + +**Aktueller Zustand**: +- `buildContinuationContext(allSections, lastRawResponse, useCaseId)` extrahiert `template_structure` aus dem Response +- `template_structure` wird aus dem Response abgeleitet, nicht explizit übergeben + +**⚠️ KRITISCH: Template-Struktur NICHT extrahieren** +- **Template-Struktur wird NICHT aus dem initialen Prompt extrahiert** +- **Template-Struktur wird NICHT aus dem Response extrahiert** +- **Template-Struktur ist ein expliziter Parameter**, der beim Erstellen des initialen Prompts erstellt wird +- **Derselbe Template-Struktur-Parameter** wird dann für den Continuation-Prompt verwendet +- Dies garantiert **exakte Identität** zwischen initialem und Continuation-Prompt + +**Ziel-Architektur**: +```python +from pydantic import BaseModel +from typing import Optional, List, Dict, Any + +class ContinuationContext(BaseModel): + """Pydantic model for continuation context information.""" + section_count: int + delivered_summary: str + cut_off_element: Optional[str] = None + element_before_cutoff: Optional[str] = None + template_structure: Optional[str] = None # JSON structure template from initial prompt + last_complete_part: Optional[str] = None + incomplete_part: Optional[str] = None + structure_context: Optional[str] = None + last_raw_json: Optional[str] = None + +def buildContinuationContext( + allSections: List[Dict[str, Any]], + lastRawResponse: Optional[str] = None, + useCaseId: Optional[str] = None, + templateStructure: Optional[str] = None # NEU: Expliziter Parameter +) -> ContinuationContext: + """ + Build context information from accumulated sections for continuation prompt. + + Args: + allSections: List of ALL sections accumulated across ALL iterations + lastRawResponse: Raw JSON response from last iteration (can be broken/incomplete) + useCaseId: Optional use case ID to determine expected JSON structure + templateStructure: JSON structure template from initial prompt (MUST be identical) + + Returns: + ContinuationContext: Pydantic model with all continuation context information + """ + # ... implementation ... + return ContinuationContext( + section_count=len(allSections), + delivered_summary=delivered_summary, + cut_off_element=cut_off_element, + element_before_cutoff=element_before_cutoff, + template_structure=templateStructure, # Explizit übergeben + last_complete_part=last_complete_part, + incomplete_part=incomplete_part, + structure_context=structure_context, + last_raw_json=lastRawResponse + ) +``` + +**Vorteile**: +- ✅ **Typsicherheit**: Pydantic-Modell statt Dict für bessere IDE-Support und Validierung +- ✅ **Konsistenz**: Gleiche Architektur wie andere Modelle im System (z.B. Prompt-Args-Dataclasses) +- ✅ **Validierung**: Automatische Validierung der Felder durch Pydantic +- ✅ **Dokumentation**: Explizite Feld-Definitionen statt Dict-Keys +- ✅ Initialer und iterativer Prompt haben garantiert die gleiche Struktur +- ✅ Keine Abhängigkeit von Response-Parsing für Template-Extraktion +- ✅ Explizite Kontrolle über Template-Struktur + +**Erforderliche Änderungen**: +1. `ContinuationContext` Pydantic-Modell erstellen (`jsonUtils.py` oder separate Model-Datei) +2. `buildContinuationContext()` erweitern um `templateStructure` Parameter +3. `buildContinuationContext()` Rückgabetyp ändern von `Dict[str, Any]` zu `ContinuationContext` +4. Alle Aufrufe von `buildContinuationContext()` anpassen, um `templateStructure` zu übergeben +5. Alle Verwendungen von `continuationContext` anpassen (Dict-Zugriff → Attribut-Zugriff) + - `continuationContext.get("delivered_summary")` → `continuationContext.delivered_summary` + - `continuationContext.get("cut_off_element")` → `continuationContext.cut_off_element` + - etc. + +--- + +### 2. Prompt-Builder-Architektur + +**Aktueller Zustand**: +- Alle Prompt-Builder verwenden `**kwargs` für Parameter +- Parameter werden aus `kwargs` extrahiert (nicht typsicher) +- 3 identische Instanzen von `buildSectionPromptWithContinuation()` in `subStructureFilling.py` + +**Ziel-Architektur**: + +**Entscheidungen**: +- ✅ **BasePrompt als Parameter übergeben**: Saubere Lösung, garantiert Identität mit initialem Prompt +- ✅ **Dataclasses für Parameter-Strukturen**: Ja, für Typsicherheit und bessere Code-Qualität +- ✅ **Schrittweise Migration**: Von `**kwargs` zu expliziten Parametern mit klarem Plan +- ✅ **Einheitliche Signatur**: Alle Builder auf die gleiche Signatur umstellen + +**Einheitliche Signatur mit Dataclasses**: +```python +from typing import Protocol +from dataclasses import dataclass + +# Beispiel: Section Content Prompt Args +@dataclass +class SectionPromptArgs: + """Type-safe arguments for section content prompt builder.""" + section: Dict[str, Any] + contentParts: List[ContentPart] + userPrompt: str + generationHint: str + allSections: List[Dict[str, Any]] + sectionIndex: int + isAggregation: bool + services: Any + +# Beispiel: Chapter Structure Prompt Args +@dataclass +class ChapterStructurePromptArgs: + """Type-safe arguments for chapter structure prompt builder.""" + userPrompt: str + contentParts: List[ContentPart] + outputFormat: str + services: Any + +# Einheitliche Signatur für ALLE Prompt-Builder: +class PromptBuilder(Protocol): + """Protocol for prompt builders with unified signature.""" + async def __call__( + self, + continuationContext: ContinuationContext, # Pydantic-Modell (NICHT Optional - wird immer übergeben) + templateStructure: str, # Expliziter Parameter (aus promptArgs) - NICHT extrahiert, sondern als Parameter übergeben + basePrompt: str, # Initialer Prompt (als Parameter übergeben) - NICHT regeneriert, sondern als Parameter übergeben + promptArgs: Any # Dataclass mit use-case-spezifischen Parametern + ) -> str: + """Build continuation prompt with unified signature.""" + ... +``` + +**Vorteile**: +- ✅ Typsicherheit durch Dataclasses +- ✅ Einheitliche Signatur für alle Builder +- ✅ BasePrompt als Parameter garantiert Identität +- ✅ Bessere IDE-Support und Dokumentation + +**Aufruf-Stelle** (`subAiCallLooping.py` Zeile 124): +```python +# Aktueller Aufruf: +iterationPrompt = await promptBuilder(continuationContext=continuationContext, **promptArgs) +``` + +**Aktuelle Signaturen (INKONSISTENT)**: +- `buildSectionPromptWithContinuation(continuationContext: Dict[str, Any], **kwargs)` → `continuationContext` NICHT Optional +- `buildChapterStructurePromptWithContinuation(continuationContext: Optional[Dict[str, Any]] = None, **kwargs)` → `continuationContext` Optional +- `buildCodeContentPromptWithContinuation(continuationContext: Optional[Dict[str, Any]] = None, **kwargs)` → `continuationContext` Optional + +**Problem**: Inkonsistente Signaturen! Einige Builder haben `Optional`, andere nicht. + +**Parameter-Übergabe-Unterschiede (AKTUELL)**: + +| Use Case | Parameter-Übergabe | Beschreibung | +|----------|-------------------|--------------| +| `section_content` | Alle `promptArgs` werden übergeben | `section`, `contentParts`, `userPrompt`, `generationHint`, `allSections`, `sectionIndex`, `isAggregation`, `services` | +| `chapter_structure` | Alle `promptArgs` werden übergeben | `userPrompt`, `contentParts`, `outputFormat`, `services` | +| `code_content` | Alle `promptArgs` werden übergeben | `filename`, `fileType`, `functions`, `classes`, `dependencies`, `metadata`, `userPrompt`, `contentParts`, `contextInfo`, `services` | +| Andere Use Cases (historisch) | Gefilterte `promptArgs` | Nur: `outputFormat`, `userPrompt`, `title`, `extracted_content`, `services` | + +**Hinweis**: Die historische Unterscheidung zwischen `section_content` (alle Parameter) und anderen Use Cases (gefilterte Parameter) wurde bereits aufgehoben. Alle aktuellen Use Cases verwenden alle `promptArgs`. + +**⚠️ KRITISCH: Template-Struktur-Quelle** + +**WICHTIG**: Template-Struktur wird **NICHT aus dem initialen Prompt extrahiert**. Stattdessen: + +| Use Case | Template-Erstellung | Beschreibung | +|----------|---------------------|--------------| +| `section_content` | Wird erstellt via `_getContentStructureExample(contentType)` | Template wird beim Erstellen des initialen Prompts **erstellt** (nicht extrahiert) | +| `chapter_structure` | Wird erstellt aus "EXAMPLE STRUCTURE" Struktur | Template wird beim Erstellen des initialen Prompts **erstellt** (nicht extrahiert) | +| `code_content` | Wird erstellt aus JSON-Struktur-Definition | Template wird beim Erstellen des initialen Prompts **erstellt** (nicht extrahiert) | +| `code_structure` | Wird erstellt aus "## FILE STRUCTURE REQUIREMENTS" | Template wird beim Erstellen des initialen Prompts **erstellt** (nicht extrahiert) | + +**Ziel-Architektur**: +- **Template wird beim Erstellen des initialen Prompts erstellt** (nicht extrahiert!) +- **Template wird als separater Parameter zurückgegeben**: `(prompt, templateStructure)` +- **Template wird in `promptArgs` gespeichert** beim Aufruf von `callAiWithLooping()` +- **Template wird aus `promptArgs` geholt** und an Continuation-Prompt-Builder übergeben +- **Garantiert exakte Identität** zwischen initialem und Continuation-Prompt + +**Einheitliche Signatur (ZIEL)**: +```python +# Einheitliche Signatur für ALLE Prompt-Builder: +async def buildXxxPromptWithContinuation( + continuationContext: ContinuationContext, # Pydantic-Modell (NICHT Optional - wird immer übergeben) + templateStructure: str, # Expliziter Parameter (aus promptArgs) + basePrompt: str, # Initialer Prompt (als Parameter übergeben) + promptArgs: Any # Dataclass mit use-case-spezifischen Parametern +) -> str: + """Build continuation prompt with unified signature.""" + ... +``` + +**Erforderliche Änderungen**: + +1. **Einheitlicher Prompt-Builder für `section_content`**: + - 3 Instanzen zu einer einzigen Funktion konsolidieren + - Funktion als Methode der Klasse definieren (nicht nested) + - Dataclass `SectionPromptArgs` für Parameter verwenden + +2. **Alle Prompt-Builder harmonisieren**: + - `continuationContext` als **NICHT-Optional** Parameter (wird immer übergeben) + - `templateStructure` als expliziten Parameter hinzufügen + - `basePrompt` als expliziten Parameter hinzufügen (garantiert Identität) + - Dataclasses für use-case-spezifische Parameter einführen + - `**kwargs` schrittweise durch Dataclass-Parameter ersetzen + +3. **`buildCodeStructurePromptWithContinuation()` implementieren**: + - Als Klassenmethode in `CodeGenerationPath` + - Ähnlich wie `buildChapterStructurePromptWithContinuation()` + - Template aus "## FILE STRUCTURE REQUIREMENTS" (Zeile 276-303 in `codePath.py`) + - Dataclass `CodeStructurePromptArgs` erstellen + +4. **Aufruf-Stelle anpassen** (`subAiCallLooping.py` Zeile 112-124): + ```python + # Aktuell: + continuationContext = buildContinuationContext(allSections, lastRawResponse, useCaseId) # Gibt Dict zurück + iterationPrompt = await promptBuilder(continuationContext=continuationContext, **promptArgs) + + # Verwendung (aktuell): + deliveredSummary = continuationContext.get("delivered_summary", "") + cutOffElement = continuationContext.get("cut_off_element", "") + + # Ziel (nach vollständiger Migration): + # templateStructure und basePrompt werden bereits beim Erstellen des initialen Prompts als Parameter übergeben + templateStructure = promptArgs.get("templateStructure") # Aus promptArgs + basePrompt = prompt # Initialer Prompt (wird bereits übergeben) + continuationContext = buildContinuationContext( + allSections, lastRawResponse, useCaseId, templateStructure + ) + + # Dataclass aus promptArgs erstellen (use-case-spezifisch) + # Beispiel für section_content: + sectionArgs = SectionPromptArgs( + section=promptArgs["section"], + contentParts=promptArgs["contentParts"], + userPrompt=promptArgs["userPrompt"], + generationHint=promptArgs["generationHint"], + allSections=promptArgs["allSections"], + sectionIndex=promptArgs["sectionIndex"], + isAggregation=promptArgs["isAggregation"], + services=promptArgs["services"] + ) + + iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + promptArgs=sectionArgs # Dataclass statt **kwargs + ) + ``` + +**⚠️ WICHTIG: Keine Rückwärtskompatibilität** +- **Keine Backward Compatibility** - neue Architektur wird vollständig implementiert +- **Keine Fallback-Logik** - alle Parameter sind REQUIRED +- **Klare neue Architektur** - alte Implementierungen werden vollständig ersetzt + +**Schrittweise Migration**: +- **Phase 1**: `ContinuationContext` Pydantic-Modell erstellen, `templateStructure` als expliziten Parameter hinzufügen +- **Phase 2**: `basePrompt` als expliziten Parameter hinzufügen +- **Phase 3**: Prompt-Builder konsolidieren und als Klassenmethoden definieren +- **Phase 4**: Alle Builder auf einheitliche Signatur umstellen (einheitliche Continuation-Prompt-Struktur) +- **Phase 5**: Dataclasses einführen und `**kwargs` schrittweise ersetzen + +--- + +### 5. Prompt-Builder-Aufruf-Mechanismus + +**Zentrale Aufruf-Stelle**: `subAiCallLooping.py` Zeile 112-124 + +**Aktueller Aufruf-Mechanismus**: +```python +# In subAiCallLooping.py (Zeile 112-124): +if (len(allSections) > 0 or lastRawResponse) and promptBuilder and promptArgs: + # Build continuation context + continuationContext = buildContinuationContext(allSections, lastRawResponse, useCaseId) + + # Ensure services is available in promptArgs + if not promptArgs.get('services') and hasattr(self, 'services'): + promptArgs['services'] = self.services + + # Call prompt builder with continuationContext and **kwargs + iterationPrompt = await promptBuilder(continuationContext=continuationContext, **promptArgs) +else: + # First iteration - use original prompt + iterationPrompt = prompt +``` + +**Aufruf-Beispiele für verschiedene Use Cases**: + +#### 1. `section_content` Use Case + +**Aufruf-Stelle**: `subStructureFilling.py` (3 Instanzen: Zeilen ~850, ~1100, ~1461) + +**Aktueller Aufruf**: +```python +# In _generateSectionContent() oder ähnlicher Methode: +generationPrompt, templateStructure = self._buildSectionGenerationPrompt( + section=section, + contentParts=contentParts, + userPrompt=userPrompt, + generationHint=generationHint, + allSections=allSections, + sectionIndex=sectionIndex, + isAggregation=isAggregation, + language=language +) + +# Nested function definieren (wird später als promptBuilder übergeben) +async def buildSectionPromptWithContinuation( + continuationContext: Dict[str, Any], + **kwargs +) -> str: + # Extrahiert Parameter aus kwargs: + section = kwargs.get("section") + contentParts = kwargs.get("contentParts", []) + userPrompt = kwargs.get("userPrompt", "") + generationHint = kwargs.get("generationHint", "") + allSections = kwargs.get("allSections", []) + sectionIndex = kwargs.get("sectionIndex", 0) + isAggregation = kwargs.get("isAggregation", False) + # ... baut Continuation-Prompt + return continuationPrompt + +# Aufruf von callAiWithLooping: +aiResponse = await self.aiService.callAiWithLooping( + prompt=generationPrompt, + options=options, + promptBuilder=buildSectionPromptWithContinuation, # Nested function + promptArgs={ + "section": section, + "contentParts": contentParts, + "userPrompt": userPrompt, + "generationHint": generationHint, + "allSections": allSections, + "sectionIndex": sectionIndex, + "isAggregation": isAggregation, + "services": self.services + }, + useCaseId="section_content", + debugPrefix=f"{chapterId}_section_{sectionId}", + contentParts=contentParts +) +``` + +**Ziel-Aufruf (nach Migration)**: +```python +# In _generateSectionContent(): +generationPrompt, templateStructure = self._buildSectionGenerationPrompt(...) + +# Prompt-Builder als Klassenmethode (konsolidiert): +# (wird als self.buildSectionPromptWithContinuation definiert) + +# Aufruf von callAiWithLooping: +aiResponse = await self.aiService.callAiWithLooping( + prompt=generationPrompt, + options=options, + promptBuilder=self.buildSectionPromptWithContinuation, # Klassenmethode + promptArgs={ + "section": section, + "contentParts": contentParts, + "userPrompt": userPrompt, + "generationHint": generationHint, + "allSections": allSections, + "sectionIndex": sectionIndex, + "isAggregation": isAggregation, + "services": self.services, + "templateStructure": templateStructure, # NEU: Explizit hinzugefügt + "basePrompt": generationPrompt # NEU: Explizit hinzugefügt + }, + useCaseId="section_content", + debugPrefix=f"{chapterId}_section_{sectionId}", + contentParts=contentParts +) + +# In subAiCallLooping.py (nach Migration): +templateStructure = promptArgs.get("templateStructure") +basePrompt = prompt # Initialer Prompt +continuationContext = buildContinuationContext( + allSections, lastRawResponse, useCaseId, templateStructure +) + +# Dataclass erstellen (use-case-spezifisch): +sectionArgs = SectionPromptArgs( + section=promptArgs["section"], + contentParts=promptArgs["contentParts"], + userPrompt=promptArgs["userPrompt"], + generationHint=promptArgs["generationHint"], + allSections=promptArgs["allSections"], + sectionIndex=promptArgs["sectionIndex"], + isAggregation=promptArgs["isAggregation"], + services=promptArgs["services"] +) + +iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + promptArgs=sectionArgs # Dataclass statt **kwargs +) +``` + +#### 2. `chapter_structure` Use Case + +**Aufruf-Stelle**: `subStructureGeneration.py` Zeile 162-176 + +**Aktueller Aufruf**: +```python +# In _generateChapterStructure(): +structurePrompt = self._buildChapterStructurePrompt( + userPrompt=userPrompt, + contentParts=contentParts, + outputFormat=outputFormat +) + +# Nested function definieren: +async def buildChapterStructurePromptWithContinuation( + continuationContext: Optional[Dict[str, Any]] = None, + **kwargs +) -> str: + userPrompt = kwargs.get("userPrompt", "") + contentParts = kwargs.get("contentParts", []) + outputFormat = kwargs.get("outputFormat", "txt") + # ... baut Continuation-Prompt + return continuationPrompt or basePrompt + +# Aufruf von callAiWithLooping: +aiResponseJson = await self.aiService.callAiWithLooping( + prompt=structurePrompt, + options=options, + promptBuilder=buildChapterStructurePromptWithContinuation, + promptArgs={ + "userPrompt": userPrompt, + "outputFormat": outputFormat, + "services": self.services + # HINWEIS: contentParts wird NICHT übergeben (nur Metadata im Prompt) + }, + useCaseId="chapter_structure", + debugPrefix="chapter_structure_generation", + contentParts=None # Explizit None - nur Metadata benötigt +) +``` + +**Ziel-Aufruf (nach Migration)**: +```python +# In _generateChapterStructure(): +structurePrompt, templateStructure = self._buildChapterStructurePrompt(...) + +# Aufruf von callAiWithLooping: +aiResponseJson = await self.aiService.callAiWithLooping( + prompt=structurePrompt, + options=options, + promptBuilder=self.buildChapterStructurePromptWithContinuation, # Klassenmethode + promptArgs={ + "userPrompt": userPrompt, + "outputFormat": outputFormat, + "services": self.services, + "templateStructure": templateStructure, # NEU + "basePrompt": structurePrompt # NEU + }, + useCaseId="chapter_structure", + debugPrefix="chapter_structure_generation", + contentParts=None +) + +# In subAiCallLooping.py (nach Migration): +chapterArgs = ChapterStructurePromptArgs( + userPrompt=promptArgs["userPrompt"], + contentParts=promptArgs.get("contentParts", []), + outputFormat=promptArgs["outputFormat"], + services=promptArgs["services"] +) + +iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + promptArgs=chapterArgs # Dataclass +) +``` + +#### 3. `code_content` Use Case + +**Aufruf-Stelle**: `codePath.py` Zeile 812-830 + +**Aktueller Aufruf**: +```python +# In _generateCodeContent() -> generateFileContent(): +contentPrompt = f"""# TASK: Generate Code File Content +... +Return ONLY valid JSON in this format: +{{ + "files": [ + {{ + "filename": "{filename}", + "content": "// Complete code here", + ... + }} + ] +}} +""" + +# Nested function definieren: +async def buildCodeContentPromptWithContinuation( + continuationContext: Optional[Dict[str, Any]] = None, + **kwargs +) -> str: + filename = kwargs.get("filename", "") + fileType = kwargs.get("fileType", "") + functions = kwargs.get("functions", []) + classes = kwargs.get("classes", []) + dependencies = kwargs.get("dependencies", []) + metadata = kwargs.get("metadata", {}) + userPrompt = kwargs.get("userPrompt", "") + contentParts = kwargs.get("contentParts", []) + contextInfo = kwargs.get("contextInfo", "") + # ... baut Continuation-Prompt + return continuationPrompt or basePrompt + +# Aufruf von callAiWithLooping: +contentJson = await self.services.ai.callAiWithLooping( + prompt=contentPrompt, + options=options, + promptBuilder=buildCodeContentPromptWithContinuation, + promptArgs={ + "filename": filename, + "fileType": fileType, + "functions": functions, + "classes": classes, + "dependencies": dependencies, + "metadata": metadata, + "userPrompt": userPrompt, + "contentParts": contentParts, + "contextInfo": contextInfo, + "services": self.services + }, + useCaseId="code_content", + debugPrefix=f"code_content_{fileStructure.get('id', 'file')}", +) +``` + +**Ziel-Aufruf (nach Migration)**: +```python +# In generateFileContent(): +contentPrompt, templateStructure = self._buildCodeContentPrompt(...) + +# Aufruf von callAiWithLooping: +contentJson = await self.services.ai.callAiWithLooping( + prompt=contentPrompt, + options=options, + promptBuilder=self.buildCodeContentPromptWithContinuation, # Klassenmethode + promptArgs={ + "filename": filename, + "fileType": fileType, + "functions": functions, + "classes": classes, + "dependencies": dependencies, + "metadata": metadata, + "userPrompt": userPrompt, + "contentParts": contentParts, + "contextInfo": contextInfo, + "services": self.services, + "templateStructure": templateStructure, # NEU + "basePrompt": contentPrompt # NEU + }, + useCaseId="code_content", + debugPrefix=f"code_content_{fileStructure.get('id', 'file')}", +) + +# In subAiCallLooping.py (nach Migration): +codeContentArgs = CodeContentPromptArgs( + filename=promptArgs["filename"], + fileType=promptArgs["fileType"], + functions=promptArgs["functions"], + classes=promptArgs["classes"], + dependencies=promptArgs["dependencies"], + metadata=promptArgs["metadata"], + userPrompt=promptArgs["userPrompt"], + contentParts=promptArgs["contentParts"], + contextInfo=promptArgs["contextInfo"], + services=promptArgs["services"] +) + +iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + promptArgs=codeContentArgs # Dataclass +) +``` + +#### 4. `code_structure` Use Case + +**Aufruf-Stelle**: `codePath.py` Zeile 311-317 + +**Aktueller Aufruf** (OHNE Prompt-Builder - noch nicht implementiert): +```python +# In _generateCodeStructure(): +structureJson = await self.services.ai.callAiWithLooping( + prompt=structurePrompt, + options=options, + useCaseId="code_structure", + debugPrefix="code_structure_generation", + contentParts=contentParts + # KEIN promptBuilder - noch nicht implementiert! +) +``` + +**Ziel-Aufruf (nach Implementierung)**: +```python +# In _generateCodeStructure(): +structurePrompt, templateStructure = self._buildCodeStructurePrompt(...) + +# Aufruf von callAiWithLooping: +structureJson = await self.services.ai.callAiWithLooping( + prompt=structurePrompt, + options=options, + promptBuilder=self.buildCodeStructurePromptWithContinuation, # NEU: Implementieren + promptArgs={ + "userPrompt": userPrompt, + "contentParts": contentParts, + "services": self.services, + "templateStructure": templateStructure, # NEU + "basePrompt": structurePrompt # NEU + }, + useCaseId="code_structure", + debugPrefix="code_structure_generation", + contentParts=contentParts +) + +# In subAiCallLooping.py (nach Migration): +codeStructureArgs = CodeStructurePromptArgs( + userPrompt=promptArgs["userPrompt"], + contentParts=promptArgs["contentParts"], + services=promptArgs["services"] +) + +iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + promptArgs=codeStructureArgs # Dataclass +) +``` + +**Zusammenfassung der Aufruf-Mechanismen**: + +| Use Case | Aktueller Aufruf | Ziel-Aufruf | Status | +|----------|------------------|-------------|--------| +| `section_content` | ✅ Mit `promptBuilder` (nested function, 3 Instanzen) | ✅ Klassenmethode, Dataclass `SectionPromptArgs` | ⏳ Migration erforderlich | +| `chapter_structure` | ✅ Mit `promptBuilder` (nested function) | ✅ Klassenmethode, Dataclass `ChapterStructurePromptArgs` | ⏳ Migration erforderlich | +| `code_content` | ✅ Mit `promptBuilder` (nested function) | ✅ Klassenmethode, Dataclass `CodeContentPromptArgs` | ⏳ Migration erforderlich | +| `code_structure` | ❌ OHNE `promptBuilder` | ✅ Klassenmethode, Dataclass `CodeStructurePromptArgs` | ⏳ Implementierung erforderlich | + +**Wichtige Punkte**: +1. **Zentrale Aufruf-Stelle**: Alle Prompt-Builder werden zentral in `subAiCallLooping.py` Zeile 112-124 aufgerufen +2. **Parameter-Übergabe**: Aktuell via `**kwargs` in `promptArgs` Dict, zukünftig via Dataclasses +3. **Template-Struktur**: Wird aktuell im Builder regeneriert, zukünftig als expliziter Parameter übergeben +4. **BasePrompt**: Wird aktuell im Builder regeneriert, zukünftig als expliziter Parameter übergeben +5. **Konsolidierung**: `section_content` hat 3 identische Instanzen → 1 konsolidierte Klassenmethode + +**Parameter-Mapping für explizite Signatures mit Dataclasses**: + +| Prompt-Builder | Aktuelle Parameter (aus `**kwargs`) | Dataclass | Ziel-Signature (einheitlich) | +|----------------|--------------------------------------|-----------|------------------------------| +| `buildSectionPromptWithContinuation()` | `section`, `contentParts`, `userPrompt`, `generationHint`, `allSections`, `sectionIndex`, `isAggregation`, `services` | `SectionPromptArgs` | `async def buildSectionPromptWithContinuation(continuationContext: Dict[str, Any], templateStructure: str, basePrompt: str, promptArgs: SectionPromptArgs) -> str` | +| `buildChapterStructurePromptWithContinuation()` | `userPrompt`, `contentParts`, `outputFormat`, `services` | `ChapterStructurePromptArgs` | `async def buildChapterStructurePromptWithContinuation(continuationContext: Dict[str, Any], templateStructure: str, basePrompt: str, promptArgs: ChapterStructurePromptArgs) -> str` | +| `buildCodeContentPromptWithContinuation()` | `filename`, `fileType`, `functions`, `classes`, `dependencies`, `metadata`, `userPrompt`, `contentParts`, `contextInfo`, `services` | `CodeContentPromptArgs` | `async def buildCodeContentPromptWithContinuation(continuationContext: Dict[str, Any], templateStructure: str, basePrompt: str, promptArgs: CodeContentPromptArgs) -> str` | +| `buildCodeStructurePromptWithContinuation()` | ❌ **FEHLT** | `CodeStructurePromptArgs` | `async def buildCodeStructurePromptWithContinuation(continuationContext: Dict[str, Any], templateStructure: str, basePrompt: str, promptArgs: CodeStructurePromptArgs) -> str` | + +**Vollständige Dataclass-Definitionen**: +```python +@dataclass +class SectionPromptArgs: + """Type-safe arguments for section content prompt builder.""" + section: Dict[str, Any] + contentParts: List[ContentPart] + userPrompt: str + generationHint: str + allSections: List[Dict[str, Any]] + sectionIndex: int + isAggregation: bool + services: Any + +@dataclass +class ChapterStructurePromptArgs: + """Type-safe arguments for chapter structure prompt builder.""" + userPrompt: str + contentParts: List[ContentPart] + outputFormat: str + services: Any + +@dataclass +class CodeContentPromptArgs: + """Type-safe arguments for code content prompt builder.""" + filename: str + fileType: str + functions: List[Dict] + classes: List[Dict] + dependencies: List[str] + metadata: Dict[str, Any] + userPrompt: str + contentParts: List[ContentPart] + contextInfo: str + services: Any + +@dataclass +class CodeStructurePromptArgs: + """Type-safe arguments for code structure prompt builder.""" + userPrompt: str + contentParts: List[ContentPart] + services: Any +``` + +**Prompt-Builder Locations und Konsolidierung**: + +| Prompt-Builder | Location | Anzahl | Typ | Konsolidierung | +|----------------|----------|--------|-----|----------------| +| `buildSectionPromptWithContinuation` | `subStructureFilling.py` | 3 Instanzen | Nested functions (Zeilen 814, 1100, 1461) | ✅ 3 → 1 (als Klassenmethode in `StructureFiller`) | +| `buildChapterStructurePromptWithContinuation` | `subStructureGeneration.py` | 1 Instanz | Nested function (Zeile 111) | ✅ Als Klassenmethode in `StructureGenerator` | +| `buildCodeContentPromptWithContinuation` | `codePath.py` | 1 Instanz | Nested function (Zeile 683) | ✅ Als Klassenmethode in `CodeGenerationPath` | +| `buildCodeStructurePromptWithContinuation` | ❌ **FEHLT** | 0 | - | ✅ Als Klassenmethode in `CodeGenerationPath` (neu erstellen) | + +**Architektur-Entscheidung**: +- **NICHT** in eine zentrale Klasse verschieben (zu enge Kopplung) +- **ABER**: Konsolidierung innerhalb der jeweiligen Klassen +- **Vorteil**: Modulare Struktur, keine enge Kopplung, einfache Wartung + +**Aufruf-Mechanismus**: +- Alle Builder werden in `subAiCallLooping.py` Zeile 124 aufgerufen +- Einheitliche Signatur ermöglicht einheitlichen Aufruf ohne use-case-spezifische Logik +- Nach Migration: `await promptBuilder(continuationContext=..., templateStructure=..., basePrompt=..., promptArgs=dataclassInstance)` + +--- + +### 3. Template-Struktur-Parameter + +**⚠️ KRITISCH: Template-Struktur ist ein expliziter Parameter** + +**WICHTIG**: +- **Template-Struktur wird NICHT aus dem initialen Prompt extrahiert** +- **Template-Struktur wird NICHT aus dem Response extrahiert** +- **Template-Struktur ist ein expliziter Parameter**, der beim Erstellen des initialen Prompts **erstellt** wird +- **Derselbe Template-Struktur-Parameter** wird dann für den Continuation-Prompt verwendet +- Dies garantiert **exakte Identität** zwischen initialem und Continuation-Prompt + +**Ziel-Architektur**: +- **Template wird beim Erstellen des initialen Prompts als expliziter Parameter erstellt** +- **Template wird in `promptArgs` gespeichert** beim Aufruf von `callAiWithLooping()` +- **Template wird aus `promptArgs` extrahiert** und an `buildContinuationContext()` und `promptBuilder` übergeben +- **Keine Extraktion aus Prompt-String** - Template ist ein expliziter Parameter, garantiert identisch + +**Beispiel-Implementierung**: +```python +# Beim Erstellen des initialen Prompts: +def _buildSectionGenerationPrompt( + self, + section: Dict[str, Any], + contentParts: List[ContentPart], + userPrompt: str, + generationHint: str, + allSections: List[Dict[str, Any]], + sectionIndex: int, + isAggregation: bool, + language: str +) -> tuple[str, str]: # Gibt (prompt, templateStructure) zurück + """Build section generation prompt and return prompt + template structure. + + WICHTIG: templateStructure wird hier ERSTELLT (nicht extrahiert) und als separater + Parameter zurückgegeben. Derselbe Parameter wird dann für den Continuation-Prompt verwendet. + """ + + contentType = section.get("content_type", "paragraph") + contentStructureExample = self._getContentStructureExample(contentType) + + # Template-Struktur explizit ERSTELLEN (nicht extrahieren!) + # Dies ist der Parameter, der später für den Continuation-Prompt verwendet wird + templateStructure = f"""{{ + "elements": [ + {{ + "type": "{contentType}", + "content": {contentStructureExample} + }} + ] +}}""" + + # Prompt mit Template erstellen (Template wird hier eingebettet) + prompt = f"""... +Return ONLY valid JSON in this format: +{templateStructure} +...""" + + # WICHTIG: Beide zurückgeben - prompt UND templateStructure als separater Parameter + return prompt, templateStructure # templateStructure wird NICHT aus prompt extrahiert! + +# Beim Aufruf von callAiWithLooping: +prompt, templateStructure = self._buildSectionGenerationPrompt(...) +promptArgs["templateStructure"] = templateStructure # In promptArgs speichern +promptArgs["basePrompt"] = prompt # BasePrompt auch speichern + +# Später im Continuation-Prompt: +templateStructure = promptArgs.get("templateStructure") # Aus promptArgs holen +basePrompt = promptArgs.get("basePrompt") # BasePrompt aus promptArgs holen +continuationContext = buildContinuationContext( + allSections, lastRawResponse, useCaseId, templateStructure +) + +# Dataclass erstellen (nach Migration): +sectionArgs = SectionPromptArgs( + section=promptArgs["section"], + contentParts=promptArgs["contentParts"], + userPrompt=promptArgs["userPrompt"], + generationHint=promptArgs["generationHint"], + allSections=promptArgs["allSections"], + sectionIndex=promptArgs["sectionIndex"], + isAggregation=promptArgs["isAggregation"], + services=promptArgs["services"] +) + +iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, # Derselbe Parameter wie im initialen Prompt + basePrompt=basePrompt, # Derselbe Prompt wie initial + promptArgs=sectionArgs # Dataclass statt **kwargs +) +``` + +**Vorteil**: +- ✅ Garantierte Identität: Initialer und Continuation-Prompt verwenden exakt denselben Template-Parameter +- ✅ Keine Extraktion nötig: Template wird nicht aus Prompt-String geparst +- ✅ Einfacher: Template wird einmal erstellt und wiederverwendet + +**Erforderliche Änderungen**: +1. **Alle Prompt-Erstellungs-Funktionen anpassen**: + - Template-Struktur **explizit erstellen** (nicht extrahieren!) + - Template-Struktur als separater Rückgabewert zurückgeben: `(prompt, templateStructure)` + - Template-Struktur in `promptArgs` speichern beim Aufruf von `callAiWithLooping()` +2. **In `callAiWithLooping()`**: + - `templateStructure` aus `promptArgs` holen (REQUIRED - Fehler wenn fehlt) + - `basePrompt` aus `prompt` Parameter holen (ist der initiale Prompt) + - Beide an `buildContinuationContext()` und `promptBuilder` übergeben +3. **Alle Prompt-Builder anpassen**: + - `templateStructure` als expliziten Parameter akzeptieren (nicht extrahieren!) + - `basePrompt` als expliziten Parameter akzeptieren (nicht regenerieren!) + - Template und BasePrompt direkt verwenden (garantiert identisch mit initialem Prompt) + +--- + +### 4. Konsolidierung: 3 Instanzen → 1 Funktion + +**Aktueller Zustand**: +- 3 identische Instanzen von `buildSectionPromptWithContinuation()` in `subStructureFilling.py` +- Unterschiede nur in `promptArgs` (z.B. `contentParts` manchmal leer, manchmal gefüllt) + +**Ziel-Architektur**: +```python +class StructureFiller: + """Handles structure filling for document generation.""" + + async def buildSectionPromptWithContinuation( + self, + continuationContext: Dict[str, Any], + templateStructure: str, # NEU: Expliziter Parameter + section: Dict[str, Any], + contentParts: List[ContentPart], + userPrompt: str, + generationHint: str, + allSections: List[Dict[str, Any]], + sectionIndex: int, + isAggregation: bool, + language: str + ) -> str: + """ + Build section prompt with continuation context. + + Single unified implementation for all section content generation contexts. + """ + # Build base prompt + basePrompt = self._buildSectionGenerationPrompt( + section=section, + contentParts=contentParts, + userPrompt=userPrompt, + generationHint=generationHint, + allSections=allSections, + sectionIndex=sectionIndex, + isAggregation=isAggregation, + language=language + ) + + # Use provided templateStructure (from initial prompt) + # This ensures exact match between initial and continuation prompts + structureTemplate = f"""JSON Structure Template: +{templateStructure} + +""" + + # Build continuation prompt with template and context + ... +``` + +**Erforderliche Änderungen**: +1. Eine einzige Methode `buildSectionPromptWithContinuation()` in `StructureFiller` Klasse definieren +2. Alle 3 nested functions entfernen +3. Alle Aufrufe anpassen, um die neue Methode zu verwenden + +--- + +## Zusammenfassung: Erforderliche Code-Änderungen + +### Priorität 1: Template-Struktur-Konsistenz (KRITISCH) + +1. **`ContinuationContext` Pydantic-Modell erstellen** (`jsonUtils.py` oder separate Model-Datei): + - Alle Felder definieren: `section_count`, `delivered_summary`, `cut_off_element`, `element_before_cutoff`, `template_structure`, `last_complete_part`, `incomplete_part`, `structure_context`, `last_raw_json` + - Optional-Felder korrekt markieren + +2. **`buildContinuationContext()` erweitern** (`jsonUtils.py`): + - Parameter `templateStructure: Optional[str]` hinzufügen + - Rückgabetyp ändern von `Dict[str, Any]` zu `ContinuationContext` + - `ContinuationContext`-Instanz erstellen und zurückgeben statt Dict + +3. **Prompt-Erstellungs-Funktionen anpassen** (z.B. `_buildSectionGenerationPrompt()`, `_buildChapterStructurePrompt()`, etc.): + - Template-Struktur explizit erstellen (nicht extrahieren) + - Template-Struktur in `promptArgs` speichern beim Erstellen des initialen Prompts + - Template-Struktur als Teil des Prompts einbetten + +4. **Alle Verwendungen von `continuationContext` anpassen**: + - **Prompt-Builder** (`subStructureFilling.py`, `subStructureGeneration.py`, `codePath.py`, etc.): + - `continuationContext.get("delivered_summary")` → `continuationContext.delivered_summary` + - `continuationContext.get("cut_off_element")` → `continuationContext.cut_off_element` + - `continuationContext.get("element_before_cutoff")` → `continuationContext.element_before_cutoff` + - `continuationContext.get("template_structure")` → `continuationContext.template_structure` + - `continuationContext.get("last_complete_part")` → `continuationContext.last_complete_part` + - `continuationContext.get("incomplete_part")` → `continuationContext.incomplete_part` + - `continuationContext.get("structure_context")` → `continuationContext.structure_context` + - `continuationContext.get("last_raw_json")` → `continuationContext.last_raw_json` + - **Andere Verwendungen** (`subPromptBuilderGeneration.py`, etc.): + - Alle Dict-Zugriffe auf Attribut-Zugriffe umstellen + +5. **`callAiWithLooping()` anpassen** (`subAiCallLooping.py`): + - **Parameter-Validierung**: `templateStructure` und `basePrompt` aus `promptArgs` holen (REQUIRED - Fehler wenn fehlt) + - **Error Handling**: `ValueError` werfen wenn `templateStructure` oder `basePrompt` fehlen + - `templateStructure` an `buildContinuationContext()` übergeben + - `basePrompt` aus `prompt` Parameter holen (ist der initiale Prompt) + - `continuationContext` (Pydantic-Modell) an `promptBuilder` übergeben + - `templateStructure` und `basePrompt` an `promptBuilder` übergeben + +### Priorität 2: Prompt-Builder-Harmonisierung + +4. **`buildSectionPromptWithContinuation()` konsolidieren** (`subStructureFilling.py`): + - 3 Instanzen zu einer Methode zusammenführen + - Als Klassenmethode definieren (nicht nested) + +5. **Alle Prompt-Builder erweitern**: + - `templateStructure` als expliziten Parameter hinzufügen + - Template aus Parameter verwenden (nicht aus `continuationContext` extrahieren) + +### Priorität 3: Typsicherheit (Optional, aber empfohlen) + +6. **Typsichere Parameter einführen**: + - Dataclasses für Parameter-Strukturen erstellen + - `**kwargs` durch explizite Parameter ersetzen + +--- + +## Use Cases im Detail + +**Hinweis zu JSON-Template-Strukturen**: +Die **JSON-Template-Struktur im Prompt** ist die Struktur, die der AI im initialen Prompt als Vorlage gegeben wird. Diese Struktur wird auch im Continuation-Prompt verwendet, um Konsistenz zu gewährleisten. Die Template-Struktur zeigt der AI, welches JSON-Format erwartet wird. + +--- + +### 1. `section_content` ✅ + +**JSON-Template-Struktur im Prompt**: +```json +{ + "elements": [ + { + "type": "{contentType}", + "content": {contentStructureExample} + } + ] +} +``` + +**Template-Quelle**: `_getContentStructureExample(contentType)` - dynamisch je nach `contentType`: +- `table`: `{"headers": ["Column1", "Column2"], "rows": [["Value1", "Value2"], ["Value3", "Value4"]]}` +- `bullet_list`: `{"items": ["Item 1", "Item 2", "Item 3"]}` +- `heading`: `{"text": "Section Title", "level": 2}` +- `paragraph`: `{"text": "This is paragraph text."}` +- `code_block`: `{"code": "function example() { return true; }", "language": "javascript"}` +- `image`: `{"base64Data": "", "altText": "Description", "caption": "Optional caption"}` + +**Prompt-Location**: `subStructureFilling.py` Zeile 2338-2348 ("## OUTPUT FORMAT") + +**Looping-Mechanismus**: +- ✅ **In `loopingUseCases` Liste**: Ja +- ✅ **In `directReturnUseCases` Liste**: Ja +- ✅ **Unterstützt Iterationen**: Ja +- ✅ **Unterstützt Accumulation**: Nein (`supportsAccumulation=False`) +- ✅ **Requires Extraction**: Nein (`requiresExtraction=False`) + +**Prompt-Builder**: +- **Spezifisch**: `buildSectionPromptWithContinuation()` (3 Instanzen → **SOLLTE konsolidiert werden**) +- **Parameter**: Alle `promptArgs` werden übergeben +- **Location**: `subStructureFilling.py` (nested functions → **SOLLTE Klassenmethode werden**) + +**Merging-Strategie**: +- String-basiertes Merging mit Overlap-Detection +- Fallback auf Deep-Structure-Merging bei Parsing-Fehlern +- Normalisierung: Konvertiert Listen von Strings zu `paragraph` Elementen + +**Besonderheiten**: +- Verwendet spezifischen Prompt-Builder (`buildSectionPromptWithContinuation`) +- JSON-Struktur-Template kommt aus initialem Prompt (`_getContentStructureExample()`) +- **PROBLEM**: 3 verschiedene Implementierungen des Prompt-Builders (je nach Kontext) +- **Template-Struktur-Quelle**: ✅ **KORREKT** - Kommt aus initialem Prompt, nicht aus Response-Extraktion + +**Template-Struktur-Quelle Analyse**: +- ✅ **`section_content`**: Template kommt aus initialem Prompt via `_getContentStructureExample(contentType)` +- ✅ **Template wird dynamisch generiert** basierend auf `contentType` (z.B. `table`, `paragraph`, etc.) +- ✅ **Template wird im Continuation-Prompt exakt wiederholt** (gleiche Logik wie im initialen Prompt) +- ✅ **Keine Extraktion aus Response** - Template wird als Parameter übergeben (Ziel-Architektur) + +**✅ Template-Konsistenz geprüft**: +- **Initial**: `subStructureFilling.py` Zeile 2342-2349 ("## OUTPUT FORMAT") +- **Continuation**: `subStructureFilling.py` Zeile 877-887 ("JSON Structure Template") +- **Status**: ✅ **IDENTISCH** - Beide verwenden `contentType = section.get("content_type", "paragraph")` und `contentStructureExample = self._getContentStructureExample(contentType)` + +--- + +### 2. `chapter_structure` ✅ + +**JSON-Template-Struktur im Prompt**: +```json +{ + "metadata": { + "title": "...", + "language": "..." + }, + "documents": [{ + "id": "doc_1", + "title": "...", + "filename": "...", + "outputFormat": "...", + "language": "...", + "chapters": [ + { + "id": "chapter_1", + "level": 1, + "title": "...", + "contentParts": {}, + "generationHint": "...", + "sections": [] + } + ] + }] +} +``` + +**Template-Quelle**: Im initialen Prompt bei "EXAMPLE STRUCTURE" (Zeile 418-445 in `subStructureGeneration.py`) + +**Prompt-Location**: `subStructureGeneration.py` Zeile 418-445 ("EXAMPLE STRUCTURE") + +**Looping-Mechanismus**: +- ✅ **In `loopingUseCases` Liste**: Ja +- ✅ **In `directReturnUseCases` Liste**: Ja +- ✅ **Unterstützt Iterationen**: Ja +- ✅ **Unterstützt Accumulation**: Nein (`supportsAccumulation=False`) +- ✅ **Requires Extraction**: Nein (`requiresExtraction=False`) + +**Prompt-Builder**: +- **Spezifisch**: `buildChapterStructurePromptWithContinuation()` (nested function in `subStructureGeneration.py`) +- **Parameter**: Alle `promptArgs` werden übergeben (`userPrompt`, `contentParts`, `outputFormat`, `services`) +- **Location**: `subStructureGeneration.py` (nested function) + +**Merging-Strategie**: +- String-basiertes Merging mit Overlap-Detection +- Fallback auf Deep-Structure-Merging bei Parsing-Fehlern +- Direkter Return nach Completion + +**Besonderheiten**: +- Verwendet spezifischen Prompt-Builder (`buildChapterStructurePromptWithContinuation`) +- JSON-Struktur-Template kommt aus initialem Prompt ("EXAMPLE STRUCTURE") +- Template wird im Continuation-Prompt exakt wiederholt + +**✅ Template-Konsistenz geprüft**: +- **Initial**: `subStructureGeneration.py` Zeile 418-445 ("EXAMPLE STRUCTURE") +- **Continuation**: `subStructureGeneration.py` Zeile 111-155 (`buildChapterStructurePromptWithContinuation`) +- **Status**: ✅ **IDENTISCH** - Beide verwenden das gleiche Template aus `_buildChapterStructurePrompt()` + +--- + +### 3. `code_structure` ✅ + +**JSON-Template-Struktur im Prompt**: +```json +{ + "metadata": { + "language": "", + "projectType": "single_file|multi_file", + "projectName": "" + }, + "files": [ + { + "id": "", + "filename": "", + "fileType": "", + "dependencies": [], + "imports": [], + "functions": [], + "classes": [] + } + ] +} +``` + +**Template-Quelle**: Im initialen Prompt bei "## FILE STRUCTURE REQUIREMENTS" (Zeile 276-303 in `codePath.py`) + +**Prompt-Location**: `codePath.py` Zeile 276-303 ("## FILE STRUCTURE REQUIREMENTS") + +**Looping-Mechanismus**: +- ✅ **In `loopingUseCases` Liste**: Ja +- ✅ **In `directReturnUseCases` Liste**: Ja +- ✅ **Unterstützt Iterationen**: Ja +- ✅ **Unterstützt Accumulation**: Nein (`supportsAccumulation=False`) +- ✅ **Requires Extraction**: Nein (`requiresExtraction=False`) + +**Prompt-Builder**: +- **Status**: ❌ **FEHLT** - Kein `promptBuilder` wird übergeben +- **Problem**: Continuation-Prompts werden nicht unterstützt + +**Merging-Strategie**: +- String-basiertes Merging mit Overlap-Detection +- Fallback auf Deep-Structure-Merging bei Parsing-Fehlern +- Direkter Return nach Completion + +**Besonderheiten**: +- **KRITISCH**: Es gibt **KEINEN `promptBuilder`** für `code_structure`! +- Continuation-Prompts werden nicht gebaut, daher keine Iterationen möglich + +**❌ Template-Konsistenz geprüft**: +- **Initial**: `codePath.py` Zeile 276-303 ("## FILE STRUCTURE REQUIREMENTS") +- **Continuation**: ❌ **FEHLT** - Kein Prompt-Builder vorhanden +- **Status**: ❌ **PROBLEM** - Es gibt **KEINEN `promptBuilder`** für `code_structure`! Continuation-Prompts werden nicht unterstützt. +- **Erforderliche Änderung**: Ein `buildCodeStructurePromptWithContinuation()` muss erstellt werden, der: + 1. Das Template aus "## FILE STRUCTURE REQUIREMENTS" (Zeile 276-303) extrahiert + 2. Es im Continuation-Prompt als "JSON Structure Template:" wiederholt + 3. Die gleiche Struktur wie `buildChapterStructurePromptWithContinuation` verwendet + +--- + +### 4. `code_content` ✅ + +**JSON-Template-Struktur im Prompt**: +```json +{ + "files": [ + { + "filename": "{filename}", + "content": "// Complete code here", + "functions": [], + "classes": [] + } + ] +} +``` + +**Template-Quelle**: Im initialen Prompt bei "Return ONLY valid JSON in this format:" (Zeile 668-678 in `codePath.py`) + +**Prompt-Location**: `codePath.py` Zeile 643-679 + +**Looping-Mechanismus**: +- ✅ **In `loopingUseCases` Liste**: Ja +- ✅ **In `directReturnUseCases` Liste**: Ja +- ✅ **Unterstützt Iterationen**: Ja +- ✅ **Unterstützt Accumulation**: Ja (`supportsAccumulation=True`) +- ✅ **Requires Extraction**: Nein (`requiresExtraction=False`) + +**Prompt-Builder**: +- **Spezifisch**: `buildCodeContentPromptWithContinuation()` (nested function in `codePath.py`) +- **Parameter**: Alle `promptArgs` werden übergeben (`filename`, `fileType`, `functions`, `classes`, `dependencies`, `metadata`, `userPrompt`, `contentParts`, `contextInfo`, `services`) +- **Location**: `codePath.py` (nested function) + +**Merging-Strategie**: +- String-basiertes Merging mit Overlap-Detection +- Fallback auf Deep-Structure-Merging bei Parsing-Fehlern +- Direkter Return nach Completion + +**Besonderheiten**: +- Verwendet spezifischen Prompt-Builder (`buildCodeContentPromptWithContinuation`) +- JSON-Struktur-Template kommt aus initialem Prompt (Zeile 668-678) +- Template wird im Continuation-Prompt exakt wiederholt + +**✅ Template-Konsistenz geprüft**: +- **Initial**: `codePath.py` Zeile 668-678 ("Return ONLY valid JSON in this format:") +- **Continuation**: `codePath.py` Zeile 668-678 (wird im `buildCodeContentPromptWithContinuation` exakt wiederholt) +- **Status**: ✅ **IDENTISCH** - Beide verwenden das gleiche Template mit den gleichen Beispielwerten + +--- + +## Merging-Strategien + +**Merging-Strategien**: +- **Primär**: String-basiertes Merging mit `mergeJsonStringsWithOverlap()` +- **Fallback**: Deep-Structure-Merging bei Parsing-Fehlern (für alle Use Cases) + - Wenn String-Merge erfolgreich, aber Parsing fehlschlägt: Versucht jedes Fragment einzeln zu parsen + - Verwendet `mergeDeepStructures()` für intelligentes Merging der geparsten Fragmente + - Funktioniert rekursiv für beliebige JSON-Strukturen (Arrays, Objekte, verschachtelt) +- **Overlap-Detection**: Multi-Strategy (exact, line-based, partial line-based, structure-aware) + +--- + +## Continuation-Prompt-Strukturen im Detail + +### Fix vs. Variabel Analyse + +**Kernprinzip**: Alle Iterations-Prompts sollten nach der **gleichen Logik** aufgebaut sein, aber für verschiedene JSON-Strukturen. Die Struktur des Iterations-Prompts selbst sollte bei allen Use Cases **gleich** sein. + +#### ✅ FIX (sollte bei allen Use Cases identisch sein) + +| Element | Inhalt | Beschreibung | +|---------|--------|--------------| +| **Separator** | `--- CONTINUATION REQUEST ---` | Trennlinie zwischen Base Prompt und Continuation-Anweisungen | +| **Einleitung** | `The previous JSON response was incomplete. Continue from where it stopped.` | Standard-Einleitung für alle Continuation-Requests | +| **Section-Header** | `JSON Structure Template:` | Header für Template-Sektion | +| **Section-Header** | `Context showing structure hierarchy with cut point:` | Header für Context-Sektion | +| **Section-Header** | `Overlap Requirement:` | Header für Overlap-Anweisungen | +| **Overlap-Anweisung** | `To ensure proper merging, your response MUST start by repeating approximately the last 100 characters from the previous response, then continue with new content.` | Standard-Overlap-Anweisung | +| **Section-Header** | `Last ~100 characters from previous response (repeat these at the start):` | Header für Overlap-Context | +| **Section-Header** | `TASK:` | Header für Task-Anweisungen | +| **TASK Punkt 1** | `Start your response by repeating the last ~100 characters shown above (for overlap/merging)` | Overlap-Start-Anweisung | +| **TASK Punkt 2** | `Complete the incomplete element shown in the context above (marked with CUT POINT)` | Completion-Anweisung | +| **TASK Punkt 3** | `Continue generating the remaining content following the JSON structure template above` | Continuation-Anweisung | +| **TASK Punkt 4** | `Return ONLY valid JSON following the structure template - no overlap/continuation wrapper objects` | Format-Anweisung | +| **Section-Header** | `CRITICAL:` | Header für kritische Anweisungen | +| **CRITICAL Punkt 1** | `Your response must be valid JSON matching the structure template above` | JSON-Format-Anforderung | +| **CRITICAL Punkt 2** | `Start with overlap (~100 chars) then continue seamlessly` | Overlap-Seamless-Anweisung | +| **CRITICAL Punkt 3** | `Complete the incomplete element and continue with remaining elements` | Completion-Continuation-Anweisung | + +#### 🔄 VARIABEL (use-case-spezifisch) + +| Element | Beschreibung | Aktuelle Implementierung | +|---------|--------------|-------------------------| +| **`basePrompt`** | Initialer Prompt (use-case-spezifisch) | Wird als Parameter übergeben, variiert je nach Use Case | +| **`structureTemplate`** | JSON-Struktur-Template (use-case-spezifisch) | Wird aus initialem Prompt extrahiert oder als Parameter übergeben | +| **`unifiedContext`** | Hierarchischer Cut-Point-Context (use-case-spezifisch, aber gleiche Logik) | Wird aus `_buildIncompleteContext()` erstellt - sollte für alle Use Cases nach gleicher Logik funktionieren | +| **`overlapContext`** | Letzte ~100 Zeichen (gleiche Logik, aber unterschiedlicher Inhalt) | `lastRawJson[-100:].strip()` - gleiche Logik für alle | + +**⚠️ MANDATORY: Alle Use Cases müssen die einheitliche Struktur verwenden** + +**Wichtig**: +- ✅ **Fix-Elemente** MÜSSEN bei allen Use Cases **exakt identisch** sein - keine Variationen erlaubt +- 🔄 **Variable Elemente** variieren im Inhalt, aber die **Logik** zur Erstellung ist gleich +- ✅ **Alle Use Cases** (`section_content`, `chapter_structure`, `code_structure`, `code_content`) verwenden **dieselbe Continuation-Prompt-Struktur** + +### Einheitliche Continuation-Prompt-Struktur (Ziel) + +**⚠️ MANDATORY: Alle Use Cases MÜSSEN diese einheitliche Struktur verwenden** + +**Keine Variationen erlaubt** - alle Use Cases (`section_content`, `chapter_structure`, `code_structure`, `code_content`) verwenden **exakt dieselbe Continuation-Prompt-Struktur**: + +1. **Base Prompt** (initialer Prompt - unverändert, als Parameter übergeben) +2. **--- CONTINUATION REQUEST ---** (Trennlinie) +3. **JSON Structure Template:** (Header) +4. **Template-Struktur** (exakt identisch mit initialem Prompt, als Parameter übergeben) +5. **Context showing structure hierarchy with cut point:** (Header) +6. **Hierarchischer Context** (hierarchischer Cut-Point-Context) +7. **Overlap Requirement:** (Header) +8. **Overlap-Anweisung** (~100 Zeichen) +9. **Last ~100 characters from previous response (repeat these at the start):** (Header) +10. **Overlap-Context** (letzte ~100 Zeichen) +11. **TASK:** (Header) +12. **TASK-Anweisungen** (4 Punkte) +13. **CRITICAL:** (Header) +14. **CRITICAL-Anweisungen** (3 Punkte) + +**⚠️ KRITISCH: Template-Konsistenz** +- Das `JSON Structure Template` im Continuation-Prompt **MUSS exakt identisch** mit dem Template im initialen Prompt sein +- **Template wird NICHT extrahiert** - es wird als expliziter Parameter (`templateStructure`) übergeben +- **BasePrompt wird NICHT regeneriert** - es wird als expliziter Parameter (`basePrompt`) übergeben +- Dies garantiert **exakte Identität** zwischen initialem und Continuation-Prompt + +### Die 3 Prompt-Systeme für `section_content` + +**Besonderheit**: Der `section_content` Use Case verwendet **3 verschiedene Prompt-Systeme**, die sich durch den **Verarbeitungsmodus** unterscheiden, aber alle die gleiche Template-Struktur verwenden: + +#### System 1: Aggregation-Modus + +**Kontext**: `if needsAggregation and useAiCall` + +**Verwendung**: +- Alle ContentParts einer Section werden **zusammen** verarbeitet +- Ein einziger AI-Call für alle Parts +- Verwendet `isAggregation=True` im Prompt + +**Code-Location**: +- `subStructureFilling.py` Zeile ~611-922 +- `buildSectionPromptWithContinuation` Zeile ~814-901 + +**Besonderheiten**: +- Verarbeitet mehrere ContentParts gleichzeitig +- Aggregiert alle Parts zu einem einzigen Prompt +- Verwendet `useCaseId="section_content"` + +#### System 2: Einzelverarbeitung ohne ContentParts + +**Kontext**: `if len(contentPartIds) == 0 and useAiCall and generationHint` + +**Verwendung**: +- **Keine ContentParts** vorhanden +- Generation **nur mit generationHint** +- Content wird von Grund auf generiert + +**Code-Location**: +- `subStructureFilling.py` Zeile ~1039-1208 +- `buildSectionPromptWithContinuation` Zeile ~1101-1197 + +**Besonderheiten**: +- Generiert Content ohne Input-ContentParts +- Verwendet nur `generationHint` als Basis +- Verwendet `useCaseId="section_content"` + +#### System 3: Einzelverarbeitung mit ContentParts + +**Kontext**: Einzelne ContentParts werden nacheinander verarbeitet + +**Verwendung**: +- **Einzelne ContentParts** werden sequenziell verarbeitet +- Jeder Part wird einzeln an die AI gesendet +- Mehrere AI-Calls pro Section (einer pro Part) + +**Code-Location**: +- `subStructureFilling.py` Zeile ~1350-1570 +- `buildSectionPromptWithContinuation` Zeile ~1463-1567 + +**Besonderheiten**: +- Verarbeitet Parts einzeln +- Kann mehrere Iterationen pro Section haben +- Verwendet `useCaseId="section_content"` + +**Gemeinsame Basis aller 3 Systeme**: +- ✅ Alle verwenden die **gleiche Template-Struktur** aus `_getContentStructureExample()` +- ✅ Alle verwenden `useCaseId="section_content"` +- ✅ Alle verwenden die **gleiche Continuation-Prompt-Struktur** (Fix-Elemente) +- ✅ Alle verwenden `buildSectionPromptWithContinuation()` (aktuell 3 Instanzen, zukünftig 1 konsolidierte Funktion) + +**Entscheidungslogik** (in `_processSingleSection()`): +```python +if needsAggregation and useAiCall: + # System 1: Aggregation +elif len(contentPartIds) == 0 and useAiCall and generationHint: + # System 2: Ohne ContentParts +else: + # System 3: Mit ContentParts (Einzelverarbeitung) +``` + +**Ziel-Architektur**: +- Alle 3 Systeme sollten die **konsolidierte** `buildSectionPromptWithContinuation()` Klassenmethode verwenden +- Template-Struktur wird als Parameter übergeben (garantiert Identität) +- BasePrompt wird als Parameter übergeben (garantiert Identität) + +--- + +## Error Handling + +**Parameter-Validierung in `callAiWithLooping()`**: + +```python +# In subAiCallLooping.py: +if (len(allSections) > 0 or lastRawResponse) and promptBuilder and promptArgs: + # REQUIRED: templateStructure und basePrompt müssen in promptArgs vorhanden sein + templateStructure = promptArgs.get("templateStructure") + if not templateStructure: + raise ValueError( + f"templateStructure is REQUIRED in promptArgs for use case '{useCaseId}'. " + "Prompt creation functions must return (prompt, templateStructure) tuple." + ) + + basePrompt = promptArgs.get("basePrompt") + if not basePrompt: + # Fallback: use prompt parameter (should be the same) + basePrompt = prompt + # Log warning if they differ + if basePrompt != prompt: + logger.warning( + f"basePrompt not found in promptArgs for use case '{useCaseId}', " + "using prompt parameter instead. This may indicate a bug." + ) + + # Build continuation context + continuationContext = buildContinuationContext( + allSections, lastRawResponse, useCaseId, templateStructure + ) + + # Call prompt builder with validated parameters + iterationPrompt = await promptBuilder( + continuationContext=continuationContext, + templateStructure=templateStructure, + basePrompt=basePrompt, + **promptArgs # During migration, still use **kwargs + ) +``` + +**Error Messages**: +- Klare Fehlermeldungen wenn REQUIRED Parameter fehlen +- Hinweise auf erwartete Format (z.B. "Prompt creation functions must return (prompt, templateStructure) tuple") +- Logging für Warnungen (z.B. wenn basePrompt nicht in promptArgs gefunden wird) + +--- + +## Implementierungs-Roadmap + +### Phase 1: ContinuationContext Pydantic-Modell und Template-Struktur-Parameter (KRITISCH) +1. ✅ `ContinuationContext` Pydantic-Modell erstellen (`jsonUtils.py` oder separate Model-Datei) +2. ✅ `buildContinuationContext()` um `templateStructure` Parameter erweitern +3. ✅ `buildContinuationContext()` Rückgabetyp ändern von `Dict[str, Any]` zu `ContinuationContext` +4. ✅ Alle Prompt-Erstellungs-Funktionen anpassen, um `templateStructure` zu **erstellen** (nicht extrahieren!) und als `(prompt, templateStructure)` Tuple zurückzugeben +5. ✅ Alle Aufrufe anpassen, um `templateStructure` in `promptArgs` zu speichern +6. ✅ `callAiWithLooping()` anpassen, um `templateStructure` aus `promptArgs` zu holen (mit Validierung) und an `buildContinuationContext()` zu übergeben +7. ✅ Alle Prompt-Builder um `templateStructure` Parameter erweitern (als expliziten Parameter, nicht extrahieren!) + +### Phase 2: BasePrompt als Parameter +8. ✅ Alle Prompt-Erstellungs-Funktionen anpassen, um `basePrompt` in `promptArgs` zu speichern (oder aus `prompt` Parameter verwenden) +9. ✅ `callAiWithLooping()` anpassen, um `basePrompt` aus `promptArgs` zu holen (mit Validierung) +10. ✅ Alle Prompt-Builder um `basePrompt` Parameter erweitern (als expliziten Parameter, nicht regenerieren!) +11. ✅ BasePrompt als Parameter übergeben (garantiert Identität mit initialem Prompt) + +### Phase 3: Prompt-Builder-Konsolidierung und einheitliche Continuation-Prompt-Struktur +12. ✅ `buildSectionPromptWithContinuation()` konsolidieren (3 → 1) +13. ✅ `buildCodeStructurePromptWithContinuation()` implementieren (fehlt aktuell) +14. ✅ Alle Prompt-Builder als Klassenmethoden definieren (nicht nested) +15. ✅ **Alle Prompt-Builder auf einheitliche Continuation-Prompt-Struktur umstellen** (siehe "Einheitliche Continuation-Prompt-Struktur") +16. ✅ `continuationContext` als NICHT-Optional Parameter (wird immer übergeben, Typ: `ContinuationContext`) + +### Phase 4: Einheitliche Signatur +17. ✅ Alle Builder auf einheitliche Signatur umstellen: + - `continuationContext: ContinuationContext` (Pydantic-Modell, NICHT Optional) + - `templateStructure: str` (expliziter Parameter) + - `basePrompt: str` (expliziter Parameter) + - `promptArgs: Any` (während Migration noch Dict, später Dataclass) + +### Phase 5: Typsicherheit mit Dataclasses +18. ✅ Dataclasses für Parameter-Strukturen einführen (`SectionPromptArgs`, `ChapterStructurePromptArgs`, `CodeContentPromptArgs`, `CodeStructurePromptArgs`) +19. ✅ Schrittweise Migration von `**kwargs` zu Dataclass-Parametern +20. ✅ Alle Builder auf Dataclass-basierte Parameter umstellen (`promptArgs: SectionPromptArgs` statt `promptArgs: Any`) + +--- + diff --git a/reviews/20260111 doc_session_handling_analysis.md b/reviews/20260111 doc_session_handling_analysis.md new file mode 100644 index 0000000..664eb5a --- /dev/null +++ b/reviews/20260111 doc_session_handling_analysis.md @@ -0,0 +1,250 @@ +# Session Handling Analysis for Horizontal Scaling + +## Executive Summary + +**✅ YES, your application is STATELESS and ready for horizontal scaling with load balancers.** + +The session handling architecture is designed to work across multiple gateway instances without requiring sticky sessions or shared in-memory storage. Users will **NOT** lose their sessions when API calls hit different gateway instances. + +--- + +## Architecture Overview + +### Backend Session Management (Gateway) + +#### 1. **JWT Token-Based Authentication** +- **Location**: `gateway/modules/auth/authentication.py` +- **Token Storage**: JWT tokens are stored in **httpOnly cookies** (`auth_token` and `refresh_token`) +- **Token Format**: Self-contained JWT tokens with claims including: + - `sub` (username) + - `userId` + - `mandateId` + - `jti` (token ID) + - `sid` (session ID) + - `authenticationAuthority` + - `exp` (expiration) + +#### 2. **Database-Backed Token Validation** +- **Location**: `gateway/modules/interfaces/interfaceDbAppObjects.py` +- **Token Table**: All tokens are stored in a `Token` database table with fields: + - `id` (jti - token ID) + - `userId` + - `authority` + - `sessionId` + - `mandateId` + - `status` (ACTIVE/REVOKED) + - `expiresAt` + - `revokedAt`, `revokedBy`, `reason` + +- **Validation Process** (per request): + 1. JWT token is extracted from httpOnly cookie or Authorization header + 2. Token is decoded and validated (signature, expiration) + 3. Token ID (`jti`) is extracted from the JWT payload + 4. **Database query** is performed to verify: + - Token exists in database + - Token status is ACTIVE + - Token matches user, session, and mandate context + 5. User is retrieved from database based on token claims + +**Key Code Reference** (`gateway/modules/auth/authentication.py:141-191`): +```python +# For LOCAL gateway JWTs, enforce DB-backed token validity and revocation +if tokenId: + db_tokens = dbApp.getRecordset(Token, recordFilter={"id": tokenId}) + +if db_tokens: + db_token = db_tokens[0] + token_authority = str(db_token.get("authority", "")).lower() + if token_authority == str(AuthAuthority.LOCAL.value): + # Must be active and match user/session/mandate + active_token = appInterface.findActiveTokenById( + tokenId=tokenId, + userId=user.id, + authority=AuthAuthority.LOCAL, + sessionId=sessionId, + mandateId=str(mandateId) if mandateId else None, + ) + if not active_token: + raise credentialsException +``` + +#### 3. **No In-Memory Session Storage** +- ✅ **No Redis** - No Redis or similar caching layer found +- ✅ **No Memcached** - No memcached usage found +- ✅ **No In-Memory Sessions** - All session state is in the database +- ✅ **Stateless Design** - Each request is independently validated + +#### 4. **Session Management** +- **Session ID**: Generated on login (`uuid.uuid4()`) and stored in: + - JWT token claim (`sid`) + - Token database record (`sessionId`) +- **Logout**: Revokes all tokens for a session by updating database records (sets `status=REVOKED`) +- **Token Refresh**: Creates new tokens and stores them in database + +**Key Code Reference** (`gateway/modules/routes/routeSecurityLocal.py:92-131`): +```python +# Create session id and include in token claims for session-scoped logout +session_id = str(uuid.uuid4()) +token_data["sid"] = session_id + +# Create access token + set cookie +access_token, _access_expires = createAccessToken(token_data) +setAccessTokenCookie(response, access_token) + +# Save access token to database +token = Token( + id=jti, + userId=user.id, + authority=AuthAuthority.LOCAL, + tokenAccess=access_token, + tokenType="bearer", + expiresAt=expires_at.timestamp(), + sessionId=session_id, + mandateId=str(user.mandateId) +) +userInterface.saveAccessToken(token) +``` + +### Frontend Session Management + +#### 1. **Cookie-Based Token Storage** +- **Location**: `frontend_agents/public/js/security/auth.js` and `frontend_agents/public/js/shared/apiCalls.js` +- **Storage Method**: Tokens are stored in **httpOnly cookies** (not localStorage or sessionStorage) +- **Automatic Transmission**: Cookies are automatically sent with requests using `credentials: 'include'` + +**Key Code Reference** (`frontend_agents/public/js/shared/apiCalls.js:151-153`): +```javascript +// Note: With httpOnly cookies, we don't need to manually add Authorization header +// The browser automatically includes cookies with credentials: 'include' +``` + +#### 2. **CSRF Token Storage** +- **Location**: `sessionStorage` (client-side only) +- **Purpose**: CSRF protection, not session state +- **Note**: CSRF tokens can be regenerated if lost, so this doesn't affect session persistence + +--- + +## Horizontal Scaling Compatibility + +### ✅ **Fully Compatible - No Issues** + +#### Why It Works: + +1. **Stateless Backend** + - Each gateway instance validates tokens independently + - No shared in-memory state between instances + - All state is in the shared database + +2. **Database as Single Source of Truth** + - Token validation queries the database on every request + - Token revocation updates the database + - All instances see the same token state + +3. **Cookie-Based Tokens** + - Cookies are sent by the browser to whichever instance handles the request + - No server-side session storage needed + - Load balancer doesn't need sticky sessions + +4. **JWT Self-Contained Claims** + - Token contains all necessary user context + - Database validation ensures token hasn't been revoked + - No need to look up session state from another instance + +### Load Balancer Configuration + +**Recommended Settings:** +- ✅ **Session Affinity**: **NOT REQUIRED** (can use round-robin or least-connections) +- ✅ **Health Checks**: Standard HTTP health checks +- ✅ **Cookie Handling**: No special configuration needed (browser handles cookies automatically) + +### Potential Considerations + +#### 1. **Database Connection Pooling** +- Ensure each gateway instance has proper database connection pooling +- Database should handle concurrent connections from multiple instances +- **Status**: ✅ Should work fine if database is configured for multiple connections + +#### 2. **CSRF Token Regeneration** +- CSRF tokens stored in `sessionStorage` may be lost if user switches instances +- **Impact**: Minimal - CSRF tokens are regenerated automatically +- **Code Reference**: `frontend_agents/public/js/shared/apiCalls.js:186-203` handles CSRF token generation + +#### 3. **Token Refresh Race Conditions** +- If multiple requests refresh tokens simultaneously, ensure database handles concurrent updates +- **Status**: ✅ Current implementation uses database transactions (via `saveAccessToken`) + +#### 4. **Cookie Domain and Path** +- Ensure cookies are set with correct domain/path for load balancer +- **Current Settings** (`gateway/modules/auth/jwtService.py:58-66`): + - `path="/"` ✅ + - `samesite="strict"` ✅ + - `httponly=True` ✅ + - `secure` (based on HTTPS) ✅ + +--- + +## Testing Recommendations + +### 1. **Multi-Instance Test** +- Deploy 2+ gateway instances behind a load balancer +- Login on one instance +- Make requests that hit different instances +- Verify session persists across instances + +### 2. **Token Revocation Test** +- Login on instance A +- Logout on instance B +- Verify token is revoked (cannot make requests on instance A) + +### 3. **Concurrent Request Test** +- Make multiple simultaneous requests +- Verify all requests succeed regardless of which instance handles them + +### 4. **Database Connection Test** +- Monitor database connections from multiple instances +- Verify connection pooling works correctly +- Check for connection leaks + +--- + +## Summary + +| Aspect | Status | Notes | +|--------|--------|-------| +| **Stateless Backend** | ✅ YES | No in-memory session storage | +| **Database-Backed** | ✅ YES | All token state in database | +| **Cookie-Based** | ✅ YES | httpOnly cookies, auto-sent by browser | +| **Load Balancer Ready** | ✅ YES | No sticky sessions needed | +| **Horizontal Scaling** | ✅ READY | Can scale to multiple instances | + +### Conclusion + +**Your application is fully ready for horizontal scaling.** The session handling architecture is stateless and database-backed, which means: + +1. ✅ Users will **NOT** lose sessions when requests hit different instances +2. ✅ Load balancer can use **round-robin** or **least-connections** (no sticky sessions needed) +3. ✅ Token validation works independently on each instance +4. ✅ Token revocation works across all instances (via database) + +The only shared state is in the database, which you've confirmed will be a single logical instance. This is the correct architecture for horizontal scaling. + +--- + +## Files Analyzed + +### Backend +- `gateway/modules/auth/authentication.py` - Token validation +- `gateway/modules/auth/jwtService.py` - JWT creation and cookie management +- `gateway/modules/routes/routeSecurityLocal.py` - Login/logout endpoints +- `gateway/modules/interfaces/interfaceDbAppObjects.py` - Token database operations +- `gateway/modules/datamodels/datamodelSecurity.py` - Token data model + +### Frontend +- `frontend_agents/public/js/security/auth.js` - Authentication logic +- `frontend_agents/public/js/shared/apiCalls.js` - API calls with cookie handling + +--- + +*Analysis Date: 2025-01-27* +*Analyzed by: AI Assistant* diff --git a/reviews/20260111 doc_session_handling_summary_operations.md b/reviews/20260111 doc_session_handling_summary_operations.md new file mode 100644 index 0000000..d3acc1a --- /dev/null +++ b/reviews/20260111 doc_session_handling_summary_operations.md @@ -0,0 +1,94 @@ +# PowerOn Gateway - Session Handling for Horizontal Scaling + +## Executive Summary + +**Status: ✅ READY FOR HORIZONTAL SCALING** + +The PowerOn Gateway uses a stateless, database-backed session architecture that supports horizontal scaling with load balancers. User sessions persist across multiple gateway instances without requiring sticky sessions or shared in-memory storage. + +--- + +## Architecture Overview + +### Session Management Approach + +**Authentication Method**: JWT tokens stored in httpOnly cookies +- Access token: `auth_token` cookie +- Refresh token: `refresh_token` cookie +- Tokens contain user context (userId, mandateId, sessionId) + +**Token Validation**: Database-backed +- All tokens stored in `Token` database table +- Each request validates token against database +- Token status: ACTIVE or REVOKED +- No in-memory session storage (no Redis/Memcached) + +**Key Characteristics**: +- ✅ Stateless backend design +- ✅ Database as single source of truth +- ✅ Cookie-based token transmission +- ✅ Independent token validation per instance + +--- + +## Load Balancer Configuration + +### Recommended Settings + +| Setting | Value | Notes | +|---------|-------|-------| +| **Session Affinity** | **NOT REQUIRED** | Can use round-robin or least-connections | +| **Health Checks** | Standard HTTP | Standard endpoint health checks | +| **Cookie Handling** | Default | Browser handles cookies automatically | +| **Sticky Sessions** | **NOT NEEDED** | Gateway instances are stateless | + +### Cookie Configuration + +Current cookie settings (configured in code): +- `path="/"` - Available across all paths +- `samesite="strict"` - CSRF protection +- `httponly=True` - XSS protection +- `secure` - Enabled when using HTTPS + +**No special load balancer cookie configuration required.** + +--- + +## Deployment Requirements + +### Database Configuration + +**Requirements for Logical Database**: +- Single logical database instance (shared across all gateway instances) +- Database must be accessible from all gateway instances +- Database must support concurrent connections from multiple instances +- Each gateway instance requires proper database connection pooling +- Database should handle concurrent token validation queries efficiently + +**Token Table**: Contains all session state +- Token ID (jti) +- User ID, Session ID, Mandate ID +- Status (ACTIVE/REVOKED) +- Expiration timestamps + +### Gateway Instance Configuration + +Each gateway instance: +- ✅ Operates independently +- ✅ Validates tokens via database queries +- ✅ No shared state with other instances +- ✅ Can be added/removed without affecting active sessions + + +### Key Points for Operations + +1. ✅ **No sticky sessions required** - Load balancer can distribute requests freely +2. ✅ **Shared logical database required** - All instances access the same database +3. ✅ **Instances are independent** - Can add/remove instances without downtime +4. ✅ **Sessions persist across instances** - Users won't lose sessions during failover + +--- + + +*Document prepared for Operations Center*, Patrick Motsch, PowerON AG, 2026-01-11 + diff --git a/ui_nyla/feature-userregistration/doc_userregistration_ui_adaptations.md b/ui_nyla/feature-userregistration/doc_userregistration_ui_adaptations.md new file mode 100644 index 0000000..0a250d6 --- /dev/null +++ b/ui_nyla/feature-userregistration/doc_userregistration_ui_adaptations.md @@ -0,0 +1,793 @@ +# User Registration & Password Reset - UI Adaptations + +## Overview + +This document describes the necessary UI changes and adaptations required in the Nyla frontend (`frontend_nyla`) to implement the magic link-based user authentication process described in `doc_userauth_process_concept.md`. + +**Last Updated**: Based on codebase analysis of current Nyla frontend implementation. + +**Frontend Stack**: React 19, TypeScript, Vite, React Router +**Pattern**: Hooks-based architecture (`useAuthentication.ts`), component-based pages + +## Current Frontend State + +### Existing Pages + +1. **`src/pages/Register.tsx`** + - Contains registration form with: + - Username field (with availability check) + - Password field (required) + - Confirm password field (required) + - Email field + - Full name field + - Language selector (defaults to 'de') + - Uses `useRegister()` hook from `useAuthentication.ts` + - **Needs modification**: Remove password fields, add email-only registration + +2. **`src/pages/Login.tsx`** + - Contains login form with: + - Username field + - Password field + - Microsoft authentication button + - Google authentication button (placeholder) + - Registration link + - **Missing**: Password reset button/link + +3. **`src/hooks/useAuthentication.ts`** + - Contains `useRegister()` hook that sends password + - Contains `useAuth()` hook for login + - Contains `useUsernameAvailability()` hook + - **Needs modification**: Update `useRegister()` to handle no-password registration + - **Missing**: Password reset request and reset password hooks + +4. **`src/api.ts`** + - Axios instance with interceptors + - Base URL from environment variables + - **No changes needed** - existing API setup is sufficient + +## Required UI Changes + +### 1. Login Page (`src/pages/Login.tsx`) + +#### Changes Required: +- Add "Password Reset" link/button below the login form +- Link should navigate to `/password-reset-request` route +- Style should match existing link styles (use `styles.textButton`) + +#### Implementation: +```tsx +// Add after the disclaimer div, before the login button +
+ +
+``` + +#### Styling Considerations: +- Use existing CSS classes from `Login.module.css` +- Match styling with registration link +- Ensure responsive design matches login page layout + +### 2. Registration Page (`src/pages/Register.tsx`) + +#### Changes Required: +- **Remove** password and confirm password fields +- **Keep** username, email, fullName fields +- Update form validation to not require password +- Update success message to indicate email will be sent +- Add spam folder reminder message + +#### Implementation Changes: + +**Remove password-related state:** +```tsx +// Remove these lines: +const [passwordFocused, setPasswordFocused] = useState(false); +const [confirmPasswordFocused, setConfirmPasswordFocused] = useState(false); + +// Update formData interface: +interface RegisterFormData { + username: string; + // password: string; // REMOVED + // confirmPassword: string; // REMOVED + email: string; + fullName: string; +} + +// Update initial state: +const [formData, setFormData] = useState({ + username: '', + // password: '', // REMOVED + // confirmPassword: '', // REMOVED + email: '', + fullName: '' +}); +``` + +**Update validation:** +```tsx +const validateForm = (): boolean => { + // Remove password checks + if (!formData.username || !formData.email || !formData.fullName) { + setValidationError('Bitte füllen Sie alle Pflichtfelder aus.'); + return false; + } + + if (!formData.email.includes('@')) { + setValidationError('Bitte geben Sie eine gültige E-Mail-Adresse ein.'); + return false; + } + + return true; +}; +``` + +**Update success message:** +```tsx +// After successful registration: +await register(registrationData); +navigate('/login', { + state: { + registered: true, + message: 'Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mail, um Ihr Passwort zu setzen. Falls Sie keine E-Mail erhalten, prüfen Sie bitte auch Ihren Spam-Ordner.' + } +}); +``` + +**Remove password input fields from JSX:** +```tsx +// Remove these divs: +{/*
+ + +
+
+ + +
*/} +``` + +**Add info message:** +```tsx +// Add after the disclaimer, before the submit button: +
+

Nach der Registrierung erhalten Sie eine E-Mail mit einem Link zum Setzen Ihres Passworts.

+

Bitte prüfen Sie auch Ihren Spam-Ordner, falls Sie keine E-Mail erhalten.

+
+``` + +### 3. New Page: Password Reset Request (`src/pages/PasswordResetRequest.tsx`) + +#### Purpose: +Allow users to request a password reset by entering their email address. + +#### Structure: +- Similar layout to `Register.tsx` and `Login.tsx` +- Single email input field +- Submit button +- Link back to login page +- Success/error message area + +#### Implementation: +```tsx +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { usePasswordResetRequest } from '../hooks/useAuthentication'; +import styles from './PasswordResetRequest.module.css'; + +function PasswordResetRequest() { + const navigate = useNavigate(); + const { requestPasswordReset, error, isLoading } = usePasswordResetRequest(); + const [email, setEmail] = useState(''); + const [emailFocused, setEmailFocused] = useState(false); + const [successMessage, setSuccessMessage] = useState(null); + + useEffect(() => { + document.title = "PowerOn AI Platform - Passwort zurücksetzen"; + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!email || !email.includes('@')) { + return; + } + + try { + await requestPasswordReset(email); + setSuccessMessage('Falls ein Konto mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet. Bitte prüfen Sie Ihre E-Mail und auch Ihren Spam-Ordner.'); + + // Redirect to login after showing message + setTimeout(() => { + navigate('/login', { + state: { + message: 'Falls ein Konto mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet.' + } + }); + }, 3000); + } catch (err) { + console.error('Password reset request failed:', err); + } + }; + + return ( +
+
+
+
+ Power + On +
+
+
+
+
+ {error && ( +
{error}
+ )} + {successMessage && ( +
{successMessage}
+ )} + +

Passwort zurücksetzen

+ +
+ setEmail(e.target.value)} + onFocus={() => setEmailFocused(true)} + onBlur={() => setEmailFocused(false)} + className={`${styles.input} ${emailFocused || email ? styles.focused : ''}`} + /> + +
+ + + +
+ Zurück zum + +
+
+
+
+
+
+ ); +} + +export default PasswordResetRequest; +``` + +#### CSS Module (`src/pages/PasswordResetRequest.module.css`): +- Copy styles from `Register.module.css` or `Login.module.css` +- Add styles for `.success` message (green background) +- Add styles for `.title` heading + +### 4. New Page: Password Reset (`src/pages/ResetPassword.tsx`) + +#### Purpose: +Allow users to set a new password using the token from the magic link. + +#### Structure: +- Similar layout to `Register.tsx` +- Password field (with strength indicator) +- Confirm password field +- Submit button +- Extract token from URL parameter (`?token=`) +- Success/error message area + +#### Implementation: +```tsx +import { useState, useEffect } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useResetPassword } from '../hooks/useAuthentication'; +import styles from './ResetPassword.module.css'; + +function ResetPassword() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { resetPassword, error, isLoading } = useResetPassword(); + + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordFocused, setPasswordFocused] = useState(false); + const [confirmPasswordFocused, setConfirmPasswordFocused] = useState(false); + const [validationError, setValidationError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + + const token = searchParams.get('token'); + + useEffect(() => { + document.title = "PowerOn AI Platform - Neues Passwort setzen"; + + // Validate token format (UUID) + if (!token || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(token)) { + setValidationError('Ungültiger oder fehlender Reset-Token.'); + } + }, [token]); + + const validateForm = (): boolean => { + if (!password || !confirmPassword) { + setValidationError('Bitte füllen Sie alle Felder aus.'); + return false; + } + + if (password.length < 8) { + setValidationError('Passwort muss mindestens 8 Zeichen lang sein.'); + return false; + } + + if (password !== confirmPassword) { + setValidationError('Die Passwörter stimmen nicht überein.'); + return false; + } + + return true; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm() || !token) { + return; + } + + try { + await resetPassword(token, password); + setSuccessMessage('Passwort erfolgreich gesetzt! Sie werden zum Login weitergeleitet...'); + + // Redirect to login after 3 seconds + setTimeout(() => { + navigate('/login', { + state: { + message: 'Passwort erfolgreich gesetzt. Bitte melden Sie sich an.' + } + }); + }, 3000); + } catch (err) { + console.error('Password reset failed:', err); + } + }; + + if (!token) { + return ( +
+
+
+ Ungültiger oder fehlender Reset-Token. Bitte fordern Sie einen neuen Reset-Link an. +
+ +
+
+ ); + } + + return ( +
+
+
+
+ Power + On +
+
+
+
+
+

Neues Passwort setzen

+ + {(validationError || error) && ( +
{validationError || error}
+ )} + {successMessage && ( +
{successMessage}
+ )} + +
+ setPassword(e.target.value)} + onFocus={() => setPasswordFocused(true)} + onBlur={() => setPasswordFocused(false)} + className={`${styles.input} ${passwordFocused || password ? styles.focused : ''}`} + /> + +
+ Mindestens 8 Zeichen + +
+ setConfirmPassword(e.target.value)} + onFocus={() => setConfirmPasswordFocused(true)} + onBlur={() => setConfirmPasswordFocused(false)} + className={`${styles.input} ${confirmPasswordFocused || confirmPassword ? styles.focused : ''}`} + /> + +
+ + + +
+ Zurück zum + +
+
+
+
+
+
+ ); +} + +export default ResetPassword; +``` + +#### CSS Module (`src/pages/ResetPassword.module.css`): +- Copy styles from `Register.module.css` +- Add styles for `.success` message +- Add styles for `.title` heading +- Add styles for `.passwordHint` (small text below password field) + +### 5. Authentication Hooks (`src/hooks/useAuthentication.ts`) + +#### New Hooks Required: + +1. **`usePasswordResetRequest()`** +```tsx +export function usePasswordResetRequest() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const requestPasswordReset = async (email: string): Promise => { + setIsLoading(true); + setError(null); + + try { + const response = await api.post('/api/local/password-reset-request', { email }); + + // Backend always returns success (security: don't reveal if email exists) + // No need to check response data + } catch (error: any) { + let errorMessage = 'An error occurred during password reset request'; + + if (error.response) { + if (error.response.data?.detail) { + if (Array.isArray(error.response.data.detail)) { + errorMessage = error.response.data.detail.map((err: any) => err.msg).join(', '); + } else { + errorMessage = error.response.data.detail; + } + } + } else if (error.request) { + errorMessage = 'No response received from server'; + } else { + errorMessage = error.message; + } + + setError(errorMessage); + throw error; + } finally { + setIsLoading(false); + } + }; + + return { + requestPasswordReset, + error, + isLoading + }; +} +``` + +2. **`useResetPassword()`** +```tsx +export function useResetPassword() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const resetPassword = async (token: string, password: string): Promise => { + setIsLoading(true); + setError(null); + + try { + const response = await api.post('/api/local/password-reset', { token, password }); + + // Success - password reset completed + } catch (error: any) { + let errorMessage = 'An error occurred during password reset'; + + if (error.response) { + if (error.response.data?.detail) { + if (Array.isArray(error.response.data.detail)) { + errorMessage = error.response.data.detail.map((err: any) => err.msg).join(', '); + } else { + errorMessage = error.response.data.detail; + } + } + } else if (error.request) { + errorMessage = 'No response received from server'; + } else { + errorMessage = error.message; + } + + setError(errorMessage); + throw error; + } finally { + setIsLoading(false); + } + }; + + return { + resetPassword, + error, + isLoading + }; +} +``` + +#### Update `useRegister()` Hook: + +**Modify the `register` function:** +```tsx +const register = async (userData: RegisterData): Promise => { + setIsLoading(true); + setError(null); + + try { + // Remove password from dataToSend + const dataToSend = { + userData: { + username: userData.username, + email: userData.email, + fullName: userData.fullName, + language: userData.language || 'de', + enabled: userData.enabled !== undefined ? userData.enabled : true, + privilege: userData.privilege || 'user' + } + // password: userData.password // REMOVED + }; + + const response = await api.post('/api/local/register', dataToSend, { + headers: { + 'Content-Type': 'application/json' + } + }); + + return { + success: true, + message: 'Registration successful. Please check your email to set your password.', + user: response.data + }; + } catch (error: any) { + // ... existing error handling ... + } finally { + setIsLoading(false); + } +}; +``` + +**Update `RegisterData` interface:** +```tsx +interface RegisterData { + username: string; + // password: string; // REMOVED - no longer required + email: string; + fullName: string; + language?: string; + enabled?: boolean; + privilege?: string; +} +``` + +### 6. Routing Configuration + +#### Update `src/App.tsx` or routing configuration: + +Add routes for new pages: +```tsx +import PasswordResetRequest from './pages/PasswordResetRequest'; +import ResetPassword from './pages/ResetPassword'; + +// In your routes: +} /> +} /> +``` + +## UI/UX Considerations + +### Error Handling + +1. **Registration Errors**: + - Username already exists → Show error on username field (already implemented) + - Email already exists → Show generic success (security: don't reveal email exists) + - Email sending fails → Show generic success (don't reveal email issues) + +2. **Password Reset Request Errors**: + - Invalid email format → Show error on email field + - Email not found → Show generic success (security: don't reveal email doesn't exist) + - Rate limiting → Show error message + +3. **Password Reset Errors**: + - Invalid/expired token → Show error message, link back to password reset request + - Password too weak → Show specific requirements + - Password mismatch → Show error on confirm password field + +### Success Messages + +1. **Registration Success**: + ``` + "Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mail, um Ihr Passwort zu setzen. + Falls Sie keine E-Mail erhalten, prüfen Sie bitte auch Ihren Spam-Ordner." + ``` + +2. **Password Reset Request Success**: + ``` + "Falls ein Konto mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet. + Bitte prüfen Sie Ihre E-Mail und auch Ihren Spam-Ordner." + ``` + +3. **Password Reset Success**: + ``` + "Passwort erfolgreich gesetzt! Sie werden zum Login weitergeleitet..." + ``` + +### Accessibility + +- All form fields should have proper labels (already implemented with floating labels) +- Error messages should be associated with form fields using ARIA attributes +- Success messages should be announced to screen readers +- Form validation should provide clear, actionable feedback + +### Responsive Design + +- All pages should work on mobile devices (existing styles should handle this) +- Form layouts should adapt to smaller screens +- Buttons should be appropriately sized for touch interfaces +- Error messages should be readable on all screen sizes + +## Testing Checklist + +### Registration Flow +- [ ] User can register without password +- [ ] Email validation works correctly +- [ ] Success message displays correctly +- [ ] Redirect to login works +- [ ] Error handling for duplicate username +- [ ] Error handling for duplicate email (should show generic success) + +### Password Reset Request Flow +- [ ] User can access password reset request page from login +- [ ] Email validation works correctly +- [ ] Success message displays correctly +- [ ] Redirect to login works +- [ ] Error handling for invalid email format +- [ ] Error handling for rate limiting + +### Password Reset Flow +- [ ] User can access reset page with valid token +- [ ] Token extraction from URL works +- [ ] Password validation works correctly +- [ ] Password confirmation validation works +- [ ] Success message displays correctly +- [ ] Redirect to login works after 3 seconds +- [ ] Error handling for invalid token +- [ ] Error handling for expired token +- [ ] Error handling for weak password + +### Integration Testing +- [ ] End-to-end registration flow works +- [ ] End-to-end password reset flow works +- [ ] Email links work correctly +- [ ] Token expiration handling works +- [ ] Multiple reset requests invalidate old tokens + +## Implementation Order + +1. **Backend Changes First** (prerequisites): + - Add resetToken fields to UserInDB model + - Implement password reset endpoints + - Implement email sending functionality + +2. **Frontend Hooks**: + - Add `usePasswordResetRequest()` hook + - Add `useResetPassword()` hook + - Update `useRegister()` hook (remove password requirement) + +3. **Frontend Pages**: + - Create `PasswordResetRequest.tsx` + - Create `ResetPassword.tsx` + - Update `Login.tsx` (add reset button) + - Update `Register.tsx` (remove password fields) + +4. **Routing**: + - Add routes for new pages + +5. **Styling**: + - Create CSS modules for new pages + - Add success message styles + - Ensure consistent styling + +6. **Testing**: + - Test each flow independently + - Test integration between frontend and backend + - Test error scenarios + - Test edge cases + +## Notes + +- All text should be in German to match existing UI (`Login.tsx` uses German) +- CSS modules should match existing patterns from `Register.module.css` and `Login.module.css` +- Form validation should use existing patterns from `Register.tsx` +- Error handling should use existing patterns (error state, error display) +- Success messages should use new success state pattern +- API calls should use existing `api.ts` instance +- Hooks should follow existing patterns from `useAuthentication.ts` +- Token validation should check UUID format before making API call +- Use React Router's `useSearchParams` for token extraction from URL + +## File Structure + +``` +frontend_nyla/ +├── src/ +│ ├── pages/ +│ │ ├── Login.tsx (MODIFY - add password reset link) +│ │ ├── Register.tsx (MODIFY - remove password fields) +│ │ ├── PasswordResetRequest.tsx (NEW) +│ │ ├── ResetPassword.tsx (NEW) +│ │ ├── Login.module.css (MODIFY - add password reset link styles) +│ │ ├── Register.module.css (MODIFY - add info message styles) +│ │ ├── PasswordResetRequest.module.css (NEW) +│ │ └── ResetPassword.module.css (NEW) +│ ├── hooks/ +│ │ └── useAuthentication.ts (MODIFY - update useRegister, add new hooks) +│ └── App.tsx (MODIFY - add routes) +```