# ED-04-007 – Implementation Plan
## SourceValue → SourceDelivery Reference

- **Status:** Completed (2026-08-01, PL)
- **Version:** 0.1
- **Scope:** ED-04 – Source Delivery Integration
- **Basis:** `reconcile_ADR002.WP2_20260801_5.zip`
- **References:**
  - ADR002 – Source Delivery Model V4
  - DM001 – Domain Model v0.7
  - DM002 – Persistence Model v0.6

---

# 1. Ziel

ED-04-007 vervollständigt die in ADR002 und DM001 definierte Beziehung:

```text
SourceDelivery
        │
        ▼
SourceValue
```

Jeder neu erzeugte `SourceValue` referenziert genau die `SourceDelivery`, innerhalb derer er verarbeitet wurde.

Die Referenz wird

- im Domain Object `SourceValue`,
- im Application Flow,
- und in der Tabelle `source_value`

durchgängig geführt.

---

# 2. Befund im aktuellen Projektstand

Der aktuelle Code erzeugt bei einem OpenRefine-POST bereits eine `SourceDelivery`:

```text
ReconciliationController
        │
        ▼
OpenRefineSourceDeliveryRegistrar
        │
        ▼
SourceDeliveryService
        │
        ▼
source_delivery
```

Die zurückgegebene `SourceDelivery` wird im Controller derzeit jedoch verworfen.

Danach wird der `ReconciliationApplicationService` ohne `SourceDeliveryId` erzeugt:

```text
SourceDelivery wird registriert
        │
        ├── ID wird nicht weitergegeben
        │
        ▼
ReconciliationApplicationService
        │
        ▼
SourceValue ohne sourceDeliveryId
```

ED-04-007 schließt genau diese Lücke.

---

# 3. Korrigierter Datentyp

`source_delivery.id` ist im aktuellen Schema:

```sql
VARCHAR(36)
```

und enthält eine UUID v4.

Daher muss auch die neue Fremdschlüsselspalte lauten:

```sql
source_value.source_delivery_id VARCHAR(36)
```

Die frühere Annahme `BIGINT` war für den aktuellen Projektstand falsch.

---

# 4. Ziel-Laufzeitpfad

Nach ED-04-007 gilt:

```text
ReconciliationController
        │
        ▼
OpenRefineSourceDeliveryRegistrar.register(...)
        │
        ▼
SourceDelivery
        │
        ▼
SourceDeliveryId
        │
        ▼
ApplicationServiceFactory.create(tenant, sourceDeliveryId)
        │
        ▼
ReconciliationApplicationService
        │
        ▼
SourceValue::create(..., sourceDeliveryId)
        │
        ▼
SourceValueMapper
        │
        ▼
source_value.source_delivery_id
```

Die interne UUID der `SourceDelivery` ist unabhängig von der externen OpenRefine-`delivery_id`.

Die Übernahme der externen `delivery_id` bleibt Gegenstand von ED-04-008.

---

# 5. Domain-Änderung

## 5.1 SourceValue

**Datei**

```text
src/Domain/Reconciliation/SourceValue.php
```

`SourceValue` erhält die verpflichtende Property:

```php
private readonly SourceDeliveryId $sourceDeliveryId
```

Erforderliche Anpassungen:

- Konstruktor
- `SourceValue::create(...)`
- Getter:

```php
public function sourceDeliveryId(): SourceDeliveryId
```

Die Referenz ist nach der Erzeugung unveränderlich.

---

# 6. Application-Änderung

## 6.1 ApplicationServiceFactory

**Datei**

```text
src/Application/Reconciliation/ApplicationServiceFactory.php
```

Neue Signatur:

```php
public function create(
    TenantContext $tenant,
    SourceDeliveryId $sourceDeliveryId,
): ReconciliationApplicationService;
```

## 6.2 RuntimeFactory

**Datei**

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

`RuntimeFactory::create(...)` übernimmt die `SourceDeliveryId` und übergibt sie an den `ReconciliationApplicationService`.

Discovery Framework, Routes und Adapter bleiben unverändert.

## 6.3 ReconciliationApplicationService

**Datei**

```text
src/Application/Reconciliation/ReconciliationApplicationService.php
```

Der Service erhält:

```php
private readonly SourceDeliveryId $sourceDeliveryId
```

Beim Erzeugen des `SourceValue` wird diese ID übergeben:

```php
SourceValue::create(
    sourceDeliveryId: $this->sourceDeliveryId,
    ...
)
```

---

# 7. Controller-Änderung

**Datei**

```text
src/Controller/ReconciliationController.php
```

Bei POST:

```php
$sourceDelivery = $this->sourceDeliveryRegistrar->register(...);
```

Danach:

```php
$applicationService = $this->applicationServiceFactory->create(
    $tenant,
    $sourceDelivery->id(),
);
```

Wenn keine `SourceDeliveryRegistrar` konfiguriert ist, darf keine persistente Verarbeitung mit einem künstlichen oder leeren Identifier fortgesetzt werden.

Für die aktuelle produktive Konfiguration ist der Registrar vorhanden, sobald Datenbankzugriff aktiviert ist.

Die genaue Behandlung der nicht-persistenten Test-/Fallback-Konfiguration wird anhand der bestehenden Controller-Tests umgesetzt, ohne eine Fake-UUID in den Produktivcode einzuführen.

---

# 8. Persistence-Änderung

## 8.1 Migration

Neue Datei:

```text
database/migrations/0004_source_value_source_delivery.sql
```

Zielstruktur:

```sql
ALTER TABLE `source_value`
    ADD COLUMN `source_delivery_id` VARCHAR(36) NULL AFTER `id`,
    ADD KEY `idx_source_value_source_delivery` (`source_delivery_id`),
    ADD CONSTRAINT `fk_source_value_source_delivery`
        FOREIGN KEY (`source_delivery_id`)
        REFERENCES `source_delivery` (`id`)
        ON UPDATE RESTRICT
        ON DELETE RESTRICT;
```

## 8.2 Übergangsnullbarkeit

Fachlich ist `sourceDeliveryId` laut DM001 verpflichtend.

Die Migration führt die Spalte zunächst technisch `NULL`-fähig ein, weil bereits persistierte `source_value`-Datensätze keine rekonstruierbare `SourceDelivery` besitzen.

Für alle **neu** erzeugten `SourceValue` erzwingt das Domain Model die Referenz.

Eine spätere Datenbereinigung kann die Spalte auf `NOT NULL` umstellen, sobald Altbestände migriert oder verworfen wurden.

Damit vermeiden wir:

- eine destruktive Migration,
- erfundene Provenienz für Altbestände,
- und einen Migrationsfehler auf bestehenden Entwicklungsdatenbanken.

## 8.3 SourceValueMapper

**Datei**

```text
src/Infrastructure/Persistence/Mapper/SourceValueMapper.php
```

Anpassungen:

- `source_delivery_id` beim INSERT speichern,
- `source_delivery_id` beim SELECT laden,
- beim Rehydrieren `SourceDeliveryId::fromString(...)` verwenden.

Für neu persistierte Datensätze ist die ID verpflichtend.

Bei Altbeständen mit `NULL` wird eine `PersistenceException` ausgelöst, sobald ein solcher Datensatz als aktuelles Domain Object rehydriert werden soll. Es wird keine fiktive ID erzeugt.

---

# 9. Repository

`RelationalSourceValueRepository` bleibt strukturell unverändert.

Die neue Referenz wird vollständig durch

- das Domain Object,
- und den `SourceValueMapper`

getragen.

---

# 10. Betroffene bestehende Tests

Durch die neue verpflichtende Domain-Property müssen alle direkten `SourceValue::create(...)`-Aufrufe in Tests eine gültige `SourceDeliveryId` erhalten.

Dazu wird eine stabile Test-UUID verwendet, beispielsweise:

```text
11111111-1111-4111-8111-111111111111
```

Betroffen sind insbesondere:

- DomainFoundationTest
- ContextItemTest
- RehydrationTest
- RelationalRepositoriesTest
- ContextPersistenceTest
- ReconciliationApplicationService-bezogene Tests
- RuntimeFactory-bezogene Tests
- Controller-/Integrationstests mit `ApplicationServiceFactory`

---

# 11. Neue Tests

## 11.1 SourceValueSourceDeliveryReferenceTest

**Pfad**

```text
tests/Domain/Reconciliation/SourceValueSourceDeliveryReferenceTest.php
```

Prüft:

- `SourceValue` benötigt eine `SourceDeliveryId`,
- Getter liefert dieselbe ID,
- Referenz bleibt unveränderlich.

## 11.2 SourceValueSourceDeliveryPersistenceTest

**Pfad**

```text
tests/Persistence/SourceValueSourceDeliveryPersistenceTest.php
```

Prüft:

- Mapper schreibt UUID nach `source_delivery_id`,
- Mapper rehydriert dieselbe UUID,
- FK-Typ ist mit `source_delivery.id` kompatibel.

## 11.3 OpenRefineSourceValueDeliveryIntegrationTest

**Pfad**

```text
tests/Integration/OpenRefineSourceValueDeliveryIntegrationTest.php
```

Prüft die Kette:

```text
SourceDelivery registrieren
→ SourceDeliveryId an Application Service
→ SourceValue persistieren
→ source_value.source_delivery_id entspricht source_delivery.id
```

---

# 12. Geplanter Änderungsumfang

## Neue Dateien

```text
database/migrations/0004_source_value_source_delivery.sql
tests/Domain/Reconciliation/SourceValueSourceDeliveryReferenceTest.php
tests/Persistence/SourceValueSourceDeliveryPersistenceTest.php
tests/Integration/OpenRefineSourceValueDeliveryIntegrationTest.php
```

## Geänderte Produktionsdateien

```text
src/Domain/Reconciliation/SourceValue.php
src/Application/Reconciliation/ApplicationServiceFactory.php
src/Application/Reconciliation/ReconciliationApplicationService.php
src/Infrastructure/Factory/RuntimeFactory.php
src/Controller/ReconciliationController.php
src/Infrastructure/Persistence/Mapper/SourceValueMapper.php
```

## Geänderte Tests

Alle Tests, die `SourceValue` oder `ApplicationServiceFactory::create(...)` direkt erzeugen beziehungsweise aufrufen.

---

# 13. Nicht Bestandteil

ED-04-007 übernimmt nicht:

- OpenRefine-`delivery_id` aus GET,
- OpenRefine-`delivery_id` aus Manifest-Aufrufen,
- Mapping der externen `delivery_id` auf `external_delivery_id`,
- Wiederverwendung einer bestehenden Delivery anhand der externen ID,
- Änderungen am Discovery Framework,
- Änderungen an Candidate Discovery,
- SourceSystem Integration.

Diese Punkte gehören zu ED-04-008 beziehungsweise späteren Paketen.

---

# 14. Akzeptanzkriterien

ED-04-007 ist abgeschlossen, wenn:

1. `SourceValue` eine verpflichtende `SourceDeliveryId` besitzt.
2. `source_value` die Spalte `source_delivery_id` enthält.
3. Spalte und referenzierter Primärschlüssel beide `VARCHAR(36)` verwenden.
4. Index und Foreign Key vorhanden sind.
5. Der Controller die neu registrierte `SourceDeliveryId` an den Application Service weitergibt.
6. Der Application Service dieselbe ID in jedem erzeugten `SourceValue` verwendet.
7. Der Mapper die UUID speichert und rehydriert.
8. Mehrere Commands eines OpenRefine-POST dieselbe interne `SourceDeliveryId` erhalten.
9. Bestehende Candidate-Discovery- und OpenRefine-Ergebnisse unverändert bleiben.
10. Alle angepassten und neuen Tests erfolgreich sind.
11. Der OpenRefine-End-to-End-Test weiterhin erfolgreich ist.

---

# 15. Engineering-Votum

ED-04-007 ist kein reiner Datenbank-Foreign-Key-Patch.

Der aktuelle Code zeigt, dass die intern erzeugte `SourceDeliveryId` zusätzlich durch

```text
Controller
→ ApplicationServiceFactory
→ ReconciliationApplicationService
→ SourceValue
→ Mapper
```

gereicht werden muss.

Die Umsetzung bleibt dennoch klein und geradlinig:

- eine neue Migration,
- eine verpflichtende Domain-Referenz,
- ein zusätzlicher Parameter entlang des bestehenden Application-Pfads,
- Mapper-Erweiterung,
- fokussierte Tests.

Die externe OpenRefine-`delivery_id` bleibt davon klar getrennt und wird erst in ED-04-008 behandelt.
