Polling Config
constructor(fetch: suspend () -> PollingResult<T>, isTerminalSuccess: (T) -> Boolean, shouldRetryOnError: (Error?) -> Boolean = { true }, backoff: BackoffPolicy = BackoffPolicy(), dispatcher: CoroutineDispatcher = Dispatchers.Default, onAttempt: (attempt: Int, delayMs: Long?) -> Unit = { _, _ -> }, onResult: (attempt: Int, result: PollingResult<T>) -> Unit = { _, _ -> }, onComplete: (attempts: Int, durationMs: Long, outcome: PollingOutcome<T>) -> Unit = { _, _, _ -> }, throwableMapper: (Throwable) -> Error = { t ->
val msg = t.message ?: (t::class.simpleName ?: "Throwable")
// Use a stable public default code without exposing internal ErrorCodes
Error(-1, msg)
})