---
name: platform-workflow
description: Workflow-Engine API used by FlowDesigner-generated Use-Case orchestrators — six routing statuses (`ON_SUCCESS` / `ON_FAIL` / `ON_TIMEOUT` / `ON_SKIP` / `ON_CANCEL` / `ON_EVENT`), `WorkflowBuilder` graph construction, `handlerFactory` Closure conventions, three opaque `WorkflowContext` slots (`reference`, `response`, `exception`), R5 routing-safety rules.
zone: post-active
persona: C
prerequisites: [platform-implementation]
next: []
---

The FlowDesigner-Orchestrator (`<Name>Handler.php`) wires a `WorkflowConfig` and calls `$workflow($config, $dto)`; the Engine walks the graph, invokes each Node-Action-Stub `__invoke(WorkflowContextInterface): WorkflowResultInterface`, and stamps every result with its producing handler FQCN. Knowing this API is mandatory both for Node bodies (which return a `WorkflowResult`) and for hand-written Orchestrator-Mantel-Code.

### 1. The six routing statuses

A Node returns exactly one of these via `new WorkflowResult(WorkflowResult::ON_*, $payload)`:

| Konstante | Bedeutung |
|---|---|
| `ON_SUCCESS` | Erfolgreicher Abschluss des Handlers — Default-Happy-Path. |
| `ON_FAIL` | Fachlicher Misserfolg (Validierung, Geschaeftsregel verletzt). |
| `ON_TIMEOUT` | Geplanter Recovery-Pfad: Service-Side-Timeout in fachliches Routing uebersetzt. |
| `ON_SKIP` | Handler nicht anwendbar — Flow ueberspringt zum Re-Konvergenz-Punkt. |
| `ON_CANCEL` | Fachlicher Abbruch (Stornierung, Zustimmung zurueckgezogen) — Cleanup-Pfad. |
| `ON_EVENT` | Aktiver async-Hand-off via DomainEvent — Folge-Runs entstehen extern (kein synchroner Folge-Node). |

User-Code laesst `handlerFqcn` immer `null` — die Engine stamped es via `WorkflowResult::withHandler()` selbst, sobald sie das Ergebnis in den Context anhaengt.

### 2. WorkflowBuilder im `config()`-Body

Der Generator emittiert den Builder-Aufruf; Hand-edits am Orchestrator (z.B. zusaetzliche Transition) folgen demselben Muster:

```php
protected function config(): WorkflowConfigInterface
{
    return (new WorkflowBuilder())
        ->node(ValidateInput::class)
            ->onSuccess(LoadAggregate::class)
            ->onFail(RejectInput::class)
        ->node(LoadAggregate::class)
            ->onSuccess(MutateState::class)
            ->onSkip(RejectInput::class)
        ->node(MutateState::class)
            ->onSuccess(PersistAndEmit::class)
            ->onCancel(CompensateState::class)
        ->node(PersistAndEmit::class)
        ->node(RejectInput::class)
        ->node(CompensateState::class)
        ->build();
}
```

End-Nodes (kein `on*()`-Aufruf) lassen die Engine ordentlich beenden.

### 3. handlerFactory-Closure

`new Workflow($factory)` akzeptiert optional `Closure(string $fqcn, mixed $data): object`. Die Konvention im Aggregat-Kontext ist `fn($cls, $data) => $this->context($cls, $data)`, sodass jeder Node eine frische BC mit `$data` als Payload bekommt — Nodes lesen es via `$this->payload()`. Bei `$data === null` ist `$this->handle($cls)` der Default, und der aeussere Payload bleibt erhalten. Ohne Factory ruft die Engine `new $fqcn()` (`$data` ignoriert) — fuer puren PHP-Code ausserhalb des Aggregat-Kontexts brauchbar.

### 4. Drei opake Context-Slots

`WorkflowContext` traegt neben dem Result-Chain drei freie Slots, die *nicht* vom Routing inspiziert werden:

| Slot | Getter / Setter | Verwendung |
|---|---|---|
| `reference` | `reference()` / `setReference(mixed)` | Out-of-Band-Kanal Orchestrator → Node (z.B. vor-aufgeloeste Aggregat-Identitaet weiterreichen). |
| `response` | `response()` / `setResponse(mixed)` | Out-of-Band-Kanal Node → Orchestrator (z.B. `DomainResponse` aus dem End-Node ablegen, statt ueber `WorkflowResult.data`). |
| `exception` | `getException()` / `setException(\Throwable)` | Ein gefangenes `Throwable` einsteuern, ohne den Engine-Loop zu unterbrechen — Cleanup-Nodes koennen es auslesen und in die Response-Mapping-Schicht ueberfuehren. |

Slots sind explizit *nicht* fuer Daten gedacht, die zwischen sequentiellen Nodes fliessen — dafuer dienen `WorkflowResult.data` und `WorkflowContext::getPrevious() / getLatest($fqcn) / getAll($fqcn) / getChain()`.

### 5. R5 — Routing-Safety

Die Engine bricht ohne Throw ab, wenn

1. der aktuelle Handler keinerlei Transitions konfiguriert hat, **oder**
2. fuer den zurueckgegebenen Status keine Transition existiert, **oder**
3. das konfigurierte Transition-Target nicht selbst per `addNode()` registriert ist.

In allen drei Faellen erhaelt der Aufrufer den vollstaendigen `WorkflowContext` zurueck; die Verantwortung fuer "war das jetzt ein gewolltes Ende oder ein Konfigurationsfehler?" liegt beim Orchestrator-Mantel (typisch: `try/catch` + Pruefung von `$context->getException()` und `$context->getPrevious()`).

### Anchors

- `platform-implementation` (Generated baseline, override targets, decision tree).
- `support-workflow` (the engine implementation itself).
