# WP3-001-004 – Implementation Plan  
## Candidate Discovery Router

- **Status:** Proposed for PL approval
- **Version:** 0.1
- **Scope:** WP3 – Discovery Framework Foundation
- **Basis:** aktueller Projektstand `reconcile_ADR002.WP2_20260801_3.zip`
- **Related:** WP3-001-004 – Candidate Discovery Router v0.2

---

# 1. Ziel

WP3-001-004 führt den `CandidateDiscoveryRouter` als Implementierung des bestehenden `CandidateDiscoveryGateway` ein.

Der Router verbindet:

- `DiscoveryConfigurationRegistry`
- `DiscoveryRoute`
- `CandidateDiscoveryAdapterRegistry`
- `CandidateDiscoveryAdapter`

zu einem einheitlichen Candidate-Discovery-Ablauf.

Der Router führt selbst keine technische Discovery aus und erzeugt keine `CandidateItem`-Entities.

---

# 2. Analyse des aktuellen Projektstands

Im aktuellen Projekt sind die Voraussetzungen aus WP3-001-001 bis WP3-001-003 vorhanden:

```text
DiscoveryRoute
DiscoveryConfigurationRegistry
CandidateDiscoveryAdapter
CandidateDiscoveryAdapterRegistry
DiscoveredCandidate
ReconciliationCommand
CandidateDiscoveryGateway
```

Der bestehende Application Port lautet:

```php
interface CandidateDiscoveryGateway
{
    /** @return list<DiscoveredCandidate> */
    public function discover(ReconciliationCommand $command): array;
}
```

Der neue Router kann diesen Port ohne zusätzliche Mapping-Schicht implementieren.

Der bestehende `LegacyCandidateDiscoveryAdapter` implementiert weiterhin direkt den `CandidateDiscoveryGateway`. Er wird erst in WP3-001-005 durch einen Compatibility Adapter hinter den neuen Router verschoben.

---

# 3. Neue Produktionsdateien

## 3.1 CandidateDiscoveryRouter

**Pfad**

```text
src/Infrastructure/Discovery/Routing/CandidateDiscoveryRouter.php
```

**Namespace**

```php
App\Infrastructure\Discovery\Routing
```

**Typ**

```php
final readonly class CandidateDiscoveryRouter implements CandidateDiscoveryGateway
```

**Abhängigkeiten**

```php
DiscoveryConfigurationRegistry
CandidateDiscoveryAdapterRegistry
```

**Konstruktor**

```php
public function __construct(
    private DiscoveryConfigurationRegistry $routeRegistry,
    private CandidateDiscoveryAdapterRegistry $adapterRegistry,
)
```

**Öffentliche API**

```php
/** @return list<DiscoveredCandidate> */
public function discover(ReconciliationCommand $command): array
```

---

# 4. Ermittlung des Discovery Scope

Der Router benötigt für die Route-Auflösung eine `scopeVocabularyUri`.

Im aktuellen `ReconciliationCommand` stehen hierfür zwei Werte zur Verfügung:

```text
targetVocabularyUri
subVocabularyId
```

Für WP3-001-004 gilt folgende KISS-Regel:

```text
subVocabularyId vorhanden
→ scopeVocabularyUri = subVocabularyId

subVocabularyId nicht vorhanden
→ scopeVocabularyUri = targetVocabularyUri
```

Beispiele:

```text
targetVocabularyUri = http://matcult-the.vocnet.org
subVocabularyId     = http://matcult-the.vocnet.org/00000019

scopeVocabularyUri  = http://matcult-the.vocnet.org/00000019
```

```text
targetVocabularyUri = http://matcult-the.vocnet.org
subVocabularyId     = null

scopeVocabularyUri  = http://matcult-the.vocnet.org
```

Der Router prüft nicht:

- ob der Scope fachlich zum Target Vocabulary gehört,
- ob die URI ein Vocabulary oder SubVocabulary bezeichnet,
- ob ein SKOS Concept Scheme vorliegt.

Diese Semantik bleibt Bestandteil der Konfiguration.

---

# 5. Routing-Ablauf

Der Router verarbeitet eine Anfrage in dieser Reihenfolge:

```text
ReconciliationCommand
        │
        ▼
scopeVocabularyUri ableiten
        │
        ▼
DiscoveryConfigurationRegistry.routesForScope(...)
        │
        ▼
Route-Kardinalität prüfen
        │
        ├── 0 Routes  → Fehler
        ├── 1 Route   → verwenden
        └── >1 Routes → Mehrdeutigkeitsfehler
        │
        ▼
DiscoveryRoute.adapterKey
        │
        ▼
CandidateDiscoveryAdapterRegistry.adapterFor(...)
        │
        ▼
CandidateDiscoveryAdapter.discover(...)
        │
        ▼
DiscoveredCandidate[]
```

Der Router verändert weder den `ReconciliationCommand` noch die vom Adapter gelieferten `DiscoveredCandidate`-Instanzen.

---

# 6. Fehlerverhalten

WP3-001-004 führt zwei Routing-spezifische Exception-Klassen ein.

## 6.1 DiscoveryConfigurationException

**Pfad**

```text
src/Infrastructure/Discovery/Routing/DiscoveryConfigurationException.php
```

**Typ**

```php
final class DiscoveryConfigurationException extends RuntimeException
```

**Auslöser**

Keine aktive Discovery Route für den ermittelten Discovery Scope.

**Meldung**

```text
No active DiscoveryRoute configured for scope: <scopeVocabularyUri>
```

---

## 6.2 AmbiguousDiscoveryRouteException

**Pfad**

```text
src/Infrastructure/Discovery/Routing/AmbiguousDiscoveryRouteException.php
```

**Typ**

```php
final class AmbiguousDiscoveryRouteException extends RuntimeException
```

**Auslöser**

Mehr als eine aktive Discovery Route für denselben Discovery Scope.

**Meldung**

```text
Multiple active DiscoveryRoutes configured for scope: <scopeVocabularyUri>
```

Die Exceptions gehören in WP3-001-004, weil der Router die 0/1/>1-Kardinalitätsregel besitzt. Sie werden nicht bis WP3-001-005 verschoben.

---

# 7. Router-Invarianten

Der `CandidateDiscoveryRouter`

- implementiert `CandidateDiscoveryGateway`,
- verwendet ausschließlich die beiden Registries,
- kennt keine konkreten Adapterklassen,
- kennt keine technischen Protokolle oder Endpoints,
- verändert keine `DiscoveryRoute`,
- verändert den `ReconciliationCommand` nicht,
- erzeugt keine `CandidateItem`-Entities,
- gibt `DiscoveredCandidate[]` unverändert zurück,
- verwendet niemals implizit „erste Route gewinnt“.

---

# 8. Neue Testdatei

## CandidateDiscoveryRouterTest

**Pfad**

```text
tests/Infrastructure/Discovery/Routing/CandidateDiscoveryRouterTest.php
```

Die bestehende Testimplementierung wird eingebunden über:

```text
tests/Infrastructure/Discovery/Adapter/Support/DummyCandidateDiscoveryAdapter.php
```

Sie bleibt ausschließlich Testcode.

---

# 9. Testfälle

## T001 – Router implementiert CandidateDiscoveryGateway

```text
CandidateDiscoveryRouter instanceof CandidateDiscoveryGateway
```

## T002 – Root Vocabulary als Discovery Scope

Bei `subVocabularyId = null` wird `targetVocabularyUri` für die Route-Auflösung verwendet.

## T003 – SubVocabulary als Discovery Scope

Bei gesetzter `subVocabularyId` wird diese unverändert als `scopeVocabularyUri` verwendet.

## T004 – Genau eine Route

Der über `adapterKey` referenzierte Adapter wird aufgerufen.

## T005 – Command-Identität

Der Adapter erhält dieselbe `ReconciliationCommand`-Instanz.

## T006 – Ergebnis-Identität und Reihenfolge

Die vom Adapter gelieferten `DiscoveredCandidate[]` werden unverändert und in derselben Reihenfolge zurückgegeben.

## T007 – Keine Route

Es wird `DiscoveryConfigurationException` mit der dokumentierten Meldung ausgelöst.

## T008 – Mehrere Routes

Es wird `AmbiguousDiscoveryRouteException` mit der dokumentierten Meldung ausgelöst.

## T009 – Deaktivierte Route

Eine ausschließlich deaktivierte Route wird durch die Registry herausgefiltert und führt im Router zum Fall „keine Route“.

## T010 – Unbekannter Adapter-Key

Der Fehler der `CandidateDiscoveryAdapterRegistry` wird nicht verschluckt oder umgedeutet.

**Erwartete Ausgabe**

```text
WP3-001-004 Candidate Discovery Router: OK
```

---

# 10. Änderungen an bestehenden Dateien

Für WP3-001-004 sind keine Änderungen an bestehenden Produktionsdateien vorgesehen.

Insbesondere unverändert bleiben:

- `CandidateDiscoveryGateway`
- `ReconciliationCommand`
- `DiscoveredCandidate`
- `DiscoveryRoute`
- `DiscoveryConfigurationRegistry`
- `CandidateDiscoveryAdapter`
- `CandidateDiscoveryAdapterRegistry`
- `LegacyCandidateDiscoveryAdapter`
- `ReconciliationApplicationService`
- Composition Root
- RuntimeContext
- RuntimeFactory

Die produktive Runtime-Integration folgt erst in WP3-001-005.

---

# 11. Geplanter Änderungsumfang

## Neue Produktionsdateien

```text
src/Infrastructure/Discovery/Routing/CandidateDiscoveryRouter.php
src/Infrastructure/Discovery/Routing/DiscoveryConfigurationException.php
src/Infrastructure/Discovery/Routing/AmbiguousDiscoveryRouteException.php
```

## Neue Testdatei

```text
tests/Infrastructure/Discovery/Routing/CandidateDiscoveryRouterTest.php
```

## Geänderte bestehende Dateien

```text
keine
```

---

# 12. Akzeptanzkriterien

WP3-001-004 ist umgesetzt, wenn:

1. `CandidateDiscoveryRouter` den `CandidateDiscoveryGateway` implementiert.
2. Der Discovery Scope aus `subVocabularyId` beziehungsweise `targetVocabularyUri` abgeleitet wird.
3. Der Router alle passenden aktiven Routes über die `DiscoveryConfigurationRegistry` erhält.
4. Keine Route einen eindeutigen Konfigurationsfehler erzeugt.
5. Genau eine Route verwendet wird.
6. Mehrere Routes einen eindeutigen Mehrdeutigkeitsfehler erzeugen.
7. Der Adapter über die `CandidateDiscoveryAdapterRegistry` aufgelöst wird.
8. `ReconciliationCommand` unverändert delegiert wird.
9. `DiscoveredCandidate[]` unverändert zurückgegeben werden.
10. Der Router keine technische Discovery- oder Domain-Mapping-Logik enthält.
11. Der neue Router-Test erfolgreich ist.
12. Die Tests von WP3-001-001 bis WP3-001-003 weiterhin erfolgreich sind.
13. Alle übrigen ausführbaren Bestandstests weiterhin erfolgreich sind.

---

# 13. Out of Scope

Nicht Bestandteil sind:

- Runtime Integration,
- Composition Root,
- Compatibility Adapter,
- produktive Adapter,
- Local Reconciliation Store Adapter,
- xTree JSON API Adapter,
- Route-Priorisierung,
- Fallback-Routen,
- Route Selector,
- parallele oder hybride Discovery,
- HTTP, REST oder SPARQL,
- Änderungen am `ReconciliationCommand`,
- semantische Validierung zwischen Target Vocabulary und Discovery Scope.

---

# 14. Engineering-Votum

Der vorgeschlagene Schnitt bleibt klein:

- eine Router-Klasse,
- zwei spezifische Exception-Klassen,
- ein fokussierter Unit Test,
- keine Änderungen an bestehenden Dateien,
- keine Runtime-Verdrahtung.

Die Route-Auswahl bleibt gemäß LA Review 002 bewusst im Router. Ein eigener `DiscoveryRouteSelector` wird erst eingeführt, wenn eine reale Auswahlpolitik wie Prioritäten, Fallbacks, Tenant-Regeln oder Hybrid Discovery entsteht.
