chore(work): decompose security-headers-rate-limit-sbom epic
Generated 1 epic + 11 stories under docs/work/epics/ from the approved PRD docs/work/prds/security-headers-rate-limit-sbom.prd.md. Stories cover rate-limit type primitives + implementations + ESLint rule + withRateLimit wrapper, auth.signIn rate-limit backfill, security headers core module + framework adapters, app wiring across web-next / web-tanstack / cms, SBOM CI workflow, and documentation. Ready for `pnpm work dispatch --execute`.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: 02-rate-limit-implementations
|
||||
epic: security-headers-rate-limit-sbom
|
||||
title: Rate-limit implementations — Noop, InMemory, Recording
|
||||
type: technical-story
|
||||
status: todo
|
||||
feature: core-shared
|
||||
depends-on: [01-rate-limit-type-primitives]
|
||||
blocks:
|
||||
[
|
||||
04-with-rate-limit-wrapper-and-conformance,
|
||||
05-auth-signin-rate-limit-backfill,
|
||||
]
|
||||
created: 2026-05-20T00:00:00Z
|
||||
updated: 2026-05-20T08:14:55.907Z
|
||||
---
|
||||
|
||||
## 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.
|
||||
- `InMemoryRateLimit` — `Map`-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.
|
||||
Reference in New Issue
Block a user