refactor(rate-limit): consolidate 3x-duplicated sliding-window limiter#1026
Merged
Conversation
sliamh11
force-pushed
the
dedup/rate-limiter-consolidation
branch
from
July 17, 2026 16:09
fa1fca3 to
f92c0eb
Compare
credential-proxy.ts, odysseus-server.ts, and ingress/gateway.ts each independently implemented the same Map<string, number[]> sliding-window rate limiter — the first two even commented "mirrors credential-proxy.ts" but were never consolidated. Extract the shared logic into src/rate-limiter.ts's createRateLimiter(), with an opt-in cleanupInterval (default false) so gateway.ts's no-interval behavior and credential-proxy.ts/odysseus-server.ts's periodic-prune behavior are both preserved exactly. All 3 call sites keep their existing thresholds, window semantics, and test-reset export names/signatures (_resetRateLimiterForTest, _resetServerStateForTest). The LIA-363 close-handler ordering (register only after a successful bind, so an EADDRINUSE retry's server.close() can't kill the timer early) is preserved verbatim in credential-proxy.ts. Slice 1 of an opportunistic dedup cleanup; not part of the tracked V2 migration roadmap. Design finalized after 8 rounds of adversarial plan review (native + GPT-5.6-Sol) before implementation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sliamh11
force-pushed
the
dedup/rate-limiter-consolidation
branch
from
July 17, 2026 16:15
f92c0eb to
09bca40
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/credential-proxy.ts,src/odysseus-server.ts, andsrc/ingress/gateway.tseach independently implemented the sameMap<string, number[]>sliding-window rate limiter — the first two even commented "mirrors credential-proxy.ts" but were never consolidated.src/rate-limiter.tsexportingcreateRateLimiter(max, windowMs, opts?)→{ isRateLimited, dispose, resetForTest }.opts.cleanupIntervalis opt-in (defaultfalse):gateway.tsomits it, preserving its current no-interval (inline-prune-only) behavior exactly;credential-proxy.tsandodysseus-server.tspass{ cleanupInterval: true }, preserving their periodicsetInterval().unref()prune behavior exactly._resetRateLimiterForTest,_resetServerStateForTest) — no changes required in any consuming test file.credential-proxy.ts(cleanup registered only after a successful bind, insidetryListen'sserver.listen()success callback — so an EADDRINUSE retry's intermediateserver.close()can't kill the timer before the real bind succeeds) is preserved verbatim; the handler now callslimiter.dispose()at the same registration point.createRateLimiter(...)instantiations incredential-proxy.tsandodysseus-server.tsare declared at module top-level (same scope as the code they replace), never insidetryListen/startOdysseusServer— avoiding a fresh, undisposed interval being recreated on every EADDRINUSE retry (same bug class LIA-363 already fixed).src/rate-limiter.test.ts(8 new cases): sliding-window correctness (within/outside window), per-key isolation,nowdefaulting,cleanupInterval: truecreates+dispose()-clears an interval,cleanupInterval: falsecreates no interval, the cleanup interval actually prunes expired entries,resetForTest()clears state,dispose()idempotency.This is Slice 1 of an opportunistic dedup/reusability cleanup — not part of the tracked V2 migration roadmap. Design finalized after 8 rounds of adversarial plan review (native + GPT-5.6-Sol co-review) before this implementation, plus 3 further plan-reviewer rounds in-session to pin down the module-scope timer placement and test-export-contract details.
Behavior-preservation verification
npx vitest run src/rate-limiter.test.ts src/credential-proxy.test.ts src/odysseus-server.test.ts src/memory-bridge.test.ts src/ingress/gateway.test.ts→ 97/97 passed, 0 regressions.npx tsc --noEmit→ clean.npx eslinton all changed files → clean.grep -rn "rateBuckets\|RateBucket\|makeRateLimiter\|rateLimitCleanupInterval" src/→ zero hits — no orphaned references to the old per-file implementations.credential-proxy.ts'sconst limiter = createRateLimiter(...)sits at module scope, and itsserver.on('close', ...)handler is still registered insidetryListen's listen-success callback (LIA-363 ordering intact).odysseus-server.tshas the same module-scope placement and close-handler timing.gateway.ts'screateRateLimiter(config.rateLimitMax, config.rateLimitWindowMs)call passes nocleanupIntervaloption, matching its pre-refactor no-interval behavior, and its call site still passes an explicit capturednowper request.Review process
Test plan
npx vitest run src/rate-limiter.test.ts src/credential-proxy.test.ts src/odysseus-server.test.ts src/memory-bridge.test.ts src/ingress/gateway.test.ts— 97/97 passingnpx tsc --noEmit— cleannpx eslinton changed files — cleanNot merging this PR — opening for review only, per the task's instructions (opportunistic cleanup outside the tracked roadmap).
🤖 Generated with Claude Code