Make the Kotlin retry cap a ceiling on the operation budget and expose it in the builder

Merged
JE
2mo ago
+121-4

The gap

FizzyHttpClient.requestWithRetry sized its budget as opRetry?.maxRetries ?: config.maxRetries. Every operation in behavior-model.json carries a retry block (112 of 112), so the ?: fallback never ran and config.maxRetries was dead for governed traffic: a client built with maxRetries = 1 still made three attempts on GetBoard. The builder DSL did not expose the knob either — FizzyConfig has it, FizzyClientBuilder dropped it — so the only way to set it was the constructor the DSL hides.

Found while adding configOverrides.maxRetries to the conformance suite (the follow-up to #180 and #181): the Kotlin runner had no way to pass a cap, and the SDK would have ignored it if it could.

The fix

The cap is now what the Go, Ruby and Rust SDKs make of it: a total attempt count, floored at one so 0 means "no retries" rather than "no request", and a ceiling on the operation's modelled budget rather than a value the model replaces — min(cap.coerceAtLeast(1), opMax), the same shape as basecamp-sdk's computeMaxAttempts. The builder passes maxRetries through. enableRetry is untouched.

The default stays 3, which is also every governed operation's modelled maximum, so a client that never set the cap behaves exactly as before.

Tests

  • computeMaxAttempts floors at one and ceilings the operation budget.

  • maxRetries = 0 on GetBoard against an always-503 engine sends exactly one request and surfaces the 503.

  • maxRetries = 2 on GetBoard (budget 3) sends exactly two.

  • Follow-up pages keep the operation budget: requestPaginated and requestPaginatedAsFlow now pass info.operation on every page a Link header leads to, so a cap raised above the model (5) yields 1 + 3 requests when page two keeps answering 503, not 1 + 5.

make kt-check green locally.

Conformance coverage

The shared case — configOverrides.maxRetries, the retry.json fixture and the override plumbing in all five runners — is #183, stacked directly on this branch. Merged together, the behavioral change does not ship on unit tests alone (AGENTS.md rule 3); Kotlin conformance on #183's head reports 128 passed, 0 failed.