// // RetryPolicy.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Exponential backoff with jitter for transient upstream failures (429/5xx, // timeouts). Never retries once the first streamed byte has been forwarded // to the client — enforced by the caller in Phase 3's UpstreamCall path. // import Foundation struct RetryPolicy: Sendable { var maxAttempts = 3 var baseDelay: TimeInterval = 0.5 var maxDelay: TimeInterval = 8.0 /// Whether a failed attempt with this upstream status is worth retrying. func shouldRetry(status: Int, attempt: Int) -> Bool { guard attempt < maxAttempts else { return false } return status == 429 || (500...599).contains(status) } /// Full-jitter exponential backoff; honors an upstream Retry-After when given. func delay(attempt: Int, retryAfter: TimeInterval? = nil) -> TimeInterval { if let retryAfter, retryAfter > 0 { return min(retryAfter, maxDelay) } let exponential = min(baseDelay * pow(2, Double(attempt - 1)), maxDelay) return Double.random(in: 0...exponential) } }