1
0

V1.1 Arbeitspakete angelegt für Claude

This commit is contained in:
2026-04-08 22:59:11 +02:00
parent 9c2a205137
commit a51fcf7055

View File

@@ -0,0 +1,596 @@
# V1.1 Arbeitspakete
> **Aktive Erweiterung:** Zusätzliche KI-Provider-Familie **Anthropic Claude** über die native Messages API, neben der bestehenden OpenAI-kompatiblen Anbindung. Bewusst minimale Erweiterung des freigegebenen Basisstands.
> **Ablage im Repository:** `docs/workpackages/V1.1 - Arbeitspakete.md`
---
## 0. Lesereihenfolge für jedes Arbeitspaket
Vor jedem AP **vollständig** lesen:
1. `CLAUDE.md`
2. `docs/specs/technik-und-architektur.md`
3. `docs/specs/fachliche-anforderungen.md`
4. dieses Dokument: Abschnitte 1 bis 6
5. **nur** das aktive Arbeitspaket aus Abschnitt 7
Nicht vorgreifen. Nicht raten. Bei echter Unklarheit knapp benennen statt erfinden.
---
## 1. Arbeitsweise (verbindlich)
Diese Regeln ersetzen die nicht vorhandene `WORKFLOW.md` und gelten für alle APs in diesem Dokument.
### 1.1 Scope-Disziplin
- Es wird **ausschließlich** das aktive Arbeitspaket umgesetzt.
- Keine Inhalte späterer Arbeitspakete vorwegnehmen.
- Keine kosmetischen Refactorings ohne direkten Bezug zum AP.
- Keine Umbenennungen außerhalb des AP-Scopes.
- Vor Änderungen die betroffenen Klassen über Typsuche im Repo lokalisieren, **nicht** über vermutete Pfade.
### 1.2 Build- und Testpflicht
Build-Kommando vom Projekt-Root, identisch für alle APs:
```
.\mvnw.cmd clean verify -pl pdf-umbenenner-domain,pdf-umbenenner-application,pdf-umbenenner-adapter-out,pdf-umbenenner-adapter-in-cli,pdf-umbenenner-bootstrap --also-make
```
- Nach jeder substanziellen Änderung: Build ausführen.
- Vor Abschluss eines AP: Build muss **fehlerfrei** sein, alle Tests grün.
- Schlägt der Build fehl: Ursache sauber beheben, nicht kaschieren.
- Bestehende Tests dürfen nicht stillschweigend gelöscht oder deaktiviert werden. Sie werden bei Bedarf **angepasst** und der Grund wird im AP-Output dokumentiert.
### 1.3 Pflicht-Tests pro AP
- Jede neue Klasse mit fachlich oder technisch relevanter Logik bekommt mindestens einen Unit-Test.
- Jede in einem AP geänderte Klasse, die bisher Tests hatte, behält Tests; betroffene Tests werden angepasst.
- Pro AP gibt es eine Liste **kritischer Pflicht-Testfälle** (siehe jeweiliges AP). Diese sind namentlich umzusetzen.
- Darüber hinaus gilt die übliche Repo-Praxis (Coverage, PIT-Mutationstests in den unmittelbar betroffenen Modulen, soweit bereits etabliert).
### 1.4 Dokumentation
Pro AP werden mitgepflegt, soweit relevant:
- JavaDoc und `package-info` der berührten Klassen
- Konfigurationsbeispiele
- unmittelbar betroffene Repository-Dokumente
### 1.5 Naming-Regel
In Code, Kommentaren und JavaDoc dürfen **keine** Versions- oder AP-Bezeichner erscheinen:
- Verboten: `V1.0`, `V1.1`, `M1``M8`, `AP-001``AP-006`
- Stattdessen: zeitlose technische Bezeichnungen.
### 1.6 Pflicht-Output-Format am Ende jedes AP
Am Ende der AP-Bearbeitung gibt Sonnet **genau** diesen Block aus:
```
- Scope erfüllt: ja/nein
- Geänderte Dateien:
- <Pfad>
- ...
- Neue Dateien:
- <Pfad>
- ...
- Build-Kommando: <verwendetes Kommando>
- Build-Status: ERFOLGREICH / FEHLGESCHLAGEN
- Pflicht-Tests umgesetzt: <Liste der namentlich geforderten Testfälle>
- Offene Punkte: keine / <Beschreibung>
- Risiken: keine / <Beschreibung>
```
---
## 2. Erweiterungsziel und Nicht-Ziele
### 2.1 Ziel
- Der bestehende OpenAI-kompatible KI-Weg bleibt unverändert nutzbar.
- Zusätzlich wird die **native Anthropic Messages API** als zweite, gleichwertig unterstützte Provider-Familie integriert.
- Genau **ein** Provider ist pro Lauf aktiv ausschließlich über Konfiguration.
- Kein automatischer Fallback, keine Parallelnutzung, keine Profilverwaltung.
- Der fachliche KI-Vertrag (`NamingProposal`) bleibt unverändert.
- Bestehende Properties-Dateien werden beim ersten Start kontrolliert ins neue Schema migriert; vorher wird automatisch eine `.bak`-Sicherung angelegt.
### 2.2 Explizit nicht Bestandteil
- Provider-Familien jenseits der zwei explizit unterstützten
- Profilverwaltung mit mehreren Konfigurationen je Provider-Familie
- automatische Fallback-Umschaltung
- parallele Nutzung mehrerer Provider in einem Lauf
- Änderung des fachlichen Ergebnisvertrags
- Änderung der Dateinamensregeln, Retry-Regeln, Batch-Betriebsmodells
- Persistenz- oder Schemaänderungen jenseits der einen additiven Provider-Identifikator-Spalte
### 2.3 Architekturtreue (unverhandelbar)
- strikte hexagonale Architektur, Abhängigkeiten zeigen nach innen
- `AiNamingPort` bleibt provider-neutral
- provider-spezifische Endpunkte, Header, Auth, Request-/Response-Formate leben **ausschließlich** im jeweiligen Adapter-Out
- keine direkte Adapter-zu-Adapter-Kopplung, keine gemeinsame „abstrakte KI-Adapter"-Zwischenschicht
- die Provider-Auswahl ist eine **Bootstrap-Verdrahtungsentscheidung**
---
## 3. Zielzustand der Konfiguration (verbindlich)
### 3.1 Properties-Schema
```properties
# bestehende, unveränderte Parameter
source.folder=...
target.folder=...
sqlite.file=...
max.retries.transient=...
max.pages=...
max.text.characters=...
prompt.template.file=...
runtime.lock.file=...
log.directory=...
log.level=...
log.ai.sensitive=...
# neue Provider-Auswahl (Pflicht)
ai.provider.active=openai-compatible
# OpenAI-kompatible Provider-Familie
ai.provider.openai-compatible.baseUrl=...
ai.provider.openai-compatible.model=...
ai.provider.openai-compatible.timeoutSeconds=...
ai.provider.openai-compatible.apiKey=...
# Anthropic-Provider-Familie (Claude)
ai.provider.claude.baseUrl=https://api.anthropic.com
ai.provider.claude.model=...
ai.provider.claude.timeoutSeconds=...
ai.provider.claude.apiKey=...
```
### 3.2 Zulässige Werte für `ai.provider.active`
- `openai-compatible`
- `claude`
Jeder andere Wert ist eine ungültige Startkonfiguration und führt zu Exit-Code `1`.
### 3.3 Pflichtwerte je aktivem Provider
| Provider | Pflicht | Optional / mit Default |
|---|---|---|
| `openai-compatible` | `baseUrl`, `model`, `timeoutSeconds`, `apiKey` (Env hat Vorrang) | |
| `claude` | `model`, `timeoutSeconds`, `apiKey` (Env hat Vorrang) | `baseUrl` (Default `https://api.anthropic.com`) |
Für den **inaktiven** Provider werden keine Pflichtwerte erzwungen.
### 3.4 Umgebungsvariablen für API-Schlüssel
| Provider | Umgebungsvariable |
|---|---|
| `openai-compatible` | `OPENAI_COMPATIBLE_API_KEY` |
| `claude` | `ANTHROPIC_API_KEY` |
- Pro Provider gilt: Umgebungsvariable hat **Vorrang** vor dem Properties-Wert derselben Provider-Familie.
- Schlüssel verschiedener Provider werden **niemals** vermischt.
- Wenn der Betrieb bisher eine andere Umgebungsvariable für den OpenAI-kompatiblen Key genutzt hat, ist diese vom Betreiber auf `OPENAI_COMPATIBLE_API_KEY` umzustellen. Das ist im Abschlussnachweis (AP-006) zu dokumentieren.
### 3.5 Legacy-Form (vor V1.1)
Eindeutig erkennbar an mindestens einem der flachen Schlüssel:
```
api.baseUrl
api.model
api.timeoutSeconds
api.key
```
ohne Vorhandensein von `ai.provider.active`.
---
## 4. Anthropic Messages API verbindlicher technischer Faktenblock
> Quelle: offizielle Claude API-Dokumentation. Diese Werte sind verbindlich und nicht zu erfinden, abzuleiten oder zu „verbessern".
### 4.1 Endpoint und Methode
- Methode: `POST`
- URL: `{baseUrl}/v1/messages`
- Default-`baseUrl`: `https://api.anthropic.com`
### 4.2 Pflicht-Header
| Header | Wert |
|---|---|
| `x-api-key` | API-Schlüssel aus `ANTHROPIC_API_KEY` (Env) bzw. `ai.provider.claude.apiKey` (Properties) |
| `anthropic-version` | `2023-06-01` |
| `content-type` | `application/json` |
Nicht `Authorization: Bearer …` verwenden. Anthropic nutzt `x-api-key`.
### 4.3 Request-Body (relevante Felder)
```json
{
"model": "<modellname aus ai.provider.claude.model>",
"max_tokens": <Integer, > 0, Pflicht>,
"system": "<optional, top-level Feld - NICHT als Message mit role=system>",
"messages": [
{ "role": "user", "content": "<Prompt-Text>" }
]
}
```
- `max_tokens` ist **Pflicht** (Unterschied zu OpenAI). Konkreter Wert: zweckmäßig fest verdrahtet im Adapter, ausreichend groß für die JSON-Antwort der Anwendung. Kein neuer Properties-Schlüssel.
- `system` wird **nicht** als Message mit `role=system` modelliert. Anthropic akzeptiert nur `user` und `assistant` im `messages`-Array; ein System-Prompt geht ausschließlich ins Top-Level-Feld `system`.
- Der bestehende Prompt der Anwendung wird **unverändert** als Inhalt der einen `user`-Message übergeben. Falls der bestehende Prompt-Mechanismus eine System-Komponente kennt, wandert diese in das `system`-Feld; sonst bleibt `system` weg.
### 4.4 Response-Body (relevante Felder)
```json
{
"id": "...",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "<die eigentliche Antwort>" }
],
"stop_reason": "...",
"usage": { "input_tokens": 0, "output_tokens": 0 }
}
```
- Der für die Anwendung relevante Text wird **konkateniert aus allen Blöcken in `content` mit `type == "text"`** in Reihenfolge gewonnen.
- Andere Block-Typen werden ignoriert.
- Liefert die API kein einziges `text`-Block, ist das ein technischer Fehler des Adapters (klassifiziert wie ein leerer/unbrauchbarer Antwortinhalt).
### 4.5 Fehlerklassifikation im Claude-Adapter
| Symptom | Klassifikation | Anmerkung |
|---|---|---|
| HTTP 4xx (außer 429) | technischer Fehler | Auth-Fehler (401/403) zählen hier rein |
| HTTP 429 | technischer Fehler | rate limit |
| HTTP 5xx | technischer Fehler | |
| Timeout | technischer Fehler | |
| Verbindung fehlgeschlagen | technischer Fehler | |
| JSON nicht parsebar | technischer Fehler | |
| Kein `content[*].text`-Block | technischer Fehler | |
| Antworttext nicht nach `NamingProposal` parsebar | greift bestehende Antwort-Validierung der Application | nicht im Adapter behandeln |
Alle technischen Adapterfehler werden auf die **bestehende** transiente Fehlersemantik der Anwendung abgebildet. Es entsteht **keine** neue Fehlerkategorie.
---
## 5. Verbindliche Regeln für jedes AP
1. **Minimale Erweiterung.** Nichts ändern, was nicht für die Erweiterung zwingend erforderlich ist.
2. **Einheitlicher fachlicher KI-Vertrag.** `NamingProposal` bleibt unverändert. Keine provider-spezifische Verzweigung in Application/Domain.
3. **Genau ein aktiver Provider.** Kein Fallback, keine Profilverwaltung.
4. **Properties-Datei bleibt führend.** Keine alternative Konfigurationsquelle.
5. **Bestehender OpenAI-Pfad bleibt funktional unverändert.**
6. **Architekturgrenzen** (siehe 2.3) werden niemals durchbrochen.
7. **Rückwärtsverträglichkeit der SQLite-Daten** bleibt erhalten.
8. **Build muss am Ende jedes AP fehlerfrei sein.**
9. **Alle Pflicht-Testfälle des AP** sind umgesetzt.
---
## 6. Granularität und Reihenfolge
Sechs Arbeitspakete in dieser zwingenden Reihenfolge:
| AP | Thema | Risiko | Charakter |
|---|---|---|---|
| AP-001 | Konfigurations-Schema einführen (additiv) | niedrig | reine Erweiterung |
| AP-002 | Legacy-Migration mit `.bak` | mittel | Datei-Umschreibung, geschützt durch Sicherung |
| AP-003 | Bootstrap-Provider-Auswahl + bestehender Adapter umschalten | hoch | Verhaltensänderung im Wiring |
| AP-004 | Persistenz: Provider-Identifikator additiv | mittel | additive DB-Schema-Migration |
| AP-005 | Nativer Anthropic-Adapter implementieren und verdrahten | mittel | neue Adapter-Klasse |
| AP-006 | Regression, Smoke, Doku, Abschlussnachweis | niedrig | Absicherung |
---
# 7. Arbeitspakete
---
## AP-001 Konfigurations-Schema einführen (additiv)
### Voraussetzung
Keine.
### Ziel
Das neue, verschachtelte Properties-Schema (Abschnitt 3.1) wird im Code als parsbare und validierbare Struktur eingeführt. Der bestehende Lese- und Validierungspfad bleibt **unangetastet** das neue Schema wird parallel additiv eingeführt. Es findet **kein** Wechsel im Bootstrap und **keine** Migration in diesem AP.
### Konkret zu erledigende Schritte
1. Im Modul `pdf-umbenenner-application` (oder dem Modul, in dem die heutigen Configuration-Klassen leben per Typsuche lokalisieren) **neue** Konfigurationstypen einführen, mindestens:
- eine Repräsentation einer einzelnen Provider-Konfiguration (Felder: `model`, `timeoutSeconds`, `baseUrl`, `apiKey`)
- eine Repräsentation der Provider-Auswahl (`activeProviderId`) plus Map oder zwei Felder für die beiden Provider-Familien
- einen klar benannten Aufzählungstyp oder konstanten String-Set für die zulässigen Werte `openai-compatible` und `claude`
2. Im Adapter-Out-Modul den **Properties-Parser** so erweitern, dass er die neuen Schlüssel aus Abschnitt 3.1 erkennt und in die neuen Typen aus Schritt 1 einliest. Der bestehende Parser für die alten flachen Schlüssel bleibt **unverändert** lauffähig (parallele Erkennung).
3. Eine **Validierung** für die neuen Typen einführen. Sie prüft:
- `ai.provider.active` ist gesetzt und ein zulässiger Wert
- alle Pflichtwerte des aktiven Providers sind vorhanden (Tabelle 3.3)
- `timeoutSeconds` ist eine positive Ganzzahl
- für Claude: Default-`baseUrl` wird gesetzt, wenn der Wert fehlt
- für den **inaktiven** Provider werden keine Pflichtwerte erzwungen
- **API-Schlüssel-Auflösung:** Umgebungsvariable des aktiven Providers (Tabelle 3.4) hat Vorrang vor dem Properties-Wert; ist beides leer, ist die Konfiguration ungültig
4. **Bootstrap und bestehende Adapter werden in diesem AP nicht umgestellt.** Die neuen Typen sind ausschließlich über neue Tests erreichbar. Der Default-Lauf der Anwendung verwendet weiterhin die alten Klassen.
5. JavaDoc für alle neuen Klassen und Methoden ergänzen.
6. Konfigurationsbeispiel (`*.example.properties` o.ä.) **nicht** in diesem AP ändern. Folgt in AP-002 zusammen mit der Migration.
### Pflicht-Testfälle (kritisch, namentlich umzusetzen)
1. `parsesNewSchemaWithOpenAiCompatibleActive` vollständiges neues Schema, OpenAI aktiv, alle Pflichtwerte gesetzt → erfolgreich geparst, Validierung grün.
2. `parsesNewSchemaWithClaudeActive` vollständiges neues Schema, Claude aktiv, alle Pflichtwerte gesetzt → erfolgreich geparst, Validierung grün.
3. `claudeBaseUrlDefaultsWhenMissing` Claude aktiv, `ai.provider.claude.baseUrl` fehlt → Default `https://api.anthropic.com` wird gesetzt, Validierung grün.
4. `rejectsMissingActiveProvider` `ai.provider.active` fehlt → Validierung schlägt fehl mit klarer Meldung.
5. `rejectsUnknownActiveProvider` `ai.provider.active=foo` → Validierung schlägt fehl.
6. `rejectsMissingMandatoryFieldForActiveProvider` aktiver Provider hat ein Pflichtfeld leer → Validierung schlägt fehl.
7. `acceptsMissingMandatoryFieldForInactiveProvider` inaktiver Provider unvollständig → Validierung grün.
8. `envVarOverridesPropertiesApiKeyForActiveProvider` `OPENAI_COMPATIBLE_API_KEY` gesetzt, Properties-Key ebenfalls gesetzt → effektiver Key ist der aus der Env-Var. Analog für `ANTHROPIC_API_KEY`.
9. `envVarOnlyResolvesForActiveProvider` Env-Var nur für inaktiven Provider gesetzt, aktiver Provider hat Properties-Key → effektiver Key ist der Properties-Key des aktiven Providers; die Env-Var des inaktiven Providers wird ignoriert.
10. `bestehende Tests bleiben grün` alle bisherigen Configuration-Tests laufen weiter.
Test-Kategorien zusätzlich: Unit-Tests für die neuen Typen (Equality, Defaults), Parser-Tests, Validator-Tests.
### Explizit NICHT Teil dieses AP
- Migration der Legacy-Datei
- `.bak`-Sicherung
- Bootstrap-Umstellung
- Änderung am bestehenden OpenAI-Adapter
- nativer Claude-Adapter
- Persistenz-Änderungen
- Logging-Änderungen
### Definition of Done
- Build fehlerfrei
- alle Pflicht-Testfälle umgesetzt und grün
- bestehende Tests grün
- JavaDoc vollständig für neue Klassen
- Pflicht-Output-Block ausgegeben
---
## AP-002 Legacy-Migration mit `.bak`
### Voraussetzung
AP-001 abgeschlossen.
### Ziel
Beim ersten Start mit erkannter Legacy-Form wird die Properties-Datei kontrolliert in das neue Schema überführt. Vor jeder Migration wird eine `.bak`-Sicherung angelegt. Nach erfolgreicher Migration läuft die Anwendung **noch** auf dem alten Bootstrap-Pfad weiter (Umschaltung folgt in AP-003); aber die Datei auf der Platte ist bereits im neuen Format und beim nächsten Start sofort durch das neue Schema lesbar.
### Konkret zu erledigende Schritte
1. Eine neue Komponente im Adapter-Out-Modul anlegen, die rein auf Properties-Datei-Ebene arbeitet (kein HTTP, kein DB-Zugriff). Verantwortlichkeiten:
- Erkennen der Legacy-Form (Abschnitt 3.5)
- `.bak`-Sicherung anlegen: `<dateiname>.bak`. Wenn `.bak` schon existiert, mit aufsteigendem numerischen Suffix sichern (`<dateiname>.bak`, `<dateiname>.bak.1`, …) **niemals** überschreiben.
- Werte umschreiben gemäß Tabelle:
| Legacy | Ziel |
|---|---|
| `api.baseUrl` | `ai.provider.openai-compatible.baseUrl` |
| `api.model` | `ai.provider.openai-compatible.model` |
| `api.timeoutSeconds` | `ai.provider.openai-compatible.timeoutSeconds` |
| `api.key` | `ai.provider.openai-compatible.apiKey` |
- `ai.provider.active=openai-compatible` ergänzen.
- Leere/auskommentierte Platzhalter für die Claude-Sektion einfügen mit kurzem Hinweis-Kommentar (ein Block, max. 6 Zeilen).
- Alle übrigen Schlüssel (`source.folder`, `target.folder`, `sqlite.file`, `max.*`, `prompt.template.file`, `runtime.lock.file`, `log.*`) **unverändert** und in **stabiler Reihenfolge** übernehmen.
- Die migrierte Datei in-place schreiben (`.tmp` + atomischer Move/Rename, kein Truncate-and-write auf das Original).
- Anschließend die Datei erneut über den **neuen** Parser aus AP-001 laden und über den neuen Validator validieren. Schlägt das fehl, ist dies ein harter Startfehler (Exit-Code 1, klare Meldung, `.bak` bleibt erhalten).
2. Die Migration wird beim Programmstart **vor** dem bestehenden Konfigurationsladen aufgerufen, sobald die Datei bekannt ist. Dieser Aufruf passiert im Bootstrap genau an einer Stelle und ist als eigene Methode klar benennbar.
3. Wird **kein** Legacy erkannt (also bereits neues Schema), passiert nichts: keine `.bak`, keine Schreibvorgänge.
4. Bestehende ConfigurationPort-Implementierung **nicht** umstellen das passiert in AP-003. Die Anwendung läuft nach AP-002 fachlich weiter wie zuvor; ihr Eingangs-File ist nur jetzt in beiden Formen lesbar.
5. Konfigurationsbeispiel im Repo (z. B. `*.example.properties`) auf das **neue** Schema umstellen. Die Datei zeigt beide Provider-Sektionen mit sprechenden Platzhalterwerten.
6. JavaDoc und kurzer Abschnitt in der Repo-Doku zur Migration ergänzen (was passiert, wann, wie wird gesichert, was bei Fehler).
### Pflicht-Testfälle
1. `migratesLegacyFileWithAllFlatKeys` Legacy-Datei mit allen vier `api.*`-Schlüsseln wird korrekt ins neue Schema überführt; Werte bleiben inhaltlich identisch; übrige Schlüssel bleiben unverändert.
2. `createsBakBeforeOverwriting` vor Migration existiert keine `.bak`, danach existiert sie mit dem **Original-Inhalt**.
3. `bakSuffixIsIncrementedIfBakExists` `.bak` existiert bereits → neue Sicherung als `.bak.1`. Keine Sicherung wird überschrieben.
4. `noOpForAlreadyMigratedFile` Datei bereits im neuen Schema → kein Schreibvorgang, kein `.bak`.
5. `reloadAfterMigrationSucceeds` nach Migration kann der neue Parser/Validator aus AP-001 die Datei fehlerfrei laden.
6. `migrationFailureKeepsBak` Migration schreibt fehlerhafte Datei (Test-Mock erzwingt Validierungsfehler nach Schreiben) → Bootstrap meldet harten Startfehler, `.bak` ist unangetastet.
7. `legacyDetectionRequiresAtLeastOneFlatKey` Datei mit `ai.provider.active=...` und ohne `api.*` → kein Legacy, keine Migration.
8. `legacyValuesEndUpInOpenAiCompatibleNamespace` Werte `api.baseUrl`, `api.model`, `api.timeoutSeconds`, `api.key` landen exakt in den vier Zielschlüsseln; `ai.provider.active=openai-compatible` ist gesetzt.
9. `unrelatedKeysSurviveUnchanged` Schlüssel wie `source.folder`, `max.pages`, `log.level` bleiben mit identischem Wert erhalten.
10. `inPlaceWriteIsAtomic` Test-Doppel für das Dateisystem belegt: erst `.tmp` schreiben, dann atomic move; kein Punkt, an dem das Original teilbeschrieben ist.
Test-Kategorien zusätzlich: temporäre Dateien in `@TempDir`, Repository-/Integrationstests für die Migrations-Komponente.
### Explizit NICHT Teil
- Bootstrap-Umstellung des aktiven Konfigurationspfads
- Änderung am bestehenden OpenAI-Adapter
- Claude-Adapter
- Persistenz
- Logging-Änderungen über die Migrations-Meldungen hinaus
### Definition of Done
- Build fehlerfrei, alle Pflicht-Testfälle grün
- Beispiel-Properties-Datei im neuen Schema
- Kurz-Doku zur Migration im Repo
- Pflicht-Output-Block ausgegeben
---
## AP-003 Bootstrap-Provider-Auswahl und Umstellung des bestehenden OpenAI-Adapters
### Voraussetzung
AP-001 und AP-002 abgeschlossen.
### Ziel
Das Bootstrap-Modul wählt anhand von `ai.provider.active` genau eine `AiNamingPort`-Implementierung als aktive Implementierung aus und verdrahtet sie. Der bestehende OpenAI-kompatible Adapter konsumiert ab jetzt seine Werte aus dem Namensraum `ai.provider.openai-compatible.*`. Sein fachliches Verhalten bleibt **unverändert**. Der aktive Provider wird beim Laufstart geloggt.
### Konkret zu erledigende Schritte
1. Im Bootstrap-Modul eine **Provider-Selektor-Komponente** einführen, die als Eingabe den Wert von `ai.provider.active` und alle bekannten `AiNamingPort`-Implementierungen erhält und genau eine zurückgibt. Initial kennt sie nur die OpenAI-Implementierung; die Erweiterung um Claude erfolgt in AP-005 an genau dieser Stelle.
2. Bestehende `AiNamingPort`-Implementierung für die OpenAI-kompatible Schnittstelle so anpassen, dass sie die Werte aus `ai.provider.openai-compatible.*` konsumiert. Der bisherige fachliche Vertrag, das Request-/Response-Mapping und das Fehlerverhalten bleiben **identisch**.
3. Bestehenden ConfigurationPort/`Configuration`-Lesepfad so umstellen, dass intern **nur noch** das neue Schema verwendet wird. Die alten flachen Klassen/Methoden, die nur zum Lesen von `api.*` dienten, werden entfernt aber **nur**, wenn sie nirgends sonst benötigt werden (per Suche prüfen). Falls noch Verweise existieren, wird der entsprechende Konsument im selben AP auf das neue Schema umgestellt.
4. Bestehende Konfigurations-Tests des Repos auf das neue Schema umstellen. Tests, die explizit das alte flache Schema geprüft haben, werden zu Migrations-Tests verschoben (gehört bereits zu AP-002) **oder** auf das neue Schema umgeschrieben. Kein Test wird stillschweigend gelöscht.
5. Logging-Anbindung erweitern: beim Laufstart wird der **aktive Provider-Identifikator** geloggt (Standard-Loglevel `INFO`). Alle übrigen geforderten Log-Inhalte (siehe `CLAUDE.md`, Logging-Mindestumfang) bleiben unverändert.
6. Sicherstellen, dass die Sensibilitätsregel für KI-Inhalte unverändert greift und provider-unabhängig gilt.
7. Adapter-zu-Adapter-Kopplung aktiv vermeiden: Der Provider-Selektor lebt im Bootstrap, **nicht** im Adapter-Out-Modul.
8. JavaDoc für Selektor und betroffene Klassen ergänzen.
### Pflicht-Testfälle
1. `bootstrapWiresOpenAiCompatibleAdapterWhenActive` `ai.provider.active=openai-compatible` → Selektor liefert die OpenAI-Implementierung.
2. `bootstrapFailsHardWhenActiveProviderUnknown` Wert ist syntaktisch gesetzt, aber kein gültiger Provider → harter Startfehler, Exit-Code 1.
3. `bootstrapFailsHardWhenSelectedProviderHasNoImplementation` Wert ist `claude`, aber Implementierung noch nicht registriert (Zustand nach AP-003) → harter Startfehler mit klarer Meldung. Dieser Test wird in AP-005 angepasst, sobald Claude registriert ist.
4. `openAiAdapterReadsValuesFromNewNamespace` Adapter-Test: gegebene `ai.provider.openai-compatible.*`-Werte landen 1:1 im HTTP-Request an die bisherige Endpoint-URL.
5. `openAiAdapterBehaviorIsUnchanged` bestehender Adapter-Verhaltenstest (Request-Form, Response-Mapping, Fehlerklassifikation) wird auf die neue Konfigurationsquelle umgestellt und bleibt grün.
6. `activeProviderIsLoggedAtRunStart` Smoke- oder Bootstrap-Test belegt, dass der aktive Provider bei Laufstart in einem definierten Log-Eintrag erscheint.
7. `existingDocumentProcessingTestsRemainGreen` sämtliche bestehenden End-to-End-/Integrations-Tests des bestehenden OpenAI-Pfads bleiben grün, ggf. mit angepasster Konfiguration.
8. `legacyFileEndToEndStillRuns` Test-Doppel: Anwendung startet mit Legacy-Datei → Migration aus AP-002 läuft → Bootstrap aus AP-003 wählt OpenAI → Lauf läuft fachlich durch wie zuvor.
Test-Kategorien zusätzlich: Bootstrap-/Wiring-Tests, ggf. Smoke-Test ohne realen externen Aufruf.
### Explizit NICHT Teil
- Claude-Adapter
- Persistenz-Erweiterung um Provider-Identifikator
- neue Fehlersemantik
- Refactoring außerhalb der Adapter-Anbindung
### Definition of Done
- Build fehlerfrei, Pflicht-Testfälle grün
- bestehender OpenAI-Pfad fachlich unverändert
- aktiver Provider wird beim Laufstart geloggt
- keine Verweise mehr auf das alte flache Schema im Produktivpfad
- Pflicht-Output-Block ausgegeben
---
## AP-004 Persistenz: Provider-Identifikator additiv
### Voraussetzung
AP-003 abgeschlossen.
### Ziel
Das SQLite-Schema wird **additiv** um eine Spalte für den Provider-Identifikator je Versuch erweitert. Bestehende Datensätze bleiben lesbar und korrekt interpretierbar (Default-Wert für Altdaten). Neue Versuche schreiben den Identifikator des für den Versuch aktiven Providers.
### Konkret zu erledigende Schritte
1. Im SQLite-Schema der Versuchshistorie eine neue Spalte hinzufügen, z. B. `ai_provider TEXT NULL` (Spaltenname per bestehender Repo-Konvention wählen, sonst wie hier vorgeschlagen). Die Spalte ist nullable.
2. Schema-Migration umsetzen:
- Bei Programmstart prüfen, ob die Spalte existiert; wenn nein, per `ALTER TABLE` ergänzen.
- Vorhandene Zeilen behalten den Wert `NULL`.
- Migration muss idempotent sein (mehrfacher Start ohne Fehler).
3. Die Versuchshistorie-Schreiblogik so erweitern, dass beim Anlegen eines neuen Versuchs der **Identifikator des aktiv ausgewählten Providers** mitgeschrieben wird (`openai-compatible` oder `claude`). Der Wert kommt aus der bereits in AP-003 verfügbaren Provider-Auswahl.
4. Dokument-Stammsatz wird **nicht** verändert.
5. Lesepfad anpassen, sodass der neue Wert mitausgelesen wird; bestehende Mapper/Domain-Typen werden minimal um ein optionales Feld erweitert. Application und Domain bekommen dadurch keinen provider-spezifischen Code das Feld bleibt ein opaker String.
6. JavaDoc und kurzer Abschnitt zur Schema-Erweiterung in der Repo-Doku ergänzen.
### Pflicht-Testfälle
1. `addsProviderColumnOnFreshDb` frische DB → Schema enthält neue Spalte.
2. `addsProviderColumnOnExistingDbWithoutColumn` DB ohne Spalte (Simulation Altbestand) → Migration legt Spalte nullable an.
3. `migrationIsIdempotent` mehrfacher Start ändert nichts und wirft keinen Fehler.
4. `existingRowsKeepNullProvider` Altzeilen behalten `NULL`.
5. `newAttemptsWriteOpenAiCompatibleProvider` aktiver Provider OpenAI → neuer Versuch hat `ai_provider='openai-compatible'`.
6. `newAttemptsWriteClaudeProvider` aktiver Provider Claude (für diesen Test wird die Provider-Auswahl gemockt; in AP-005 wird derselbe Test mit echtem Claude-Adapter wiederholt) → `ai_provider='claude'`.
7. `repositoryReadsProviderColumn` Repository-Test: gespeicherter Wert wird korrekt zurückgelesen.
8. `legacyDataReadingDoesNotFail` Test mit DB-Datei aus dem Vor-V1.1-Stand: Lesen ohne Fehler, neuer Wert ist Optional/leer.
9. `existingHistoryTestsRemainGreen` alle bestehenden Tests rund um die Versuchshistorie bleiben grün, ggf. mit minimaler Anpassung.
Test-Kategorien zusätzlich: Repository-Tests gegen echte SQLite-Instanz (in-memory oder temporär), Schema-Migrations-Tests.
### Explizit NICHT Teil
- Claude-Adapter (folgt in AP-005)
- Änderungen am Dokument-Stammsatz
- neue Wahrheitsquellen
- Reporting/Statistiken
### Definition of Done
- Build fehlerfrei, Pflicht-Testfälle grün
- bestehende Datenbestände bleiben lesbar
- Provider-Identifikator wird für neue Versuche geschrieben
- Pflicht-Output-Block ausgegeben
---
## AP-005 Nativer Anthropic-Adapter implementieren und verdrahten
### Voraussetzung
AP-001 bis AP-004 abgeschlossen.
### Ziel
Eine zweite `AiNamingPort`-Implementierung wird im Adapter-Out-Modul angelegt, die die **native Anthropic Messages API** anspricht (siehe Faktenblock in Abschnitt 4). Sie wird im Provider-Selektor aus AP-003 als zweite Option registriert. Der Adapter bildet die Anthropic-Antwort auf den **bestehenden** fachlichen Vertrag ab; es entsteht kein Sonderweg in Application oder Domain.
### Konkret zu erledigende Schritte
1. Im Adapter-Out-Modul eine neue Klasse anlegen, die `AiNamingPort` implementiert. Naming nach bestehender Repo-Konvention; per Typsuche prüfen, wie die OpenAI-Implementierung benannt ist, und analog vorgehen.
2. HTTP-Aufruf gemäß Faktenblock 4 umsetzen:
- URL aus `ai.provider.claude.baseUrl` (Default `https://api.anthropic.com`) plus Pfad `/v1/messages`
- Methode `POST`
- Header `x-api-key`, `anthropic-version: 2023-06-01`, `content-type: application/json`
- Request-Body mit `model`, `max_tokens`, `messages` (eine `user`-Message mit dem bestehenden Prompt-Text), optional `system` falls die bestehende Prompt-Mechanik ein System-Segment kennt
- Timeout aus `ai.provider.claude.timeoutSeconds`
3. API-Schlüssel-Auflösung exakt nach Tabelle 3.4: zuerst `ANTHROPIC_API_KEY`, dann `ai.provider.claude.apiKey`.
4. Antwortverarbeitung gemäß 4.4: Konkatenation aller `content[*].text`-Blöcke in Reihenfolge. Fehlt jeder `text`-Block oder ist die Antwort nicht parsebar → technischer Adapterfehler nach Tabelle 4.5.
5. Den so gewonnenen Antworttext **unverändert** an die bestehende Antwortverarbeitung der Anwendung weitergeben (`NamingProposal`-Validierung passiert in Application/Domain wie bisher).
6. Fehlerklassifikation streng nach Tabelle 4.5. Keine neuen Fehlerklassen.
7. Den Provider-Selektor aus AP-003 um die neue Implementierung erweitern. **Keine** gemeinsame Basisklasse zwischen den beiden Adaptern, **keine** Hilfsklasse, die HTTP-Logik teilt. Was beide Adapter brauchen, kommt aus dem Repo-üblichen HTTP-/JSON-Standard, nicht aus einer neuen Adapter-Zwischenschicht.
8. Den in AP-001 angelegten Test `bootstrapFailsHardWhenSelectedProviderHasNoImplementation` so anpassen, dass er ab jetzt auf einen neuen, weiterhin **unbekannten** Provider-Wert testet (Negativfall bleibt erhalten, aber `claude` ist jetzt registriert).
9. Konfigurationsbeispiel im Repo um sprechende Claude-Beispielwerte ergänzen.
10. JavaDoc für die neue Klasse und ggf. neue Hilfstypen.
### Pflicht-Testfälle
1. `claudeAdapterBuildsCorrectRequest` gegebener Prompt → HTTP-Request mit korrekter URL (`<baseUrl>/v1/messages`), Methode POST, allen drei Pflicht-Headern, Body enthält `model`, `max_tokens > 0`, `messages` mit genau einer `user`-Message und korrektem Prompt.
2. `claudeAdapterUsesEnvVarApiKey` `ANTHROPIC_API_KEY` gesetzt, Properties-Wert ebenfalls → Header `x-api-key` enthält den Env-Wert.
3. `claudeAdapterFallsBackToPropertiesApiKey` Env-Var leer, Properties-Wert gesetzt → Header `x-api-key` enthält den Properties-Wert.
4. `claudeAdapterFailsValidationWhenBothKeysMissing` beides leer → Konfigurationsfehler beim Start (greift auf AP-001-Validierung).
5. `claudeAdapterParsesSingleTextBlock` Mock-Response mit einem Block `{type:"text", text:"..."}` → Antworttext gleich dem Block-Text.
6. `claudeAdapterConcatenatesMultipleTextBlocks` mehrere `text`-Blöcke → Antworttext gleich der Konkatenation in Reihenfolge.
7. `claudeAdapterIgnoresNonTextBlocks` Mix aus `text`- und Nicht-`text`-Blöcken → nur die `text`-Inhalte landen im Antworttext.
8. `claudeAdapterFailsOnEmptyTextContent` Response ohne jeden `text`-Block → technischer Adapterfehler.
9. `claudeAdapterMapsHttp401AsTechnical` Mock-Response 401 → technischer Fehler nach Tabelle 4.5.
10. `claudeAdapterMapsHttp429AsTechnical` Mock-Response 429 → technischer Fehler.
11. `claudeAdapterMapsHttp500AsTechnical` Mock-Response 500 → technischer Fehler.
12. `claudeAdapterMapsTimeoutAsTechnical` simulierter Timeout → technischer Fehler.
13. `claudeAdapterMapsUnparseableJsonAsTechnical` Response-Body ist kein gültiges JSON → technischer Fehler.
14. `bootstrapSelectsClaudeWhenActive` `ai.provider.active=claude` → Selektor liefert die Claude-Implementierung.
15. `claudeProviderIdentifierLandsInAttemptHistory` End-zu-End mit gemocktem HTTP-Layer: nach erfolgreichem Lauf hat der neue Versuch `ai_provider='claude'` (knüpft an AP-004 an).
16. `existingOpenAiPathRemainsGreen` sämtliche bestehenden Tests des OpenAI-Pfads bleiben unverändert grün.
Test-Kategorien zusätzlich: Adapter-Tests mit gemocktem HTTP-Client (kein realer Netzwerkzugriff), Bootstrap-Wiring-Tests.
### Explizit NICHT Teil
- automatische Fallback-Logik zwischen Providern
- gemeinsame Adapter-Basisklasse
- Erweiterung des Persistenz-Schemas über AP-004 hinaus
- Anpassung des Prompts (eine etwaige System-/User-Trennung der bestehenden Prompt-Datei darf genutzt werden, aber keine inhaltliche Änderung des Prompts)
### Definition of Done
- Build fehlerfrei, alle Pflicht-Testfälle grün
- nativer Anthropic-Adapter wird über Konfiguration auswählbar und liefert auf Mock-Basis korrekte Ergebnisse
- bestehender OpenAI-Pfad unverändert grün
- Pflicht-Output-Block ausgegeben
---
## AP-006 Regression, Smoke, Doku-Konsolidierung, Abschlussnachweis
### Voraussetzung
AP-001 bis AP-005 abgeschlossen.
### Ziel
Der vollständige Erweiterungsstand wird automatisiert abgesichert, dokumentarisch konsolidiert und als minimale, architekturtreue Erweiterung des Basisstands belastbar nachgewiesen.
### Konkret zu erledigende Schritte
1. **Smoke-Test je Provider:** Zwei Smoke-Tests einrichten, die für je eine Provider-Konfiguration den Bootstrap-Pfad bis zur erfolgreichen Verdrahtung des `AiNamingPort` durchlaufen, **ohne** realen externen HTTP-Aufruf (gemockter HTTP-Layer). Beide müssen grün sein.
2. **Regression OpenAI:** Alle bestehenden End-to-End-/Integrations-Tests des OpenAI-Pfads laufen grün. Falls Anpassungen in vorigen APs Tests berührt haben, ist hier der finale Konsistenz-Check.
3. **Migration Smoke:** Ein End-zu-End-Test, der mit einer Legacy-Datei (Inhalt aus der bekannten Demo-Konfig) startet und nach einem ersten Lauf folgendes nachweist:
- `.bak` existiert mit Original-Inhalt
- Properties-Datei ist im neuen Schema
- `ai.provider.active=openai-compatible`
- der Lauf hat fachlich gleich funktioniert wie mit dem neuen Schema
4. **PIT-/Mutationstests** in den unmittelbar betroffenen Modulen ausführen, soweit bereits etabliert. Lücken im neuen Code, die deutlich unter dem bestehenden Niveau liegen, gezielt schließen. Keine willkürliche Coverage-Kosmetik.
5. **Doku-Konsolidierung:**
- Beispiel-Properties-Datei zeigt das vollständige neue Schema für **beide** Provider mit sprechenden Platzhaltern.
- Repo-Doku enthält einen kurzen Abschnitt „KI-Provider auswählen" mit den zulässigen Werten und der Env-Var-Konvention (`OPENAI_COMPATIBLE_API_KEY`, `ANTHROPIC_API_KEY`).
- Repo-Doku enthält einen kurzen Abschnitt „Migration von der Vorgängerversion" mit dem Hinweis auf `.bak`.
- JavaDoc aller in der Erweiterung neu eingeführten oder substanziell geänderten Klassen ist vorhanden.
6. **Abschlussnachweis:** Eine kurze, im Repository verbleibende Markdown-Datei unter `docs/workpackages/V1.1 - Abschlussnachweis.md` anlegen, die mindestens enthält:
- Datum, betroffene Module
- Liste der ausgeführten Pflicht-Testfälle pro AP (kann tabellarisch sein)
- Belegte Eigenschaften: zwei Provider unterstützt, genau einer aktiv, kein Fallback, fachlicher Vertrag unverändert, Persistenz rückwärtsverträglich, Migration nachgewiesen, `.bak` nachgewiesen, aktiver Provider geloggt
- explizite Bestätigung: keine Architekturbrüche, keine neuen Bibliotheken außer denen, die für HTTP/JSON ohnehin im Repo etabliert sind
- Hinweis auf die Betreiberaufgabe, ggf. die Umgebungsvariable des OpenAI-Keys auf `OPENAI_COMPATIBLE_API_KEY` umzustellen
7. Den vollständigen Reactor-Build ausführen und das Ergebnis im AP-Output festhalten.
### Pflicht-Testfälle
1. `smokeBootstrapWithOpenAiCompatibleActive`
2. `smokeBootstrapWithClaudeActive`
3. `e2eMigrationFromLegacyDemoConfig`
4. `regressionExistingOpenAiSuiteGreen` (Sammelnachweis, nicht ein einzelner Test)
5. `e2eClaudeRunWritesProviderIdentifierToHistory`
6. `e2eOpenAiRunWritesProviderIdentifierToHistory`
7. `legacyDataFromBeforeV11RemainsReadable`
Test-Kategorien zusätzlich: Mutationstests in betroffenen Modulen, Konsistenz-Checks der Doku-Beispiele gegen den realen Parser (z. B. „Beispiel-Properties-Datei wird vom Parser ohne Fehler geladen").
### Explizit NICHT Teil
- weitere Provider
- Komfortfunktionen
- großflächiges Refactoring
### Definition of Done
- vollständiger Reactor-Build fehlerfrei
- alle Pflicht-Testfälle grün
- Smoke-Tests je Provider grün
- Doku konsolidiert
- Abschlussnachweis-Datei im Repo
- Pflicht-Output-Block ausgegeben