# WP3-003 – Implementation Plan
## xTree Candidate Discovery Adapter

- **Status:** Proposed for PL approval
- **Version:** 0.1
- **Scope:** WP3 – Candidate Discovery
- **Basis:** `reconcile_ADR002.WP2_20260801_8.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
  - WP3-002 – Local Reconciliation Store Adapter
  - ADR002 – Source Delivery Model
  - DM001 – Domain Model

---

# 1. Ziel

WP3-003 bindet die bestehende xTree JSON API als zweiten produktiven `CandidateDiscoveryAdapter` an das Discovery Framework an.

Nach Abschluss dieses Work Packages werden xTree-basierte Discovery Scopes direkt über

```text
CandidateDiscoveryRouter
→ XtreeJsonApiAdapter
→ XtreeJsonApiClient
→ XtreeJsonApiResultMapper
→ DiscoveredCandidate[]
```

verarbeitet.

Der bisherige `LegacyDiscoveryCompatibilityAdapter` bleibt für nicht migrierte Candidate Sources bestehen.

---

# 2. Befund im aktuellen Projektstand

Der technische xTree-Zugriff ist im aktuellen Projekt weiterhin vorhanden.

Bereits implementiert sind:

```text
XtreeJsonApiClient
XtreeJsonApiResultMapper
SourceSystemFactory
CredentialStore
```

Der Client unterstützt:

- Login über `login.php`,
- Session-Cookie `PHPSESSID`,
- `getSearchVocItemsByTerm`,
- `getFetchHierarchy`,
- JSON-Decodierung,
- HTTP-Fehlerbehandlung.

Der Mapper transformiert:

```text
xTree JSON Response
→ Candidate[]
```

und

- ignoriert gelöschte Concepts,
- bevorzugt deutsche `prefLabel`,
- verwendet anschließend ein beliebiges `prefLabel`,
- fällt zuletzt auf den ersten vorhandenen Term zurück,
- begrenzt die Trefferzahl,
- erzeugt derzeit einen konstanten Score von `80`.

Der bestehende `XtreeProvider::search()` ist dagegen nur ein Platzhalter und liefert aktuell immer eine leere Liste.

WP3-003 soll den funktionsfähigen Client und Mapper direkt hinter den neuen Adapter Contract setzen. Der Platzhalter-Provider wird dabei nicht zur neuen produktiven Integrationsschicht ausgebaut.

---

# 3. Referenz aus der früheren Implementierung

Der archivierte WP1-Pfad zeigt einen bereits funktionierenden xTree-Zugriff:

## Root Vocabulary

```text
Vocabulary
→ XtreeJsonApiClient.searchVocItemsByTerm(...)
```

## SubVocabulary

```text
SubVocabulary
→ nodeId aus SubVocabulary-URI
→ XtreeJsonApiClient.getFetchHierarchy(...)
```

Der frühere Call Flow lautet:

```text
OpenRefine Type
→ VocabularyRegistry
→ Vocabulary oder SubVocabulary auflösen
→ SourceSystemFactory
→ XtreeJsonApiClient
→ XtreeJsonApiResultMapper
→ Candidate[]
```

WP3-003 übernimmt daraus nur die technisch bewährte xTree-Aufruflogik.

OpenRefine-spezifische Auflösung und Response-Erzeugung bleiben außerhalb des Adapters.

---

# 4. Engineering-Grundsatz

WP3-003 ist eine Re-Integration bestehender xTree-Komponenten.

Nicht neu implementiert werden:

- Login,
- Cookie-Verwaltung,
- HTTP-Kommunikation,
- JSON-Decodierung,
- Concept-Filterung,
- Label-Auswahl,
- Candidate-Erzeugung.

Neu entstehen ausschließlich:

- ein `CandidateDiscoveryAdapter` für xTree,
- die direkte Runtime-Verdrahtung,
- fokussierte Tests,
- eine kleine Härtung der vorhandenen Client-Testbarkeit.

---

# 5. Adapterinstanz pro Discovery Scope

Wie bei WP3-002 wird pro xTree-Discovery-Scope eine konfigurierte Adapterinstanz erzeugt.

Jede Instanz besitzt:

```text
targetVocabularyUri
scopeVocabularyUri
XtreeJsonApiClient
XtreeJsonApiResultMapper
TenantContext
candidateProviderUri
```

Der Adapter entscheidet nicht selbst, welches Vocabulary oder SubVocabulary verwendet wird.

Diese Entscheidung ist bereits durch `DiscoveryRoute` und Runtime-Konfiguration getroffen.

---

# 6. Neue Produktionsklasse

## 6.1 XtreeJsonApiAdapter

**Pfad**

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

**Namespace**

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

**Typ**

```php
final readonly class XtreeJsonApiAdapter
    implements CandidateDiscoveryAdapter
```

## Konstruktor

```php
public function __construct(
    private string $targetVocabularyUri,
    private string $scopeVocabularyUri,
    private XtreeJsonApiClient $client,
    private XtreeJsonApiResultMapper $mapper,
    private TenantContext $tenant,
    private string $candidateProviderUri,
)
```

## Contract

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

---

# 7. Adapter-Ablauf

Der Adapter verarbeitet eine Anfrage in folgenden Schritten:

1. Discovery Scope aus dem Command bestimmen:

```text
subVocabularyId ?? targetVocabularyUri
```

2. Scope gegen die konfigurierte `scopeVocabularyUri` prüfen.

3. Target Vocabulary gegen die konfigurierte `targetVocabularyUri` prüfen.

4. Tenant-Berechtigung prüfen.

5. Abhängig vom Scope die xTree-Methode wählen.

6. Response über `XtreeJsonApiResultMapper` in `Candidate[]` transformieren.

7. `Candidate[]` nach `DiscoveredCandidate[]` mappen.

8. Reihenfolge unverändert zurückgeben.

---

# 8. Auswahl der xTree-Methode

## 8.1 Root Vocabulary

Wenn

```text
scopeVocabularyUri == targetVocabularyUri
```

gilt:

```php
$client->searchVocItemsByTerm(
    vocabularyUri: $targetVocabularyUri,
    searchTerm: trim($command->sourceValue),
    language: $command->language ?? 'all',
    jsonFull: 1,
);
```

## 8.2 SubVocabulary

Wenn

```text
scopeVocabularyUri != targetVocabularyUri
```

gilt:

```text
nodeId = basename(parse_url(scopeVocabularyUri, PHP_URL_PATH))
```

Danach:

```php
$client->getFetchHierarchy(
    vocabularyUri: $targetVocabularyUri,
    nodeId: $nodeId,
    searchTerm: trim($command->sourceValue),
    language: $command->language ?? 'all',
    jsonFull: 1,
);
```

Die Klasse prüft nicht, ob der Scope semantisch tatsächlich ein SubVocabulary ist.

Die Konfiguration bestimmt ausschließlich, welcher Scope auf welche Route zeigt.

---

# 9. Tenant-Berechtigung

Für Root Vocabularies gilt:

```php
if (!$tenant->allowsVocabulary($targetVocabularyUri)) {
    return [];
}
```

Für SubVocabularies gilt zusätzlich:

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

Der Adapter führt keine neue Berechtigungslogik ein.

Er übernimmt lediglich die bislang im alten Aufrufpfad verwendeten Tenant-Grenzen für den direkten xTree-Zugriff.

---

# 10. Mapping nach DiscoveredCandidate

Der vorhandene `XtreeJsonApiResultMapper` liefert `Candidate[]`.

Das anschließende Mapping entspricht WP3-002 und dem bisherigen Legacy Adapter:

```text
Candidate.id
→ DiscoveredCandidate.uri

Candidate.label
→ DiscoveredCandidate.label

Candidate.score
→ normalisiert auf 0.0–1.0

Candidate.match
→ DiscoveredCandidate.match

CONCEPT
→ entityTypeUri

candidateProviderUri
→ xTree API Basis-URI

Candidate.meta
→ metadata
```

Die Score-Normalisierung bleibt:

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

Ein gemeinsamer Candidate-Mapper wird noch nicht eingeführt.

Nach WP3-003 existieren zwar zwei produktive Adapter mit ähnlicher Mappingstrecke. Die Extraktion soll jedoch erst erfolgen, wenn die konkrete Gemeinsamkeit nach beiden Implementierungen stabil beurteilt werden kann.

---

# 11. Candidate Provider URI

Für xTree-Kandidaten wird als `candidateProviderUri` die konfigurierte Basis-URL des xTree JSON Clients verwendet:

```text
http://xtree-rest.digicult-verbund.de
```

Die Reconcilix-Service-URL darf für xTree-Ergebnisse nicht verwendet werden, weil sie nicht die technische Candidate Source bezeichnet.

Dazu erhält `XtreeJsonApiClient` einen lesenden Getter:

```php
public function baseUrl(): string
```

Alternativ könnte die Basis-URL parallel in den Adapter injiziert werden. Der Getter ist vorzuziehen, weil dadurch Client und Provider URI nicht auseinanderlaufen können.

---

# 12. Härtung des XtreeJsonApiClient

WP3-003 soll keine neue HTTP-Abstraktion einführen.

Für Testbarkeit und Fehlerdiagnose werden jedoch kleine, begrenzte Anpassungen vorgesehen.

## 12.1 Basis-URL-Getter

```php
public function baseUrl(): string
```

## 12.2 Keine Änderung des Login-Verhaltens

Weiterhin gilt:

```text
erste API-Anfrage
→ Login
→ PHPSESSID speichern

weitere API-Anfragen derselben Clientinstanz
→ Session-Cookie wiederverwenden
```

## 12.3 Fehler bleiben explizit

Bestehende Fehler bleiben erhalten:

- Login fehlgeschlagen,
- `PHPSESSID` fehlt,
- cURL-Fehler,
- HTTP-Status außerhalb 2xx,
- ungültiges JSON.

WP3-003 führt kein stilles Fallback auf leere Ergebnisse ein.

Ein externer technischer Fehler muss sichtbar bleiben.

---

# 13. Credentials und Client-Erzeugung

Der bestehende `SourceSystemFactory` kann bereits einen `XtreeJsonApiClient` tenant-spezifisch erzeugen.

Dafür benötigt er:

```text
providerConfig
CredentialStore
TenantContext
```

Die Credentials bleiben außerhalb des Repositorys in:

```text
config/credentials.local.php
```

## Runtime-Grenze

Der `XtreeJsonApiClient` wird innerhalb von `RuntimeFactory::create(...)` tenant-spezifisch erzeugt.

Dadurch gilt:

```text
Tenant A
→ eigener Client
→ eigene Session

Tenant B
→ eigener Client
→ eigene Session
```

Clientinstanzen und Session-Cookies werden nicht tenantübergreifend geteilt.

---

# 14. Composition Root

## 14.1 ReconciliationCompositionRoot

Der Composition Root erhält eine `SourceSystemFactory`.

Da `Configuration` den ursprünglichen Konfigurationspfad nicht speichert, wird der `CredentialStore` im Composition Root nicht implizit aus einem unbekannten Pfad erzeugt.

KISS-konforme Lösung:

```php
public function __construct(
    Configuration $configuration,
    ?DatabaseConnectionFactory $databaseConnectionFactory = null,
    ?SourceSystemFactory $sourceSystemFactory = null,
)
```

Wenn keine Factory injiziert wurde, darf der Composition Root für Tests ohne xTree-Route weiterhin aufgebaut werden.

Für die produktive Runtime wird die Factory explizit in `public/reconcile.php` erzeugt:

```php
$credentialStore = new CredentialStore($configDir);

$sourceSystemFactory = new SourceSystemFactory(
    $configuration->providers(),
    $credentialStore,
);

$compositionRoot = new ReconciliationCompositionRoot(
    configuration: $configuration,
    sourceSystemFactory: $sourceSystemFactory,
);
```

Damit bleibt der Credentials-Pfad dort, wo er tatsächlich bekannt ist.

---

# 15. RuntimeFactory

`RuntimeFactory` erhält zusätzlich:

```php
private readonly ?SourceSystemFactory $sourceSystemFactory
```

und erzeugt xTree-Adapter nur, wenn eine xTree-Route erforderlich ist.

## 15.1 Root Vocabulary mit sourceSystem = xtree

Für jedes entsprechende `Vocabulary`:

```text
DiscoveryRoute
candidateSource = xtree
discoveryMethod = term-search
adapterKey      = xtree-json:<scope>
```

## 15.2 SubVocabulary ohne Local Store

Wenn das zugehörige Root Vocabulary `sourceSystem = xtree` besitzt:

```text
DiscoveryRoute
candidateSource = xtree
discoveryMethod = hierarchy-search
adapterKey      = xtree-json:<scope>
```

## 15.3 SubVocabulary mit aktivem Local Store

Die in WP3-002 eingeführte Local-Store-Route bleibt unverändert vorrangig, weil pro Scope nur genau eine Route erzeugt wird.

Es entsteht keine zusätzliche xTree-Route für denselben Scope.

## 15.4 Nicht-xTree-Scopes

Diese bleiben zunächst auf `legacy-discovery`.

---

# 16. Auflösung des zugehörigen Root Vocabulary

Für ein `SubVocabulary` muss das zugehörige `Vocabulary` über

```text
SubVocabulary.conceptSchemeId
```

aufgelöst werden.

Für WP3-003 wird dafür eine private Hilfsmethode in `RuntimeFactory` verwendet:

```php
private function vocabularyForConceptScheme(
    string $conceptSchemeId,
): ?Vocabulary
```

Eine neue Vocabulary Registry wird nicht eingeführt, da für diesen begrenzten Runtime-Aufbau ein linearer Lookup ausreichend ist.

---

# 17. Adapter Keys

Die Adapter Keys werden deterministisch aus dem Discovery Scope gebildet:

```text
xtree-json:<normalisierter-scope>
```

Beispiele:

```text
xtree-json:matcult-the_vocnet_org
xtree-json:matcult-the_vocnet_org_00000375
xtree-json:lvr_vocnet_org_wnk
```

Die bestehende Normalisierungslogik aus WP3-002 für Store-IDs wird nicht zweckentfremdet.

Eine kleine private Methode in `RuntimeFactory` erzeugt den technischen Key.

Der Key ist reine Runtime-Konfiguration und keine fachliche URI.

---

# 18. Bestehender XtreeProvider

`XtreeProvider` bleibt in WP3-003 unverändert.

Er wird weiterhin für Preview verwendet.

Seine leere `search()`-Methode wird nicht als produktiver Discovery-Pfad reaktiviert.

Begründung:

- `ProviderInterface` gehört zum Legacy-Orchestrator,
- `CandidateDiscoveryAdapter` ist der neue Contract,
- ein Ausbau des Providers würde alte und neue Architektur erneut vermischen.

Eine spätere Bereinigung des Legacy Providers ist ein eigenes Refactoring und nicht Bestandteil von WP3-003.

---

# 19. Neue Tests

## 19.1 XtreeJsonApiAdapterTest

**Pfad**

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

Der Test verwendet einen kontrollierten Fake-Client beziehungsweise eine testbare Client-Variante ohne echten HTTP-Zugriff.

Testfälle:

1. Adapter implementiert `CandidateDiscoveryAdapter`.
2. Root Vocabulary verwendet `searchVocItemsByTerm`.
3. SubVocabulary verwendet `getFetchHierarchy`.
4. Node-ID wird korrekt aus der Scope-URI gebildet.
5. Sprache wird aus dem Command übernommen.
6. Fallback-Sprache ist `all`.
7. Ergebnislimit wird an den Mapper übergeben.
8. Gelöschte Concepts werden nicht zurückgegeben.
9. Deutsches `prefLabel` wird bevorzugt.
10. Candidate wird korrekt nach `DiscoveredCandidate` gemappt.
11. Score wird normalisiert.
12. Metadaten bleiben erhalten.
13. Falscher Scope erzeugt einen eindeutigen Fehler.
14. Fehlende Tenant-Berechtigung liefert eine leere Liste.

Erwartete Ausgabe:

```text
WP3-003 xTree JSON API Adapter: OK
```

---

## 19.2 XtreeRuntimeIntegrationTest

**Pfad**

```text
tests/Integration/XtreeRuntimeIntegrationTest.php
```

Testfälle:

1. MCT Root Vocabulary erhält eine xTree-Route.
2. WNK Root Vocabulary erhält eine xTree-Route.
3. MCT-Berufsfacette erhält eine xTree-Hierarchy-Route.
4. MCT-Objektfacette behält ihre Local-Store-Route.
5. Jeder xTree-Scope besitzt genau eine Route.
6. Jeder Adapter-Key ist in `CandidateDiscoveryAdapterRegistry` registriert.
7. Router delegiert Root-Suche an xTree.
8. Router delegiert Berufsfacetten-Suche an xTree.
9. Der Legacy Compatibility Adapter wird für migrierte xTree-Scopes nicht aufgerufen.
10. Tenant-gebundene Clients werden nicht zwischen Runtime-Instanzen geteilt.

Erwartete Ausgabe:

```text
WP3-003 xTree Runtime Integration: OK
```

---

## 19.3 XtreeApiContractTest

**Pfad**

```text
tests/Provider/Xtree/XtreeApiContractTest.php
```

Dieser Test prüft mit kontrollierten Responses den bestehenden Client-/Mapper-Vertrag:

- Query-Parameter für `getSearchVocItemsByTerm`,
- Query-Parameter für `getFetchHierarchy`,
- Login-Cookie-Nutzung,
- Behandlung ungültigen JSON,
- Behandlung nicht erfolgreicher HTTP-Statuscodes.

Der Test soll nicht gegen die externe produktive xTree-Instanz laufen.

Externe Erreichbarkeit wird separat als manueller Smoke-Test geprüft.

Erwartete Ausgabe:

```text
WP3-003 xTree API Contract: OK
```

---

# 20. Manueller xTree-Smoke-Test

Zusätzlich zu den automatisierten Tests wird in der Zielumgebung ein realer Smoke-Test durchgeführt.

## Root Vocabulary

Beispiel:

```text
MCT
Suchterm: Altarretabel
→ getSearchVocItemsByTerm
```

## SubVocabulary

Beispiel:

```text
MCT: Personen nach Beruf
Scope: http://matcult-the.vocnet.org/00000375
→ getFetchHierarchy
```

Geprüft werden:

- Login erfolgreich,
- HTTP 2xx,
- gültiges JSON,
- mindestens ein plausibler Candidate,
- korrekte URI,
- korrektes Label,
- OpenRefine-Response erfolgreich.

Credentials werden nicht geloggt.

---

# 21. Geplanter Änderungsumfang

## Neue Produktionsdatei

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

## Geänderte Produktionsdateien

```text
src/Provider/Xtree/XtreeJsonApiClient.php
src/Infrastructure/Factory/RuntimeFactory.php
src/Infrastructure/Composition/ReconciliationCompositionRoot.php
public/reconcile.php
```

## Unveränderte produktive Bestandteile

```text
src/Provider/Xtree/XtreeJsonApiResultMapper.php
src/Provider/Xtree/XtreeProvider.php
src/SourceSystem/SourceSystemFactory.php
src/Security/CredentialStore.php
src/Infrastructure/Discovery/Routing/CandidateDiscoveryRouter.php
src/Infrastructure/Discovery/Configuration/DiscoveryConfigurationRegistry.php
src/Infrastructure/Discovery/Adapter/CandidateDiscoveryAdapterRegistry.php
src/Infrastructure/Discovery/Adapter/LocalReconciliationStoreAdapter.php
```

## Neue Testdateien

```text
tests/Infrastructure/Discovery/Adapter/XtreeJsonApiAdapterTest.php
tests/Integration/XtreeRuntimeIntegrationTest.php
tests/Provider/Xtree/XtreeApiContractTest.php
```

## Voraussichtlich angepasste Tests

```text
tests/Factory/RuntimeFactoryDiscoveryIntegrationTest.php
tests/Integration/DiscoveryRuntimeRegressionTest.php
tests/Integration/LocalReconciliationStoreRuntimeIntegrationTest.php
tests/Composition/ReconciliationCompositionRootTest.php
```

---

# 22. Akzeptanzkriterien

WP3-003 ist abgeschlossen, wenn:

1. `XtreeJsonApiAdapter` den `CandidateDiscoveryAdapter` implementiert.
2. Der Adapter bestehende xTree Client- und Mapper-Klassen wiederverwendet.
3. Root Vocabularies über `getSearchVocItemsByTerm` abgefragt werden.
4. SubVocabularies über `getFetchHierarchy` abgefragt werden.
5. MCT-Objektfacette weiterhin ausschließlich den Local Store verwendet.
6. MCT-Berufsfacette direkt über xTree läuft.
7. Root Vocabulary MCT direkt über xTree läuft.
8. Root Vocabulary WNK direkt über xTree läuft.
9. Pro Scope genau eine aktive Discovery Route existiert.
10. xTree Adapter tenant-spezifische Clientinstanzen verwenden.
11. xTree-Kandidaten korrekt als `DiscoveredCandidate[]` zurückgegeben werden.
12. Candidate Provider URI auf den xTree-Dienst verweist.
13. Technische xTree-Fehler nicht stillschweigend verschluckt werden.
14. Alle neuen automatisierten Tests erfolgreich sind.
15. Alle Tests aus WP3-001 und WP3-002 weiterhin erfolgreich sind.
16. OpenRefine-Reconciliation über Local Store weiterhin erfolgreich ist.
17. OpenRefine-Reconciliation über xTree im manuellen Smoke-Test erfolgreich ist.

---

# 23. Out of Scope

Nicht Bestandteil sind:

- xTree Solr API,
- QLever- oder SPARQL-Zugriff,
- Qdrant,
- Lobid/GND,
- gemeinsamer Candidate Mapper,
- Ranking-Änderungen,
- dynamische Fallback Chains,
- mehrere aktive Routes pro Scope,
- Hybrid Discovery,
- Entfernung des Legacy `XtreeProvider`,
- Entfernung des `LegacyDiscoveryCompatibilityAdapter`,
- Änderung der xTree API,
- neue Credentials-Verwaltung,
- Retry-Strategien,
- Circuit Breaker,
- Caching,
- persistente Discovery Configuration.

---

# 24. Risikoanalyse

## R1 – Externer Dienst nicht erreichbar

**Gegenmaßnahme**

Automatisierte Tests verwenden keine externe xTree-Instanz. Ein manueller Smoke-Test prüft die reale Erreichbarkeit separat.

## R2 – Credentials fehlen

**Gegenmaßnahme**

`CredentialStore` bleibt die einzige Quelle. Fehlende Credentials führen zu einem expliziten Fehler.

## R3 – Root- und SubVocabulary verwenden falsche API-Methode

**Gegenmaßnahme**

Getrennte Adaptertests prüfen `searchVocItemsByTerm` und `getFetchHierarchy`.

## R4 – Doppelte Route für Local Store und xTree

**Gegenmaßnahme**

Ein aktiver Local Store besitzt Vorrang bei der Runtime-Konfiguration. Für denselben Scope wird keine zusätzliche xTree-Route erzeugt.

## R5 – Alte und neue Architektur werden vermischt

**Gegenmaßnahme**

`XtreeProvider::search()` bleibt unverändert. Produktive Discovery erfolgt ausschließlich über `XtreeJsonApiAdapter`.

## R6 – RuntimeFactory wird zu komplex

**Gegenmaßnahme**

WP3-003 ergänzt zunächst private Hilfsmethoden. Nach erfolgreicher Umsetzung wird vor WP3-004 geprüft, ob ein eigener Discovery Configuration Builder fachlich notwendig geworden ist.

---

# 25. Engineering-Votum

Der aktuelle Projektstand bestätigt:

```text
Der xTree-Zugriff ist technisch nicht verloren.
```

Vorhanden sind weiterhin:

- Login,
- Session,
- JSON API Client,
- Root-Vocabulary-Suche,
- Hierarchie-Suche,
- Response Mapper.

Verloren gegangen ist ausschließlich die produktive Verdrahtung im neuen Runtime-Pfad.

WP3-003 stellt diese Verdrahtung wieder her, verwendet dabei aber erstmals den neuen `CandidateDiscoveryAdapter`-Contract.

Damit wird WP3-003 zum zweiten praktischen Architekturbeweis:

```text
Local Store und externer xTree-Dienst
verwenden denselben Discovery Framework Contract.
```
