feat(federation): add evaluateAuthFailure decision function
Pure function deciding whether the next 401/403 from an active peer triggers backoff or a transition to needs_attention. Threshold = 5, corresponding to ~21.5 min of the existing BACKOFF_SCHEDULE_MS.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { evaluateAuthFailure, AUTH_FAILURE_THRESHOLD } from './federationAuthFailure.js';
|
||||
|
||||
describe('evaluateAuthFailure', () => {
|
||||
it('returns backoff with incremented count for a first failure', () => {
|
||||
const result = evaluateAuthFailure(0);
|
||||
expect(result).toEqual({ kind: 'backoff', newAuthFailures: 1 });
|
||||
});
|
||||
|
||||
it('returns backoff below the threshold', () => {
|
||||
for (let prev = 0; prev < AUTH_FAILURE_THRESHOLD - 1; prev++) {
|
||||
const result = evaluateAuthFailure(prev);
|
||||
expect(result.kind).toBe('backoff');
|
||||
expect(result.newAuthFailures).toBe(prev + 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns transition at the threshold', () => {
|
||||
const result = evaluateAuthFailure(AUTH_FAILURE_THRESHOLD - 1);
|
||||
expect(result).toEqual({
|
||||
kind: 'transition_to_needs_attention',
|
||||
newAuthFailures: AUTH_FAILURE_THRESHOLD,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns transition beyond the threshold', () => {
|
||||
const result = evaluateAuthFailure(AUTH_FAILURE_THRESHOLD + 5);
|
||||
expect(result.kind).toBe('transition_to_needs_attention');
|
||||
expect(result.newAuthFailures).toBe(AUTH_FAILURE_THRESHOLD + 6);
|
||||
});
|
||||
|
||||
it('AUTH_FAILURE_THRESHOLD is 5', () => {
|
||||
expect(AUTH_FAILURE_THRESHOLD).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Number of consecutive 401/403 responses from an active peer before the
|
||||
* outbox worker transitions that peer to `needs_attention`.
|
||||
*
|
||||
* Rationale (see design spec §Retry Budget): with the existing
|
||||
* BACKOFF_SCHEDULE_MS = [30s, 1m, 5m, 15m, 1h], five consecutive auth
|
||||
* failures span ~21.5 min — covering the 15-min rotation grace window
|
||||
* with ~6.5 min margin while still giving clear signal that a persistent
|
||||
* desync has occurred.
|
||||
*/
|
||||
export const AUTH_FAILURE_THRESHOLD = 5;
|
||||
|
||||
export type AuthFailureAction =
|
||||
| { kind: 'backoff'; newAuthFailures: number }
|
||||
| { kind: 'transition_to_needs_attention'; newAuthFailures: number };
|
||||
|
||||
/**
|
||||
* Pure decision function. Given the current consecutive-auth-failure count,
|
||||
* return whether the next failure keeps the peer in retry-with-backoff or
|
||||
* transitions it to the `needs_attention` terminal state.
|
||||
*/
|
||||
export function evaluateAuthFailure(currentAuthFailures: number): AuthFailureAction {
|
||||
const newAuthFailures = currentAuthFailures + 1;
|
||||
if (newAuthFailures >= AUTH_FAILURE_THRESHOLD) {
|
||||
return { kind: 'transition_to_needs_attention', newAuthFailures };
|
||||
}
|
||||
return { kind: 'backoff', newAuthFailures };
|
||||
}
|
||||
Reference in New Issue
Block a user