| ... | @@ -160,16 +160,30 @@ export function validateServerUpdateReadyEvent( | ... | @@ -160,16 +160,30 @@ export function validateServerUpdateReadyEvent( |
| 160 | * each nudge is the pacer: a connection that fails instantly re-enters backoff | 160 | * each nudge is the pacer: a connection that fails instantly re-enters backoff |
| 161 | * immediately and would otherwise spin a tight retry loop. | 161 | * immediately and would otherwise spin a tight retry loop. |
| 162 | * | 162 | * |
| | 163 | * A newly restarted server can also reject the first environment credential. |
| | 164 | * Authentication blocks need the same paced retry during this known restart; |
| | 165 | * permission and configuration failures remain blocked. |
| | 166 | * |
| 163 | * Callers fork this as a child of the update command so it is interrupted as | 167 | * Callers fork this as a child of the update command so it is interrupted as |
| 164 | * soon as the update settles, whether it succeeds, fails, or times out. | 168 | * soon as the update settles, whether it succeeds, fails, or times out. |
| 165 | */ | 169 | */ |
| 166 | export function nudgeReconnectDuringUpdateRestart(input: { | 170 | export function nudgeReconnectDuringUpdateRestart(input: { |
| 167 | readonly stateChanges: Stream.Stream<{ readonly phase: string }, unknown>; | 171 | readonly stateChanges: Stream.Stream< |
| | 172 | { |
| | 173 | readonly phase: string; |
| | 174 | readonly lastFailure?: { readonly reason: string } | null; |
| | 175 | }, |
| | 176 | unknown |
| | 177 | >; |
| 168 | readonly retryNow: Effect.Effect<void>; | 178 | readonly retryNow: Effect.Effect<void>; |
| 169 | readonly interval?: Duration.Duration; | 179 | readonly interval?: Duration.Duration; |
| 170 | }): Effect.Effect<void> { | 180 | }): Effect.Effect<void> { |
| 171 | return input.stateChanges.pipe( | 181 | return input.stateChanges.pipe( |
| 172 | Stream.filter((state) => state.phase === "backoff"), | 182 | Stream.filter( |
| | 183 | (state) => |
| | 184 | state.phase === "backoff" || |
| | 185 | (state.phase === "blocked" && state.lastFailure?.reason === "authentication"), |
| | 186 | ), |
| 173 | Stream.runForEach(() => | 187 | Stream.runForEach(() => |
| 174 | Effect.sleep(input.interval ?? Duration.seconds(1)).pipe(Effect.andThen(input.retryNow)), | 188 | Effect.sleep(input.interval ?? Duration.seconds(1)).pipe(Effect.andThen(input.retryNow)), |
| 175 | ), | 189 | ), |