canBeRetried()) { throw new FailureNotRetriableException($failure->resolved_at !== null ? 'resolved' : 'dismissed'); } /** @var FormSubmission|null $submission */ $submission = FormSubmission::query()->withoutGlobalScopes()->find($failure->form_submission_id); if ($submission === null) { throw new ParentSubmissionGoneException; } try { DB::transaction(function () use ($submission): void { $result = $this->applicator->apply($submission); FormSubmission::query() ->whereKey($submission->id) ->update([ 'apply_status' => $result->applyStatus()->value, 'apply_completed_at' => now(), ]); }); $attempt = $this->recordSuccess($failure, $actor); return ['outcome' => 'succeeded', 'attempt' => $attempt]; } catch (Throwable $e) { $attempt = $this->recordFailure($failure, $submission, $actor, $e); return ['outcome' => 'failed', 'attempt' => $attempt]; } } private function recordSuccess(FormSubmissionActionFailure $failure, ?User $actor): FormSubmissionActionFailureRetryAttempt { return DB::transaction(function () use ($failure, $actor): FormSubmissionActionFailureRetryAttempt { /** @var FormSubmissionActionFailureRetryAttempt $attempt */ $attempt = FormSubmissionActionFailureRetryAttempt::query()->create([ 'form_submission_action_failure_id' => $failure->id, 'attempted_at' => now(), 'attempted_by_user_id' => $actor?->id, 'outcome' => 'succeeded', 'exception_class' => null, 'exception_message' => null, ]); $note = $actor instanceof User ? "Geslaagde retry door {$actor->name}" : 'Geslaagde retry (geautomatiseerd)'; FormSubmissionActionFailure::query() ->whereKey($failure->id) ->update([ 'retry_count' => DB::raw('retry_count + 1'), 'resolved_at' => now(), 'resolved_by_user_id' => $actor?->id, 'resolved_note' => $note, ]); return $attempt; }); } private function recordFailure( FormSubmissionActionFailure $failure, FormSubmission $submission, ?User $actor, Throwable $e, ): FormSubmissionActionFailureRetryAttempt { return DB::transaction(function () use ($failure, $submission, $actor, $e): FormSubmissionActionFailureRetryAttempt { /** @var FormSubmissionActionFailureRetryAttempt $attempt */ $attempt = FormSubmissionActionFailureRetryAttempt::query()->create([ 'form_submission_action_failure_id' => $failure->id, 'attempted_at' => now(), 'attempted_by_user_id' => $actor?->id, 'outcome' => 'failed', 'exception_class' => $e::class, 'exception_message' => $e->getMessage(), ]); FormSubmissionActionFailure::query() ->whereKey($failure->id) ->update(['retry_count' => DB::raw('retry_count + 1')]); // Per ARCH-BINDINGS §7.1 v1.2 retry-service asymmetry note + // RFC-WS-6 §Q3 v1.3 addition 2 — mirror ApplyBindingsOnFormSubmit's // outer-transaction failure path: same failure_response_code // classifier + apply_completed_at write. Single behaviour-change // point per the v1.3-delta D1 design. FormSubmission::query() ->whereKey($submission->id) ->update([ 'apply_status' => ApplyStatus::FAILED->value, 'apply_completed_at' => now(), 'failure_response_code' => FormBindingExceptionClassifier::classify($e), ]); return $attempt; }); } }