# WP3-002 – Implementation Plan
## Local Reconciliation Store Adapter

- **Status:** Proposed for PL approval
- **Version:** 0.1
- **Scope:** WP3 – Candidate Discovery
- **Basis:** `reconcile_ADR002.WP2_20260801_7.zip`
- **References:**
  - PRE_WP3-001 – Architekturübersicht Discovery Framework
  - WP3-001 – Discovery Framework Foundation
  - WP3-001-003 – Candidate Discovery Adapter Contract and Registry
  - WP3-001-004 – Candidate Discovery Router
  - WP3-001-005 – Runtime Integration
  - ADR002 – Source Delivery Model
  - DM001 – Domain Model

---

# 1. Ziel

WP3-002 bindet den bestehenden Local Reconciliation Store als ersten produktiven `CandidateDiscoveryAdapter` an das Discovery Framework an.

Nach Abschluss dieses Work Packages wird eine Discovery-Anfrage für einen entsprechend konfigurierten Discovery Scope nicht mehr über

```text
CandidateDiscoveryRouter
→ LegacyDiscoveryCompatibilityAdapter
→ LegacyCandidateDiscoveryAdapter
→ ReconciliationOrchestrator
→ ReconciliationStoreProvider
```

verarbeitet, sondern direkt über

```text
CandidateDiscoveryRouter
→ LocalReconciliationStoreAdapter
→ ReconciliationStoreProvider
→ DiscoveredCandidate[]
```

Das bestehende fachliche Suchverhalten des Local Reconciliation Store bleibt die Referenz.

---

# 2. Befund im aktuellen Projektstand

Der Local Reconciliation Store ist bereits produktiv funktionsfähig.

Die bestehende Kette lautet:

```text
ReconciliationOrchestrator
        │
        ├── ermittelt einen aktivierten ReconciliationStore
        ├── prüft Tenant-Berechtigung
        ├── lädt Provider `reconciliation-store`
        │
        ▼
ReconciliationStoreProvider
        │
        ├── lädt `terms.json`
        ├── führt normalisierten Exact Lookup aus
        ├── verwendet bei Bedarf Token Lookup
        ├── begrenzt auf Query-Limit
        │
        ▼
Candidate[]
```

Der `LegacyCandidateDiscoveryAdapter` mappt anschließend:

```text
Candidate[]
→ DiscoveredCandidate[]
```

Für WP3-002 wird diese bestehende Suchimplementierung wiederverwendet.

Nicht neu implementiert werden:

- Dateizugriff auf `terms.json`,
- Textnormalisierung,
- QueryTokenizer,
- Gewichtung,
- Trefferbegrenzung,
- Candidate-Erzeugung.

---

# 3. Engineering-Grundsatz

WP3-002 ist eine Re-Integration, keine Neuentwicklung des Local Store.

Der neue Adapter

- delegiert die Suche an den bestehenden `ReconciliationStoreProvider`,
- übersetzt `ReconciliationCommand` in `ReconciliationQuery`,
- mappt `Candidate` nach `DiscoveredCandidate`,
- besitzt genau einen konfigurierten `ReconciliationStore`,
- wird über einen eindeutigen `adapterKey` registriert.

Damit wird die bestehende Funktionalität hinter den neuen Adapter Contract verschoben, ohne sie zu duplizieren.

---

# 4. Adapterinstanz pro Reconciliation Store

Der `CandidateDiscoveryAdapter` erhält die `DiscoveryRoute` nicht als Methodenparameter.

Deshalb wird nicht ein globaler Local-Store-Adapter mit interner Store-Auswahl eingeführt.

Stattdessen wird pro aktivem `ReconciliationStore` eine konfigurierte Adapterinstanz erzeugt:

```text
LocalReconciliationStoreAdapter
├── scopeVocabularyUri
├── ReconciliationStore
├── TenantContext
├── ReconciliationStoreProvider
└── candidateProviderUri
```

Beispiel:

```text
adapterKey =
local-reconciliation-store:matcult-the_vocnet_org_00000019
```

Die zugehörige `DiscoveryRoute` verweist genau auf diesen Key.

Vorteile:

- keine zweite Store-Registry im Adapter,
- keine versteckte Auswahlregel,
- kein Zugriff des Adapters auf `DiscoveryConfigurationRegistry`,
- eindeutige Beziehung Route → Adapterinstanz,
- spätere Unterstützung mehrerer Stores ohne Umbau des Contracts.

---

# 5. Neue Produktionsklasse

## 5.1 LocalReconciliationStoreAdapter

**Pfad**

```text
src/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapter.php
```

**Namespace**

```php
App\Infrastructure\Discovery\Adapter
```

**Typ**

```php
final readonly class LocalReconciliationStoreAdapter
    implements CandidateDiscoveryAdapter
```

## Konstruktor

```php
public function __construct(
    private string $scopeVocabularyUri,
    private ReconciliationStore $store,
    private ReconciliationStoreProvider $provider,
    private TenantContext $tenant,
    private string $candidateProviderUri,
)
```

## Contract

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

---

# 6. Adapter-Ablauf

Der Adapter verarbeitet eine Anfrage in folgenden Schritten:

1. Discovery Scope aus dem Command bestimmen:

```text
subVocabularyId ?? targetVocabularyUri
```

2. Prüfen, ob der Command-Scope dem konfigurierten `scopeVocabularyUri` entspricht.

3. Bei einem SubVocabulary die Tenant-Berechtigung prüfen.

4. Aus dem Command eine bestehende `ReconciliationQuery` erzeugen.

5. `ReconciliationStoreProvider::search(...)` aufrufen:

```php
$provider->search(
    query: $legacyQuery,
    tenant: $tenant,
    vocabularyConfig: ['store' => $store],
);
```

6. Jedes `Candidate` in ein `DiscoveredCandidate` mappen.

7. Ergebnisreihenfolge unverändert zurückgeben.

---

# 7. Scope-Validierung

Der Adapter ist an genau einen Discovery Scope gebunden.

Wenn der Command einen anderen Scope enthält, wird ein Konfigurationsfehler ausgelöst:

```text
LocalReconciliationStoreAdapter scope mismatch:
expected <configuredScope>, got <commandScope>
```

Dieser Fall darf im regulären Runtime-Pfad nicht auftreten, weil der Router den Adapter über die passende Route auflöst.

Die explizite Prüfung verhindert jedoch eine unbemerkte Fehlverdrahtung.

---

# 8. Tenant-Berechtigung

Das bisherige Verhalten des `ReconciliationOrchestrator` wird erhalten.

Für einen Command mit gesetztem `subVocabularyId` gilt:

```php
if (!$tenant->allowsSubVocabulary($command->subVocabularyId)) {
    return [];
}
```

Der Adapter führt keine neue Tenant-Logik ein.

Er übernimmt lediglich die bislang im Orchestrator liegende Prüfung für den direkten Local-Store-Pfad.

---

# 9. Mapping Candidate → DiscoveredCandidate

Das Mapping entspricht dem bisherigen `LegacyCandidateDiscoveryAdapter`:

```text
Candidate.id
→ DiscoveredCandidate.uri

Candidate.label
→ DiscoveredCandidate.label

Candidate.score (0–100)
→ DiscoveredCandidate.score (0.0–1.0)

Candidate.match
→ DiscoveredCandidate.match

CONCEPT
→ DiscoveredCandidate.entityTypeUri

candidateProviderUri
→ DiscoveredCandidate.candidateProviderUri

Candidate.meta
→ DiscoveredCandidate.metadata
```

Die Score-Normalisierung bleibt:

```php
max(0.0, min(1.0, $score / 100))
```

WP3-002 führt bewusst noch keinen gemeinsamen Candidate Mapper ein.

Die kleine Mappingstrecke wird erst extrahiert, wenn mindestens ein weiterer produktiver Adapter denselben Mapper tatsächlich benötigt.

---

# 10. Runtime-Integration

## 10.1 ReconciliationCompositionRoot

**Datei**

```text
src/Infrastructure/Composition/ReconciliationCompositionRoot.php
```

Der `ReconciliationStoreProvider` wird einmal erzeugt und

- im bestehenden `ProviderRegistry` registriert,
- sowie an die `RuntimeFactory` übergeben.

Dadurch verwenden Legacy-Pfad und neuer Adapter dieselbe stateless Providerinstanz.

## 10.2 RuntimeFactory

**Datei**

```text
src/Infrastructure/Factory/RuntimeFactory.php
```

Die Factory erhält zusätzlich:

```php
private readonly ReconciliationStoreProvider $localStoreProvider
```

Beim Erzeugen der Routes und Adapter gilt:

### Root Vocabulary

```text
kein direkt konfigurierter ReconciliationStore
→ Legacy Route bleibt bestehen
```

### SubVocabulary ohne aktivierten Store

```text
→ Legacy Route bleibt bestehen
```

### SubVocabulary mit genau einem aktivierten Store

```text
DiscoveryRoute
candidateSource = local-reconciliation-store
discoveryMethod = exact-with-token-fallback
adapterKey      = local-reconciliation-store:<storeId>
```

und

```text
CandidateDiscoveryAdapterRegistry
adapterKey
→ LocalReconciliationStoreAdapter
```

### SubVocabulary mit mehr als einem aktivierten Store

```text
→ expliziter Runtime-Konfigurationsfehler
```

Es gilt kein implizites „erster Store gewinnt“.

---

# 11. Anpassung der Registry-Erzeugung

Die bisherige `RuntimeFactory::createDiscoveryRoutes()` erzeugt ausschließlich Legacy-Routen.

Für WP3-002 wird die Registry-Konfiguration gemeinsam aufgebaut:

```text
DiscoveryRuntimeConfiguration
├── routes
└── adapters
```

KISS-Variante ohne neue Produktionsklasse:

```php
/** @return array{
 *   routes: list<DiscoveryRoute>,
 *   adapters: array<string, CandidateDiscoveryAdapter>
 * }
 */
private function createDiscoveryConfiguration(
    RuntimeContext $runtimeContext,
    LegacyDiscoveryCompatibilityAdapter $legacyAdapter,
): array
```

Eine eigene Configuration-Builder-Klasse wird erst eingeführt, wenn WP3-003 oder WP3-004 die Factory andernfalls unübersichtlich macht.

---

# 12. Bestehender Legacy-Pfad

Der bestehende `ReconciliationOrchestrator` und der dortige Local-Store-Zweig bleiben in WP3-002 unverändert.

Damit kann der Regressionstest vergleichen:

```text
A: direkter Legacy-Pfad über ReconciliationOrchestrator

B: neuer Discovery-Framework-Pfad über LocalReconciliationStoreAdapter
```

Die Entfernung des Local-Store-Zweigs aus dem Orchestrator erfolgt nicht in WP3-002.

Erst nach erfolgreicher Einführung der produktiven Adapter kann Legacy-Code separat bereinigt werden.

---

# 13. Neue Tests

## 13.1 LocalReconciliationStoreAdapterTest

**Pfad**

```text
tests/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapterTest.php
```

Der Test verwendet den realen `ReconciliationStoreProvider` und eine kontrollierte temporäre `terms.json`.

Testfälle:

1. Adapter implementiert `CandidateDiscoveryAdapter`.
2. Exakter Treffer wird gefunden.
3. Token-Fallback wird verwendet.
4. Ergebnislimit wird eingehalten.
5. Score wird von 0–100 auf 0.0–1.0 normalisiert.
6. Match-Status bleibt erhalten.
7. Metadaten bleiben erhalten.
8. Ergebnisreihenfolge bleibt erhalten.
9. Tenant ohne SubVocabulary-Berechtigung erhält leere Liste.
10. Falscher Discovery Scope erzeugt einen eindeutigen Fehler.
11. Fehlende oder deaktivierte Store-Datei liefert wie bisher eine leere Liste.

Erwartete Ausgabe:

```text
WP3-002 Local Reconciliation Store Adapter: OK
```

---

## 13.2 LocalReconciliationStoreRuntimeIntegrationTest

**Pfad**

```text
tests/Integration/LocalReconciliationStoreRuntimeIntegrationTest.php
```

Testfälle:

1. Ein SubVocabulary mit aktivem Store erhält eine Local-Store-Route.
2. Die Route verwendet einen eindeutigen Local-Store-Adapter-Key.
3. Der Adapter ist in `CandidateDiscoveryAdapterRegistry` registriert.
4. Ein SubVocabulary ohne Store verwendet weiterhin `legacy-discovery`.
5. Ein Root Vocabulary verwendet weiterhin `legacy-discovery`.
6. Router delegiert Objektfacetten-Anfragen direkt an den Local Adapter.
7. Die `LegacyDiscoveryCompatibilityAdapter` wird für diesen Scope nicht aufgerufen.

Erwartete Ausgabe:

```text
WP3-002 Local Reconciliation Store Runtime Integration: OK
```

---

## 13.3 LocalReconciliationStoreRegressionTest

**Pfad**

```text
tests/Integration/LocalReconciliationStoreRegressionTest.php
```

Für denselben Command und denselben Store werden verglichen:

```text
Legacy:
ReconciliationOrchestrator
→ ReconciliationStoreProvider

Neu:
CandidateDiscoveryRouter
→ LocalReconciliationStoreAdapter
→ ReconciliationStoreProvider
```

Verglichen werden:

- Anzahl,
- Reihenfolge,
- URI,
- Label,
- Score,
- Match,
- Entity Type,
- Candidate Provider URI,
- Metadata.

Erwartete Ausgabe:

```text
WP3-002 Local Reconciliation Store Regression: OK
```

---

# 14. Änderungen an bestehenden Tests

Voraussichtlich anzupassen:

```text
tests/Factory/RuntimeFactoryDiscoveryIntegrationTest.php
tests/Integration/DiscoveryRuntimeRegressionTest.php
```

Die bisherigen Aussagen zum Legacy-Pfad bleiben für Root Vocabularies und SubVocabularies ohne Store gültig.

Für die MCT-Objektfacette wird künftig der produktive Local Adapter erwartet.

---

# 15. Geplanter Änderungsumfang

## Neue Produktionsdatei

```text
src/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapter.php
```

## Geänderte Produktionsdateien

```text
src/Infrastructure/Factory/RuntimeFactory.php
src/Infrastructure/Composition/ReconciliationCompositionRoot.php
```

## Unveränderte produktive Bestandteile

```text
src/Provider/LocalStore/ReconciliationStoreProvider.php
src/ReconciliationStore/ReconciliationStore.php
src/ReconciliationStore/QueryTokenizer.php
src/Orchestrator/ReconciliationOrchestrator.php
src/Infrastructure/Reconciliation/LegacyCandidateDiscoveryAdapter.php
src/Infrastructure/Discovery/Routing/CandidateDiscoveryRouter.php
src/Infrastructure/Discovery/Configuration/DiscoveryConfigurationRegistry.php
src/Infrastructure/Discovery/Adapter/CandidateDiscoveryAdapterRegistry.php
```

## Neue Testdateien

```text
tests/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapterTest.php
tests/Integration/LocalReconciliationStoreRuntimeIntegrationTest.php
tests/Integration/LocalReconciliationStoreRegressionTest.php
```

## Voraussichtlich geänderte Testdateien

```text
tests/Factory/RuntimeFactoryDiscoveryIntegrationTest.php
tests/Integration/DiscoveryRuntimeRegressionTest.php
```

---

# 16. Akzeptanzkriterien

WP3-002 ist abgeschlossen, wenn:

1. `LocalReconciliationStoreAdapter` den `CandidateDiscoveryAdapter` implementiert.
2. Der Adapter die bestehende `ReconciliationStoreProvider`-Logik wiederverwendet.
3. Ein Adapter genau einem aktivierten `ReconciliationStore` zugeordnet ist.
4. Ein SubVocabulary mit genau einem aktivierten Store eine Local-Store-Route erhält.
5. Root Vocabularies und Scopes ohne Store weiterhin über die Legacy Route laufen.
6. Mehrere aktivierte Stores für denselben Scope als Konfigurationsfehler behandelt werden.
7. Tenant-Berechtigungen dem bisherigen Verhalten entsprechen.
8. `Candidate[]` korrekt nach `DiscoveredCandidate[]` gemappt werden.
9. Der neue Runtime-Pfad und der bestehende Legacy-Pfad identische Ergebnisse liefern.
10. Router, Registries, Application Service und Domain Model unverändert bleiben.
11. Alle neuen Tests erfolgreich sind.
12. Alle Tests aus WP3-001 weiterhin erfolgreich sind.
13. OpenRefine-Reconciliation mit der MCT-Objektfacette weiterhin erfolgreich ist.

---

# 17. Out of Scope

Nicht Bestandteil sind:

- xTree JSON API Adapter,
- Entfernung des Local-Store-Zweigs aus dem `ReconciliationOrchestrator`,
- Entfernung des `ReconciliationStoreProvider` aus dem Legacy `ProviderRegistry`,
- gemeinsamer Candidate Mapper,
- persistente Discovery Configuration,
- Prioritäten oder Fallback-Routen,
- mehrere aktive Local Stores pro Scope,
- Hybrid Discovery,
- Ranking oder Result Fusion,
- Änderungen am Domain Model,
- Änderungen am Persistence Model,
- neue Store-Formate oder Änderungen an `terms.json`.

---

# 18. Risikoanalyse

## R1 – Verhaltensabweichung zum Legacy-Pfad

**Gegenmaßnahme**

Der Adapter verwendet denselben `ReconciliationStoreProvider`. Ein feldweiser Regressionstest vergleicht beide Pfade.

## R2 – Doppelte Store-Auswahl

**Gegenmaßnahme**

Jede Adapterinstanz besitzt genau einen Store. Der Adapter sucht nicht selbst in der Vocabulary-Konfiguration.

## R3 – Mehrere Stores für denselben Scope

**Gegenmaßnahme**

Expliziter Konfigurationsfehler statt implizitem „erster Store gewinnt“.

## R4 – Zu früher Legacy-Abbau

**Gegenmaßnahme**

Orchestrator und Legacy Provider Flow bleiben bis zum erfolgreichen Abschluss der produktiven Adapter unverändert.

## R5 – RuntimeFactory wird zu komplex

**Gegenmaßnahme**

Für WP3-002 wird zunächst eine private Konfigurationsmethode verwendet. Eine eigene Builder-Klasse wird nur bei realem Bedarf in WP3-003 oder WP3-004 eingeführt.

---

# 19. Engineering-Votum

Der aktuelle Projektstand bestätigt die ursprüngliche Vermutung:

```text
Der größte Teil der Local-Store-Funktionalität existiert bereits.
```

WP3-002 benötigt daher keine neue Suchimplementierung.

Der eigentliche neue Code besteht aus:

- einem schlanken Adapter,
- einer gezielten Runtime-Verdrahtung,
- und Regressionstests gegen den bewährten Legacy-Pfad.

Damit beweist WP3-002 erstmals praktisch, dass eine konkrete Discovery-Technologie modular an das in WP3-001 eingeführte Framework angebunden werden kann.
