resolveRetryBackoff function

RetryBackoffDecision resolveRetryBackoff({
  1. String? retryAfter,
  2. required int attempt,
  3. Duration? ladderStep,
})

Resolves ONE attempt's backoff delay (issue #1398 AC2):

  • a parseable delta-seconds Retry-After wins over the ladder for that attempt (RetryDelaySource.server), hard-capped at retryBackoffCeiling (RetryDelaySource.clamp — the trace names the advertised value the ceiling overrode);
  • Retry-After: 0 counts as absent (E2 — the server says "go now", which is what the ladder's short step already does);
  • a malformed header (non-numeric, negative, or the HTTP-date form) falls back to the ladder and flags RetryBackoffDecision .malformedHeader so the trace line can note it (E3).

Pure: no sleeping, no clock — the caller performs the wait (fake-clock tests assert the DECISION).

Implementation

RetryBackoffDecision resolveRetryBackoff({
  String? retryAfter,
  required int attempt,
  Duration? ladderStep,
}) {
  final step = ladderStep ?? retryBackoffLadderStep(attempt);
  final raw = retryAfter?.trim() ?? '';
  if (raw.isEmpty) {
    return RetryBackoffDecision(delay: step, source: RetryDelaySource.ladder);
  }
  final seconds = int.tryParse(raw);
  if (seconds == null || seconds < 0) {
    // E3: non-numeric / negative — and the HTTP-date form too (the stall
    // ladder honors delta-seconds only; codex's retry_delay is
    // delta-seconds as well).
    return RetryBackoffDecision(
      delay: step,
      source: RetryDelaySource.ladder,
      malformedHeader: true,
      rawHeader: raw,
    );
  }
  if (seconds == 0) {
    // E2: "0" is absence — the ladder applies.
    return RetryBackoffDecision(delay: step, source: RetryDelaySource.ladder);
  }
  final advertised = Duration(seconds: seconds);
  if (advertised > retryBackoffCeiling) {
    return RetryBackoffDecision(
      delay: retryBackoffCeiling,
      source: RetryDelaySource.clamp,
      advertised: advertised,
    );
  }
  return RetryBackoffDecision(
    delay: advertised,
    source: RetryDelaySource.server,
    advertised: advertised,
  );
}