0) { usleep($latencyMs * 1000); } // 2. Rate limiting — throw ApplicationFailure with nextRetryDelay $rl = $config['rateLimiting'] ?? []; if (!empty($rl['enabled']) && ($rl['hitChance'] ?? 0) > 0) { if (rand(1, 100) <= $rl['hitChance']) { $retryAfterMs = $rl['retryAfterMs'] ?? 2000; $retrySeconds = (int) ceil($retryAfterMs / 1000); throw new ApplicationFailure( "Rate limited (429) on {$activityName}", 'RateLimited', false, // nonRetryable = false → Temporal WILL retry null, null, \DateInterval::createFromDateString("{$retrySeconds} seconds"), ); } } // 3. Random failure — plain RuntimeException (Temporal retries automatically) $failureRate = $config['failureRate'] ?? 0; if ($failureRate > 0 && rand(1, 100) <= $failureRate) { throw new \RuntimeException("Simulated failure on {$activityName}"); } } }