# Service Interface: Operation Runs

## `App\Services\OperationRunService`

### `ensureRun`
Idempotently creates or retrieves an active run.

```php
public function ensureRun(
    Tenant $tenant,
    string $type,
    array $inputs,
    ?User $initiator = null
): OperationRun
```

- **Logic**:
  1. Compute `hash = sha256(tenant_id + type + sorted_json(inputs))`.
  2. Try finding active run (`queued` or `running`) with this hash.
  3. If found, return it.
  4. If not found, create new `queued` run.
  5. Return run.
  6. If an existing active run is returned (dedupe), the initiator (`user_id`, `initiator_name`) MUST NOT be replaced.

- **Dispatch failure**:
  - If queue dispatch fails after a run was created, the system MUST NOT leave misleading queued runs; instead complete the run immediately as `failed` (e.g., failure code `queue.dispatch_failed`) and show a clear UI message.

### `updateRun`
Updates the status/outcome of a run.

```php
public function updateRun(
    OperationRun $run,
    string $status,
    ?string $outcome = null,
    array $summaryCounts = [],
    array $failures = []
): OperationRun
```

### `failRun`
Helper to fail a run immediately.

```php
public function failRun(OperationRun $run, Throwable $e): OperationRun
```

## `App\Jobs\Middleware\TrackOperationRun`
Middleware for Jobs to automatically handle `running` -> `completed`/`failed` transitions if bound to a run.

## `App\Listeners\SyncRestoreRunToOperationRun`
Listener for `RestoreRun` events to update the shadow `OperationRun`.
The adapter row is created/visible only once a restore run reaches `previewed` (or later).
