# Architektur-Übersicht: Adapter-Out, CLI & Bootstrap

Diese Datei beschreibt die drei Module `pdf-umbenenner-adapter-out`, `pdf-umbenenner-adapter-in-cli`
und `pdf-umbenenner-bootstrap`: ihren Zweck, ihre Paketstruktur, die wichtigsten Klassen und die
Verdrahtungslogik beim Programmstart. Sie richtet sich an Entwickler, die in einem dieser Module
arbeiten wollen und noch keinen Überblick über das Projekt haben. Domain- und Application-Schicht
(Port-Verträge, fachliche Domänenobjekte, Use-Case-Interfaces) sind nicht Gegenstand dieses
Dokuments – sie sind in `docs/architecture/domain-overview.md` beschrieben. GUI-interne Ports und
die Struktur des GUI-Adapters finden sich in `docs/architecture/gui-overview.md`. Die hexagonale
Abhängigkeitsrichtung ist strikt: Adapter kennen Domain und Application, nicht umgekehrt. Adapter
dürfen außerdem nicht direkt voneinander abhängen.

---

## 1. Modulzweck

### pdf-umbenenner-adapter-out

Enthält alle Outbound-Adapter-Implementierungen, also die konkreten technischen Lösungen für
sämtliche Outbound-Ports der Application. Dazu gehören: Dateisystemzugriff, PDF-Textextraktion
via PDFBox, SQLite-Persistenz (Schema, Repositories, Unit of Work), HTTP-Clients für zwei
KI-Provider-Familien (OpenAI-kompatibel und Anthropic nativ), Properties-Konfiguration inklusive
Legacy-Migration, dateibasierter Run-Lock sowie Systemuhr und SHA-256-Fingerprint.

### pdf-umbenenner-adapter-in-cli

Schlanker Inbound-Adapter für den kopflosen Batch-Betrieb. Enthält genau eine Klasse
(`SchedulerBatchCommand`), die den CLI-Einstiegspunkt bildet und ausschließlich über das
Inbound-Port-Interface an die Application delegiert. Keine eigene Fachlogik.

### pdf-umbenenner-bootstrap

Composition Root der Anwendung. Verantwortlich für: CLI-Argument-Parsing,
Konfigurationsauflösung und -validierung, Aufbau des vollständigen Objektgraphen (manuell, ohne
DI-Framework), Auswahl der aktiven KI-Adapter-Implementierung, Dispatch auf GUI- oder
Headless-Pfad sowie Exit-Code-Ableitung. Bootstrap ist die einzige Stelle, an der alle Module
zusammengeführt werden.

---

## 2. Paketstruktur

### pdf-umbenenner-adapter-out

Wurzelpaket: `de.gecheckt.pdf.umbenenner.adapter.out`

| Unterpaket              | Inhalt                                                                              |
|-------------------------|-------------------------------------------------------------------------------------|
| `.ai`                   | HTTP-Adapter für OpenAI-kompatible Schnittstelle und Anthropic Messages API         |
| `.clock`                | Systemuhr-Adapter (`Instant.now()`)                                                 |
| `.configuration`        | Properties-Laden, Multi-Provider-Parsing/-Validierung, Legacy-Migration             |
| `.fingerprint`          | SHA-256-Inhalts-Fingerprint                                                         |
| `.lock`                 | Dateibasierter Run-Lock                                                             |
| `.modelcatalog`         | HTTP-Modellabruf für den GUI-Konfigurationseditor                                   |
| `.pathcheck`            | Pfadprüfung für den GUI-Editor                                                      |
| `.pdfextraction`        | PDFBox-3.x-Adapter: Textextraktion und Seitenanzahl                                 |
| `.prompt`               | Prompt-Template-Lader                                                               |
| `.resourcecreation`     | Anlegen von Ordnern und Dateien (korrigierende technische Tests)                    |
| `.sourcedocument`       | Quellordner-Scanner (nicht rekursiv)                                                |
| `.sqlite`               | Schema-Initialisierung, Repositories, Unit of Work                                 |
| `.targetcopy`           | Zielkopie via Temp-Datei und atomarem Move                                          |
| `.targetfolder`         | Kollisionsfreier Zieldateiname, Umbenennung bestehender Zieldateien                 |
| `.validation`           | API-Key-Auflösung aus Umgebungsvariablen (GUI-Editor)                               |
| `.bootstrap.validation` | `StartConfiguration`-Validierung vor Prozessstart                                   |

### pdf-umbenenner-adapter-in-cli

Wurzelpaket: `de.gecheckt.pdf.umbenenner.adapter.in.cli`

Enthält ausschließlich `SchedulerBatchCommand` sowie die zugehörige `package-info.java`.

### pdf-umbenenner-bootstrap

Wurzelpaket: `de.gecheckt.pdf.umbenenner.bootstrap`

| Unterpaket          | Inhalt                                                                                                |
|---------------------|-------------------------------------------------------------------------------------------------------|
| *(Wurzel)*          | `PdfUmbenennerApplication` (main), `BootstrapRunner`, `AiProviderSelector`                           |
| `.adapter`          | Bootstrap-interne Adapter: `Log4jProcessingLogger`, `GuiConfigurationPropertiesWriter`, `AiModelCatalogDispatcher` |
| `.singleinstance`   | `SingleInstanceGuard` – Einzelinstanz-Schutz via Loopback-ServerSocket                               |
| `.startup`          | `StartupMode`, `StartupArguments`, `CliArgumentParser`                                                |

---

## 3. Schlüsselklassen

Die folgenden Klassen sind für das Verständnis der drei Module zentral. FQN-Kürzel: `...` steht
jeweils für das Wurzelpaket des Moduls.

### Adapter-Out

#### KI-Adapter

- **`...ai.OpenAiHttpAdapter`** – implementiert `AiInvocationPort` für OpenAI-kompatible Endpunkte.
  POST `{baseUrl}/v1/chat/completions`, Bearer-Authentifizierung, extrahiert
  `choices[0].message.content`, klassifiziert HTTP-Fehler und Timeouts als
  `AiInvocationTechnicalFailure`.

- **`...ai.AnthropicClaudeHttpAdapter`** – implementiert `AiInvocationPort` für die native
  Anthropic Messages API. POST `/v1/messages`, Header `x-api-key` und `anthropic-version`,
  konkateniert `text`-Content-Blöcke aus dem Antwort-Array.

Beide Adapter liefern denselben Domain-Typ (`NamingProposal`) und enthalten keinerlei
provider-spezifische Typen in öffentlichen Signaturen. Welche Implementierung aktiv ist, entscheidet
ausschließlich der Bootstrap (→ `AiProviderSelector`).

#### Modell-Katalog (GUI)

- **`...modelcatalog.ClaudeModelCatalogAdapter`** – `AiModelCatalogPort` für Claude,
  GET `/v1/models` mit `x-api-key`.

- **`...modelcatalog.OpenAiCompatibleModelCatalogAdapter`** – `AiModelCatalogPort` für
  OpenAI-kompatibel, GET `/v1/models` mit Bearer.

#### PDF-Extraktion

- **`...pdfextraction.PdfTextExtractionPortAdapter`** – PDFBox-3.x-Adapter. Alle technischen
  Fehler werden als `PdfExtractionTechnicalError` zurückgegeben; es werden keine Exceptions
  propagiert.

#### SQLite

- **`...sqlite.SqliteSchemaInitializationAdapter`** – Flyway-basierte Schema-Initialisierung
  mit `V1__initial_schema.sql`. Drei-Fall-Strategie: leere Datenbank (Flyway führt das Skript
  vollständig aus), bestehender Datenbestand ohne Flyway-History (Schema-Prüfung, datiertes
  Backup, dann Baseline-Eintrag ohne Skriptausführung), regulärer Folgestart mit Flyway-History
  (idempotenter Lauf). Foreign-Key-Durchsetzung via `SQLiteConfig.enforceForeignKeys(true)` auf
  DataSource-Ebene, sodass jede neue Verbindung automatisch `PRAGMA foreign_keys = ON` erhält.

- **`...sqlite.SqliteUnitOfWorkAdapter`** – implementiert `UnitOfWorkPort`. Setzt
  `autoCommit=false`, führt atomare Commits durch, rollt bei Fehlern zurück. Die innere
  `TransactionOperations`-Implementierung wurde um `resetDocumentStatusForRetry(DocumentFingerprint)`
  erweitert: setzt feldgenau `overall_status = 'READY_FOR_AI'`, `content_error_count = 0`,
  `transient_error_count = 0`, `last_failure_instant = NULL`; alle anderen Felder und alle
  `processing_attempt`-Einträge bleiben unangetastet.

- **`...sqlite.SqliteDocumentRecordRepositoryAdapter`** – Stammsatz pro SHA-256-Fingerprint
  (Gesamtstatus, Fehlerzähler, Zieldateiname usw.).

- **`...sqlite.SqliteProcessingAttemptRepositoryAdapter`** – Versuchshistorie, referenziert
  über Fingerprint. Enthält u. a. Provider-Identifikator, Modellname, Prompt-Identifikator,
  KI-Rohantwort und finalen Zieldateinamen.

- **`...sqlite.SqliteHistoryQueryAdapter`** – implementiert `HistoryQueryPort`. Kapselt alle
  lesenden Datenbankoperationen für den Historien-Tab: Übersicht (`loadOverview` mit
  Sortierung `updated_at DESC, fingerprint ASC`, LIMIT 501-Strategie, case-insensitive
  Freitextsuche via `LOWER()` mit Sonderzeichen-Escape für `%` und `_`), Stammsatz-Lookup
  (`findRecordByFingerprint`) und Versuchshistorie (`findAttemptsByFingerprint`).

#### Konfiguration

- **`...configuration.PropertiesConfigurationPortAdapter`** – implementiert `ConfigurationPort`.
  Lädt `config/application.properties` (oder einen `--config`-Override), parst via
  `MultiProviderConfigurationParser`, löst API-Keys aus Umgebungsvariablen
  (`OPENAI_COMPATIBLE_API_KEY`, `ANTHROPIC_API_KEY`).

- **`...configuration.LegacyConfigurationMigrator`** – erkennt alte Flat-Key-Konfigurationen
  (Schlüssel wie `api.baseUrl`, `api.model`), legt eine `.bak`-Sicherung an und überführt den
  Inhalt in das aktuelle Multi-Provider-Schema.

#### Prompt-Adapter

- **`...prompt.FilesystemPromptPortAdapter`** – implementiert `PromptPort`. Lädt das
  Prompt-Template aus einer externen Datei und leitet den Identifikator aus dem Dateinamen ab.
  Die neue Methode `savePrompt(String content)` schreibt den Inhalt atomar: temporäre Datei
  im selben Verzeichnis anlegen (gleiche Partition), Inhalt in UTF-8 schreiben, dann
  `ATOMIC_MOVE` zur Zieldatei. Kein stiller Fallback bei `AtomicMoveNotSupportedException`.
  Der Pfad stammt aus der Adapter-internen Konfiguration, nicht aus dem Port-Aufruf.

#### Laufzeitinfrastruktur

- **`...lock.FilesystemRunLockPortAdapter`** – Lock-Datei mit PID-Inhalt. Wirft
  `RunLockUnavailableException`, wenn die Datei bereits vorhanden ist. Release löscht die Datei
  (best-effort).

- **`...clock.SystemClockAdapter`** – delegiert an `Instant.now()`.

- **`...fingerprint.Sha256FingerprintAdapter`** – SHA-256 über den Rohdatei-Inhalt. Fehler als
  `FingerprintTechnicalError`.

#### Zieldatei

- **`...targetcopy.FilesystemTargetFileCopyAdapter`** – kopiert die Quelldatei zunächst in eine
  `.tmp`-Datei, dann atomarer Move (Fallback: Standard-Move). Die Quelldatei wird in keinem Fall
  verändert.

- **`...targetfolder.FilesystemTargetFolderAdapter`** – ermittelt einen kollisionsfreien
  Zieldateinamen mit `(1)`, `(2)`-Suffix. Erkennt inhaltsidentische Duplikate via SHA-256.

#### Validierung vor Prozessstart

- **`...bootstrap.validation.StartConfigurationValidator`** – validiert die geladene
  `StartConfiguration` auf Pflichtfelder, Wertebereiche, URI-Syntax und Pfadbedingungen.
  Wird im Bootstrap-Headless-Pfad unmittelbar nach dem Laden der Konfiguration aufgerufen.

---

### Adapter-In-CLI

- **`...adapter.in.cli.SchedulerBatchCommand`** – einziger Inbound-Adapter für den Headless-Betrieb.
  Nimmt einen `BatchRunContext` entgegen, delegiert an `BatchRunProcessingUseCase.execute()` und
  gibt `BatchRunOutcome` zurück. Enthält keine eigene Fachlogik; die Verdrahtung mit dem
  Use-Case-Interface erfolgt ausschließlich im Bootstrap.

---

### Bootstrap

- **`...bootstrap.PdfUmbenennerApplication`** – `main`-Methode. Parst CLI-Argumente via
  `CliArgumentParser`, bricht bei ungültiger Verwendung mit Exit-Code 1 ab, delegiert an
  `BootstrapRunner.run()` und ruft abschließend `System.exit()` mit dem zurückgegebenen Code auf.

- **`...bootstrap.BootstrapRunner`** – Herzstück der Verdrahtung. Baut den Objektgraph für
  Headless- und GUI-Pfad, dispatcht über `StartupMode`, enthält `buildProductionBatchUseCase()`
  und `runHeadlessBatch()` als zentrale Kompositionsmethoden, liefert den Exit-Code zurück.

- **`...bootstrap.AiProviderSelector`** – einzige Stelle, an der `AiProviderFamily` auf eine
  konkrete `AiInvocationPort`-Implementierung abgebildet wird:
  `OPENAI_COMPATIBLE` → `OpenAiHttpAdapter`, `CLAUDE` → `AnthropicClaudeHttpAdapter`.

- **`...bootstrap.startup.CliArgumentParser`** – parst `--headless` und `--config <Pfad>` zu einem
  typsicheren `StartupArgumentsParseResult` (sealed: `Valid` / `Invalid`).

- **`...bootstrap.singleinstance.SingleInstanceGuard`** – bindet einen Loopback-ServerSocket auf
  Port 47832. Wirft `AnotherInstanceRunningException`, wenn der Port bereits belegt ist. Ein
  Shutdown-Hook gibt den Socket frei.

- **`...bootstrap.adapter.AiModelCatalogDispatcher`** – Bootstrap-interner Dispatcher für die GUI.
  Routet `AiModelCatalogPort`-Aufrufe anhand des `providerIdentifier` an den Claude- oder
  OpenAI-kompatiblen Modell-Katalog-Adapter. Thread-safe.

- **`...bootstrap.ApplicationVersionProvider`** – statische Hilfsklasse ohne Zustand. Liest
  `Implementation-Version` aus dem Paket-Manifest via `getClass().getPackage().getImplementationVersion()`.
  Fallback `"dev"` bei IDE-Start und ungepacktem Betrieb (kein Manifest-Eintrag vorhanden).
  Der aufgelöste Wert wird im GUI-Pfad in `GuiStartupContext.applicationVersion` eingesetzt.

- **`...bootstrap.adapter.Log4jProcessingLogger`** – implementiert `ProcessingLogger` auf Basis
  von Log4j2. Unterdrückt sensitive KI-Inhalte, wenn `AiContentSensitivity.PROTECT_SENSITIVE_CONTENT`
  gesetzt ist.

- **`...bootstrap.adapter.GuiConfigurationPropertiesWriter`** – schreibt die im GUI-Editor
  bearbeitete Konfiguration als normalisierte `application.properties` zurück auf das Dateisystem.

---

## 4. Verdrahtungslogik in Bootstrap

Die folgende Sequenz beschreibt den Ablauf von `main()` bis zum Start des eigentlichen Adapters.
Der Objektgraph wird ausschließlich durch manuelle `new`-Aufrufe aufgebaut; es wird kein
DI-Framework verwendet.

**Argument-Parsing**
- `PdfUmbenennerApplication.main()` → `CliArgumentParser.parse(args)`
- Ergebnis `Invalid` → Exit-Code 1, keine weiteren Schritte

**Einzelinstanz-Schutz**
- `BootstrapRunner.run()` → `SingleInstanceGuard.acquire()`
- `AnotherInstanceRunningException` → Exit-Code 1; im GUI-Modus zusätzlich ein Swing-Warndialog

**Modus-Dispatch**
- `BootstrapRunner.run()` wertet `startupArguments.mode()` aus:
  - `HEADLESS` → `runHeadlessBatch()`
  - `GUI` → `startGuiMode()`

**Konfigurationsauflösung (Headless-Pfad)**
- Prüfung, ob `--config`-Datei existiert (Fehler → Exit-Code 1)
- `LegacyConfigurationMigrator.migrateIfLegacy()` bei erkannter Legacy-Form
- `PropertiesConfigurationPortAdapter` lädt und parst die Properties
- `StartConfigurationValidator` validiert die geladene `StartConfiguration`
- Validierungsfehler → Exit-Code 1

**KI-Provider-Auswahl**
- Innerhalb von `buildProductionBatchUseCase()`:
  `multiProviderConfiguration().activeProviderFamily()` → `AiProviderSelector.select(family, providerConfig)`
- Ergebnis: genau eine `AiInvocationPort`-Instanz

**Objektgraph-Aufbau (Headless)**
- Erzeugte Instanzen (Reihenfolge nach Abhängigkeit): `Sha256FingerprintAdapter`,
  `SqliteDocumentRecordRepositoryAdapter`, `SqliteProcessingAttemptRepositoryAdapter`,
  `SqliteUnitOfWorkAdapter`, `FilesystemTargetFolderAdapter`, `FilesystemTargetFileCopyAdapter`,
  `FilesystemPromptPortAdapter`, `SystemClockAdapter`, `SourceDocumentCandidatesPortAdapter`,
  `PdfTextExtractionPortAdapter`, `Log4jProcessingLogger`
- Application-Services (`DocumentProcessingCoordinator`, `AiResponseValidator`,
  `AiNamingService`) werden verdrahtet und in `DefaultBatchRunProcessingUseCase` eingebettet

**CLI-Adapter**
- `BootstrapRunner` erzeugt `SchedulerBatchCommand` mit dem fertigen `BatchRunProcessingUseCase`

**Exit-Code-Ableitung**
- `BatchRunOutcome` → 0 (Lauf technisch erfolgreich) oder 1 (harter Bootstrap-/Konfigurationsfehler)
- `PdfUmbenennerApplication` ruft `System.exit(exitCode)` auf

**GUI-Pfad**
- `startGuiMode()` baut via `buildGuiStartupContext()` einen `GuiStartupContext`:
  enthält `AiModelCatalogDispatcher`, `EnvironmentApiKeyResolutionAdapter`,
  `TechnicalTestOrchestrator`, `GuiConfigurationPropertiesWriter`
- Bootstrap verdrahtet zusätzlich vier neue History-Use-Cases (`DefaultHistoryOverviewUseCase`,
  `DefaultHistoryDetailsUseCase`, `DefaultHistoryResetDocumentStatusUseCase`,
  `DefaultDeleteDocumentHistoryUseCase`) und den `DefaultPromptEditorUseCase` als anonyme
  Bridge-Implementierungen in den `GuiStartupContext`
- `ApplicationVersionProvider.resolveVersion()` wird aufgerufen und der Wert in
  `GuiStartupContext.applicationVersion` gesetzt
- Wenn eine Konfigurationsdatei beim Start bekannt ist, erzeugt Bootstrap zusätzlich einen
  vollständig verdrahteten `GuiPromptEditorPort` (kombiniert `FilesystemPromptPortAdapter` mit
  `DefaultPromptEditorUseCase`); ohne Konfiguration erhält der Context einen No-Op-Port
- `GuiAdapter.start(context)` übernimmt; ab diesem Punkt liegt die Kontrolle beim GUI-Adapter
- Im GUI-Pfad: keine SQLite-Schema-Initialisierung beim Start, kein Run-Lock-Erwerb, kein Batch-Use-Case;
  History-Operationen initialisieren die Schema-Verbindung ad-hoc pro Aufruf
- GUI-interne Ports und deren Verbindung mit Outbound-Adaptern sind in
  `docs/architecture/gui-overview.md` beschrieben

---

## 5. Einstiegspunkte je Modul

### pdf-umbenenner-adapter-out

1. **`...ai.OpenAiHttpAdapter`** – zeigt das typische Adapter-Muster: Port-Interface implementieren,
   alle provider-spezifischen Details kapseln, `ProviderConfiguration` als einzige
   Konfigurationsquelle konsumieren. Danach `AnthropicClaudeHttpAdapter` zum Vergleich lesen.

2. **`...sqlite.SqliteSchemaInitializationAdapter`** – erklärt das Datenbankschema, das alle
   SQLite-Adapter voraussetzen. Hier sieht man, welche Felder in `document_record` und
   `processing_attempt` existieren und wie Schema-Evolution additiv umgesetzt ist.

3. **`...configuration.PropertiesConfigurationPortAdapter`** – Einstieg in die
   Konfigurationskette. Von hier aus `MultiProviderConfigurationParser` und
   `LegacyConfigurationMigrator` nachverfolgen.

### pdf-umbenenner-adapter-in-cli

1. **`...adapter.in.cli.SchedulerBatchCommand`** – komprimiertes Inbound-Adapter-Muster in einer
   einzigen Klasse. Zeigt, wie ein Inbound-Adapter ausschließlich über Port-Interfaces mit der
   Application kommuniziert.

2. **`package-info.java`** – beschreibt Abhängigkeitsrichtung und Verdrahtungsvertrag dieses
   Adapters.

3. **`SchedulerBatchCommandTest`** – zeigt, wie der Adapter ohne Bootstrap testbar ist.

### pdf-umbenenner-bootstrap

1. **`PdfUmbenennerApplication`** – Startpunkt; die kurze Kette von `main()` bis `System.exit()`
   gibt einen ersten Überblick über die gesamte Startsequenz.

2. **`BootstrapRunner`** – Herzstück; `buildProductionBatchUseCase()` zeigt, wie der vollständige
   Objektgraph manuell aufgebaut wird. `runHeadlessBatch()` zeigt den Headless-Kontrollfluss.

3. **`AiProviderSelector`** – kleinste Klasse mit größter Hebelwirkung: hier liegt die einzige
   Stelle, an der die Provider-Auswahl aus der Konfiguration auf eine konkrete
   `AiInvocationPort`-Implementierung trifft.

---

*Port-Verträge und Domain-Typen: `docs/architecture/domain-overview.md`*
*GUI-interne Ports und GUI-Adapter-Struktur: `docs/architecture/gui-overview.md`*
