Auth: retry the token exchange once in place before restarting
What does this MR do and why?
GitLab returns one identical invalid_grant for a spent code and for a grant that a lagging database replica has not seen yet, and a lost connection leaves the code unspent. In both cases a second attempt with the same code succeeds where !1309 (closed)'s restart costs a browser round trip. exchangeCode now retries once after 250 ms for transport errors other than timeouts, upstream 5xx and invalid_grant. A spent code fails the retry within milliseconds and the existing restart takes over.
retryInPlacenames the policy per failure class.gitlab_pages_auth_token_fetch_failures_totalgains theretriedoutcome;gitlab_pages_auth_token_fetch_recoveries_total{via}records successes after a failure.- Only terminal failures log at error level.
Acceptance harness against the stub's contract mode, with two scenarios: a grant not yet visible and a dropped connection, each recovering with exactly two exchanges and nothing shown to the user. The earlier restart-recovery acceptance test is superseded, since the in-place retry recovers its scenario before any restart.
Part of a stack replacing !1333 (closed) for #1190 (closed), on top of !1309 (closed):
- stub contract mode (test-only)
- move token-fetch handling into its own file
- retry the exchange once in place
- bounded restart with a recovery cookie
- failure page at the requested page
Each MR targets the previous one's branch and retargets automatically when that merges.
How to validate
go test ./internal/auth/ and make acceptance ARGS='-run TestSignIn'.