# QDR-001B – Vector Discovery Infrastructure

**Status:** IMPLEMENTED / READY FOR SERVER SMOKE TEST  
**Datum:** 2026-08-30  
**Depends on:** QDR-001A – Reconciliation Run Foundation (closed)  
**Blocks:** QDR-001C – Rx Discovery Testbench

## 1. Ziel

Die bestehende Vector-/Qdrant-Reconcile-API wird als technischer Candidate-Discovery-Pfad in das vorhandene WP3 Discovery Framework eingebunden.

```text
ReconciliationCommand
        │
        ▼
CandidateDiscoveryRouter
        │ discoveryMethod = VECTOR_SIMILARITY
        ▼
DiscoveryRoute
        │
        ▼
VectorCandidateDiscoveryAdapter
        │
        ▼
QdrantReconcileClient
        │
        ▼
Vector Reconcile API
        │
        ▼
DiscoveredCandidate[]
```

QDR-001B persistiert selbst noch keine ReconciliationRuns oder Results. Diese Orchestrierung erfolgt in QDR-001C auf Basis der in QDR-001A geschaffenen Run-Ebene.

## 2. Wesentliche Implementierungsentscheidung: methodenbewusstes Routing

xTree und Vector Discovery können denselben `targetVocabularyUri` / `scopeVocabularyUri` verwenden. Eine reine Scope-Auswahl ist deshalb für parallele Discovery-Verfahren nicht ausreichend.

`ReconciliationCommand` erhält optional:

```php
public readonly ?string $discoveryMethod = null;
public readonly array $discoveryOptions = [];
```

`CandidateDiscoveryRouter` nutzt bei gesetzter Methode:

```text
scopeVocabularyUri + discoveryMethod
```

Für bestehende OpenRefine-/Legacy-Aufrufe bleibt `discoveryMethod = null`; das bisherige Scope-Routing und dessen Fehlermeldungen bleiben kompatibel.

Vector Discovery verwendet:

```text
VECTOR_SIMILARITY
```

## 3. VectorCandidateDiscoveryAdapter

Neue Klasse:

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

Adapter-ID:

```text
vector-qdrant
```

Discovery Method:

```text
VECTOR_SIMILARITY
```

Der Adapter ist nicht an MCT gekoppelt. Target- und Scope-URI werden beim Erzeugen des Adapters übergeben und gegen den Command geprüft.

## 4. Request Mapping

```text
ReconciliationCommand.requestId
→ requestId

ReconciliationCommand.targetVocabularyUri
→ targetVocabularyUri

ReconciliationCommand.subVocabularyId / targetVocabularyUri
→ scopeVocabularyUri

ReconciliationCommand.sourceValue
→ sourceValue.value

ReconciliationCommand.language
→ sourceValue.language
  (bei NULL: Client-Default)

ReconciliationCommand.preferredEntityTypeUri
→ sourceValue.preferredEntityType

ReconciliationCommand.contextProperties[]
→ contextItems[]

ReconciliationCommand.limit
→ options.limit

discoveryOptions.rerank
→ options.rerank

discoveryOptions.outputLanguage
→ options.outputLanguage
  (optional; sonst Client-Default)
```

ContextItems akzeptieren intern sowohl die bestehende OR-Struktur

```php
['pid' => '...', 'v' => '...']
```

als auch die für die Testbench natürlichere Form

```php
['typeUri' => '...', 'value' => '...', 'context_role' => 'item_discovery']
```

`context_role` wird **nicht** an die externe API übertragen. Die Rolle dient der Rx-seitigen Auswahl der ContextItems und wird im Discovery Audit dokumentiert.

## 5. Response Mapping

```text
results[].uri
→ DiscoveredCandidate.uri

results[].label
→ DiscoveredCandidate.label

results[].entityType
→ DiscoveredCandidate.entityTypeUri

results[].score
→ DiscoveredCandidate.score
```

Der Score wird unverändert übernommen. Es findet keine Normalisierung statt.

Die Response-Reihenfolge bleibt erhalten. Zusätzlich wird der technische Rank in `DiscoveredCandidate.metadata.rank` dokumentiert; bei der späteren Persistenz wird die Listenreihenfolge wie bisher zu `CandidateItem.rank`.

`match` ist für Vector Candidates in v0.1 `false`; ein fachlicher Match wird nicht aus dem Vector Score abgeleitet.

## 6. QdrantReconcileClient

Neue Klasse:

```text
src/Provider/External/QdrantReconcileClient.php
```

Verantwortung:

- POST JSON an den Vector Reconcile Endpoint,
- `Content-Type: application/json`,
- `X-API-Key`,
- Connect-/Request-Timeout,
- HTTP-Fehler,
- ungültiges JSON,
- fehlendes `results[]`.

Fehler werden als `ExternalServiceRequestException` mit vorhandenen Discovery-Audit-Fehlertypen gekapselt.

Der API-Key wird nicht in Exception-Meldungen eingebaut.

## 7. Provider-/Client-Konfiguration

`config/providers.php` enthält jetzt zusätzlich:

```php
'vector' => [
    'label' => 'Vector Discovery',
    'default_client' => 'qdrant-marburg',
    'clients' => [
        'qdrant-marburg' => [
            'class' => 'vector-qdrant',
            'reconcile_url' => 'https://halimede.digicult-verbund.de/qdrant/api/mct-reconcile/reconcile',
            'default_language' => 'de',
            'output_language' => 'de',
            'connect_timeout_seconds' => 10,
            'timeout_seconds' => 30,
        ],
    ],
],
```

Die konkrete Qdrant-Instanz ist damit konfigurierbar. Weitere Clients können später ohne Adapteränderung ergänzt werden.

## 8. Credentials

`CredentialStore` unterstützt zusätzlich konkrete technische Client-IDs.

Lokale Struktur:

```php
return [
    // bestehende Tenant-/Client-Credentials bleiben unverändert

    'clients' => [
        'qdrant-marburg' => [
            'api_key' => '...',
        ],
    ],
];
```

Im Änderungspaket liegt ausschließlich:

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

Das Beispiel darf **nicht** eine vorhandene `credentials.local.php` ersetzen. Auf dem Server wird nur der `clients.qdrant-marburg`-Block in die bestehende lokale Datei ergänzt.

## 9. SourceSystemFactory

`SourceSystemFactory` kann den neuen Client-Typ erzeugen:

```text
class = vector-qdrant
→ QdrantReconcileClient
```

Die Credentials werden dabei über die konkrete Client-ID (`qdrant-marburg`) geladen und nicht über Vocabulary- oder Tenant-Identität modelliert.

## 10. Discovery Audit

Der bestehende Discovery Access Audit wird verwendet.

Zusätzlich kann `DiscoveryAccessAuditEntry.details` technische, nicht geheime Metadaten aufnehmen. Für Vector Discovery werden dokumentiert:

```text
clientId
discoveryMethod
rerank
outputLanguage
contextTypeUris
contextRoles
contextItemCount
```

Bereits bestehende Audit-Felder liefern außerdem u. a.:

```text
targetVocabularyUri
scopeVocabularyUri
request/query ID
candidateCount
durationMs
HTTP status / error type
```

API-Keys werden nicht geloggt.

## 11. Fehlergrenzen

Kontrolliert behandelt werden:

- Netzwerk-/cURL-Fehler,
- Timeout,
- HTTP != 2xx,
- ungültiges JSON,
- Response ohne `results[]`,
- Candidate ohne URI / Label / Entity Type,
- nicht numerischer Score,
- Score außerhalb `0..1`.

Fehlerhafte Candidate-Daten werden an der Adaptergrenze abgewiesen und gelangen nicht in das Domain Model.

## 12. Tests

Neu:

```text
tests/Provider/External/QdrantReconcileClientTest.php
tests/Infrastructure/Discovery/Adapter/VectorCandidateDiscoveryAdapterTest.php
tests/Infrastructure/Discovery/Routing/VectorDiscoveryMethodRoutingTest.php
tests/Security/NamedClientCredentialStoreTest.php
tests/SourceSystem/VectorSourceSystemFactoryTest.php
```

Zusätzlich wurden bestehende Regressionstests ausgeführt:

```text
WP3-001-002 Discovery Configuration Registry: OK
WP3-001-004 Candidate Discovery Router: OK
CTX-001B Qualifier ContextItem: OK
WP1-002 Application Flow: OK
QDR-001A ReconciliationRun Domain: OK
QDR-001A ReconciliationRun Service: OK
```

## 13. Noch nicht Bestandteil von QDR-001B

Nicht implementiert:

- GUI,
- Auswahl einer SourceDelivery,
- Laden persistierter SourceValues,
- Context-Type-/Context-Role-Filter aus der DB,
- ReconciliationRun-/RunItem-Orchestrierung,
- InterpretationGraph/Result/Candidate-Persistenz für den Testbench-Lauf,
- LLM Tab,
- Ergebnisvergleich.

Diese Punkte gehören zu QDR-001C.

## 14. Server Smoke Test vor QDR-001C

Vor dem GUI ist sinnvoll, auf dem Rx-Server lediglich die lokale Credential-Konfiguration zu ergänzen und die Unit-/Regressionstests auszuführen.

Ein Live-Request gegen den Vector-Service ist optional; der eigentliche End-to-End-Live-Test erfolgt in QDR-001C mit persistierten SourceValues und ReconciliationRun.

## 15. Definition of Done

QDR-001B ist technisch erfüllt, wenn:

- der Qdrant Client konfigurierbar erzeugt werden kann,
- der Vector Adapter im WP3 Adapter Contract arbeitet,
- `VECTOR_SIMILARITY` einen parallelen Discovery-Pfad für denselben Scope selektieren kann,
- Request und Response korrekt gemappt werden,
- Scores unverändert bleiben,
- Rerank als technische Option übertragen wird,
- Credentials lokal bleiben,
- Discovery Audit keine Credentials enthält,
- die Tests grün sind.

## Hotfix 2026-08-30 – Text Context representation for current Vector API

The current Vector API validates Text Context (`https://reconcilix.vocnet.org/context/0001`) and requires a TEI root element. Rx continues to persist the original context representation unchanged. Only the technical Qdrant request mapping adapts the representation:

- Text Context already rooted in `<TEI>` / `<tei>`: sent unchanged.
- Text Context without TEI root: wrapped using the representation already proven by the existing vector discovery testbench: `<tei xmlns:dh="https://wasauchimmer.org">…</tei>`.
- Other context types, e.g. Qualifier (`.../0011`): not wrapped.

This is deliberately an adapter-level compatibility rule for the current endpoint. A future contract revision should make the representation explicit in `contextItems[]`; that contract change is outside this hotfix.

HTTP 4xx/5xx JSON responses with a string `detail` are now included in the raised exception message, while the full response preview remains available to Discovery Access Audit. This makes validation failures visible in Rx Discovery Testbench without exposing credentials.
