Files
agentic-dev/docs/work/epics/security-headers-rate-limit-sbom/02-rate-limit-implementations/_story.md

3.3 KiB

id, epic, title, type, status, feature, depends-on, blocks, created, updated
id epic title type status feature depends-on blocks created updated
02-rate-limit-implementations security-headers-rate-limit-sbom Rate-limit implementations — Noop, InMemory, Recording technical-story done core-shared
01-rate-limit-type-primitives
04-with-rate-limit-wrapper-and-conformance
05-auth-signin-rate-limit-backfill
2026-05-20T00:00:00Z 2026-05-20T08:38:25.271Z

Goal

Provide three working IRateLimit implementations that cover dev, test, and production-delegate scenarios: NoopRateLimit (always-allow, zero overhead), InMemoryRateLimit (per-process fixed-window with check-at-read expiry), and RecordingRateLimit (test helper in core-testing that captures all calls for assertions).

Why

The withRateLimit wrapper (Story 04) and the auth.signIn backfill (Story 05) both need concrete classes to wire. NoopRateLimit is also the required default in BindContext so apps without a wired rate-limit implementation boot safely. RecordingRateLimit is the testing primitive every future rate-limited use-case test will use; landing it here alongside the production impls keeps the testing toolkit coherent.

Done when

  • packages/core-shared/src/rate-limit/noop-rate-limit.ts exports NoopRateLimit with unit test: consume always resolves { allowed: true, remaining: Infinity, resetAt: epoch }, reset is a no-op.
  • packages/core-shared/src/rate-limit/in-memory-rate-limit.ts exports InMemoryRateLimit using check-at-read fixed-window expiry (no setTimeout); unit test: per-bucket tracking, decrement on consume, reset at window boundary (synthetic clock), explicit reset call restores budget to declared value.
  • packages/core-testing/src/rate-limit/recording-rate-limit.ts exports RecordingRateLimit capturing consume + reset invocation arguments; sibling test verifies capture correctness.
  • pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff all pass after each task.

In scope

  • NoopRateLimit — always-allow impl, no state, no timers.
  • InMemoryRateLimitMap-backed, per-bucket { count, resetAt }, check-at-read expiry, clock injection via constructor arg for testability.
  • RecordingRateLimit — in packages/core-testing; exposes consumeCalls + resetCalls accessors for test assertions.
  • Barrel exports from core-shared and core-testing.

Out of scope

  • Redis-backed or any distributed impl — deferred, consumer wires via ADR-022 library evaluation.
  • Token-bucket algorithm — InMemoryRateLimit uses fixed-window for simplicity; documented as dev-only.
  • withRateLimit wrapper — Story 04.

Tasks

  • Implement NoopRateLimit in packages/core-shared/src/rate-limit/noop-rate-limit.ts with sibling unit test (consume always allowed, reset no-op); implement InMemoryRateLimit in packages/core-shared/src/rate-limit/in-memory-rate-limit.ts with sibling unit test (per-bucket tracking, check-at-read expiry via injected clock, explicit reset restores budget); export both from core-shared barrel; all gates pass.
  • Implement RecordingRateLimit in packages/core-testing/src/rate-limit/recording-rate-limit.ts capturing consume + reset call arguments verbatim with consumeCalls + resetCalls accessors; sibling test verifies capture correctness; export from core-testing barrel; all gates pass.