operationRun = $operationRun; } public function handle(RestoreService $restoreService, AuditLogger $auditLogger): void { if (! $this->operationRun) { $this->fail(new \RuntimeException('OperationRun context is required for ExecuteRestoreRunJob.')); return; } $restoreRun = RestoreRun::with(['tenant', 'backupSet'])->find($this->restoreRunId); if (! $restoreRun) { return; } if ((int) ($restoreRun->operation_run_id ?? 0) !== (int) $this->operationRun->getKey()) { RestoreRun::withoutEvents(function () use ($restoreRun): void { $restoreRun->forceFill(['operation_run_id' => $this->operationRun?->getKey()])->save(); }); } if ($restoreRun->status !== RestoreRunStatus::Queued->value) { return; } app(SyncRestoreRunToOperationRun::class)->handle($restoreRun); $tenant = $restoreRun->tenant; $backupSet = $restoreRun->backupSet; if (! $tenant || ! $backupSet || $backupSet->trashed()) { $restoreRun->update([ 'status' => RestoreRunStatus::Failed->value, 'failure_reason' => 'Backup set is archived or unavailable.', 'completed_at' => CarbonImmutable::now(), ]); app(SyncRestoreRunToOperationRun::class)->handle($restoreRun->refresh()); if ($tenant) { $auditLogger->log( tenant: $tenant, action: 'restore.failed', context: [ 'metadata' => [ 'restore_run_id' => $restoreRun->id, 'backup_set_id' => $restoreRun->backup_set_id, 'reason' => 'Backup set is archived or unavailable.', ], ], actorEmail: $this->actorEmail, actorName: $this->actorName, resourceType: 'restore_run', resourceId: (string) $restoreRun->id, status: 'failed', ); } return; } try { app(WriteGateInterface::class)->evaluate($tenant, 'restore.execute'); } catch (ProviderAccessHardeningRequired $e) { $restoreRun->update([ 'status' => RestoreRunStatus::Failed->value, 'failure_reason' => $e->reasonMessage, 'completed_at' => CarbonImmutable::now(), ]); if ($this->operationRun) { app(\App\Services\OperationRunService::class)->updateRun( $this->operationRun, status: \App\Support\OperationRunStatus::Completed->value, outcome: \App\Support\OperationRunOutcome::Failed->value, failures: [[ 'code' => 'hardening.write_blocked', 'reason_code' => $e->reasonCode, 'message' => $e->reasonMessage, ]], ); } return; } $restoreRun->update([ 'status' => RestoreRunStatus::Running->value, 'started_at' => CarbonImmutable::now(), 'failure_reason' => null, ]); // Keep the canonical Monitoring/Operations adapter row in sync even if downstream // code performs restore-run updates without firing model events. app(SyncRestoreRunToOperationRun::class)->handle($restoreRun->refresh()); $auditLogger->log( tenant: $tenant, action: 'restore.started', context: [ 'metadata' => [ 'restore_run_id' => $restoreRun->id, 'backup_set_id' => $backupSet->id, ], ], actorEmail: $this->actorEmail, actorName: $this->actorName, resourceType: 'restore_run', resourceId: (string) $restoreRun->id, status: 'success', ); try { $restoreService->executeForRun( restoreRun: $restoreRun, tenant: $tenant, backupSet: $backupSet, actorEmail: $this->actorEmail, actorName: $this->actorName, ); app(SyncRestoreRunToOperationRun::class)->handle($restoreRun->refresh()); } catch (Throwable $throwable) { $restoreRun->refresh(); $safeReason = RunFailureSanitizer::sanitizeMessage($throwable->getMessage()); if ($restoreRun->status === RestoreRunStatus::Running->value) { $restoreRun->update([ 'status' => RestoreRunStatus::Failed->value, 'failure_reason' => $safeReason, 'completed_at' => CarbonImmutable::now(), ]); } app(SyncRestoreRunToOperationRun::class)->handle($restoreRun->refresh()); if ($tenant) { $auditLogger->log( tenant: $tenant, action: 'restore.failed', context: [ 'metadata' => [ 'restore_run_id' => $restoreRun->id, 'backup_set_id' => $backupSet->id, 'reason' => $safeReason, ], ], actorEmail: $this->actorEmail, actorName: $this->actorName, resourceType: 'restore_run', resourceId: (string) $restoreRun->id, status: 'failed', ); } throw $throwable; } } }