---
id: 04-with-rate-limit-wrapper-and-conformance
epic: security-headers-rate-limit-sbom
title: "`withRateLimit` wrapper and conformance extensions"
type: technical-story
status: done
feature: core-shared
depends-on: [01-rate-limit-type-primitives, 02-rate-limit-implementations]
blocks: [05-auth-signin-rate-limit-backfill]
created: 2026-05-20T00:00:00Z
updated: 2026-05-20T09:05:31.838Z
---

## Goal

Ship the `withRateLimit` wrapper (attaches `RateLimited` brand at DI bind time), extend `wireUseCase` to compose it innermost when `rateLimit.length > 0`, extend `assertFeatureConformance` to require the brand when a manifest declares rate-limit budgets, and add `rateLimit?: IRateLimit` to `BindContext` defaulting to `NoopRateLimit` — completing the full conformance enforcement loop for rate-limit.

## Why

The brand enforcement loop must be complete before any feature can declare `rateLimit` in its manifest and claim conformance. A partial landing (e.g. wrapper without `assertFeatureConformance`, or `BindContext` without a default) leaves the conformance layer in an undefined state: either boot assertions silently skip the brand check, or apps fail to boot because `ctx.rateLimit` is undefined. All four changes land together as a single coherent slice.

## Done when

- `packages/core-shared/src/rate-limit/with-rate-limit.ts` exports `withRateLimit(rateLimit, factory)` attaching the `RateLimited` brand; unit tests assert brand is attached, factory passthrough is preserved, and wrapper composes correctly with other wrappers in the canonical order.
- `wireUseCase` in `packages/core-shared/src/conformance/wire-use-case.ts` accepts optional `rateLimit?: IRateLimit` and composes `withRateLimit` innermost (after `withConsent`) when `manifest.rateLimit.length > 0`; existing `wireUseCase` tests remain green.
- `assertFeatureConformance` in `packages/core-shared/src/conformance/assert-bindings.ts` requires the `RateLimited` brand when `manifest.useCases[name].rateLimit.length > 0`; synthetic fixture test asserts `ConformanceError` is thrown when the brand is absent.
- `packages/core-shared/src/di/bind-context.ts` gains `rateLimit?: IRateLimit`; the app aggregator in `apps/web-next/src/server/bind-production.ts` (and equivalents) defaults to `new NoopRateLimit()` when the consumer does not wire a backend.
- Canonical wrapper composition order confirmed: `withSpan → withCapture → withAudit → withAnalytics → withConsent → withRateLimit → factory(deps)`.
- `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` all pass.

## In scope

- `withRateLimit(rateLimit, factory)` wrapper in `core-shared/rate-limit/with-rate-limit.ts`.
- `wireUseCase` extension accepting `rateLimit?: IRateLimit` and composing `withRateLimit` innermost.
- `assertFeatureConformance` extension: require `RateLimited` brand when `rateLimit.length > 0`.
- Synthetic fixture test for the boot assertion failure case.
- `BindContext.rateLimit?: IRateLimit` — app aggregators default to `new NoopRateLimit()`.
- Mirror prior art: `with-capture.ts` / `with-consent.ts` wrapper pattern; `wire-use-case.ts` + `assert-bindings.ts` extension pattern.

## Out of scope

- `NoopRateLimit` implementation — Story 02 (required as dependency).
- Auth backfill — Story 05.
- ESLint rule — Story 03.

## Tasks

- [x] Implement `withRateLimit(rateLimit, factory)` in `packages/core-shared/src/rate-limit/with-rate-limit.ts` attaching the `RateLimited` brand; extend `wireUseCase` to accept optional `rateLimit?: IRateLimit` and compose `withRateLimit` innermost when `manifest.rateLimit.length > 0`; extend `assertFeatureConformance` to require `RateLimited` brand when `rateLimit.length > 0` with a synthetic fixture test asserting `ConformanceError` on absent brand; add `rateLimit?: IRateLimit` to `BindContext` and default it to `new NoopRateLimit()` in app aggregators; unit tests for wrapper brand attachment and factory passthrough; all gates pass.
