order($run); $instance = $this->instance($run); // Do all fallible work FIRST, so a failure can't leave the instance // active. Onboarding tasks (idempotent) + credential delivery. foreach (['invite_users', 'upload_logo', 'connect_desktop', 'explore_apps'] as $key) { $instance->onboardingTasks()->firstOrCreate(['key' => $key], ['done' => false]); } $encrypted = $run->context('admin_password'); if ($encrypted !== null && ! $this->hasResource($run, 'credentials_sent')) { Notification::route('mail', $order->customer->email)->notify( new CloudReady($instance, (string) $instance->nc_admin_ref, Crypt::decryptString($encrypted)), ); // Record delivery so a retry after a crash doesn't re-send. $this->recordResource($run, $instance->host, 'credentials_sent', (string) $instance->nc_admin_ref); $run->forgetContext('admin_password'); } // Activate LAST — only once everything above succeeded. $instance->update(['status' => 'active']); $order->update(['status' => 'active']); return StepResult::advance(); } }