feat(core-shared): add withRateLimit wrapper and conformance enforcement

- Add withRateLimit(rateLimit, fn) in rate-limit/with-rate-limit.ts,
  attaching the RateLimited brand at DI bind time
- Extend wireUseCase to accept optional rateLimit?: IRateLimit and
  compose withRateLimit innermost (before analytics/audit); propagate
  __rateLimited through analytics + audit inline wrappers
- Extend withSpan and withCapture PROPAGATED_BRANDS to include
  __rateLimited so the outermost binding carries the brand
- Extend assertFeatureConformance to require __rateLimited brand when
  manifest.useCases[name].rateLimit.length > 0; refactored into
  helper functions to stay within complexity thresholds
- Add rateLimit?: IRateLimit to BindContext; default to NoopRateLimit
  in web-next bindAllProduction and bindAllDevSeed aggregators
- Unit tests for withRateLimit brand attachment, factory passthrough,
  and composition; synthetic fixture tests for conformance errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 09:00:31 +00:00
parent 24b2490d86
commit cb61f51ee1
12 changed files with 477 additions and 61 deletions

View File

@@ -10,6 +10,7 @@ import { attachBrand } from "@/conformance/brand-runtime";
import type { ITracer, ISpan } from "@/instrumentation/tracer.interface";
import type { ILogger } from "@/instrumentation/logger.interface";
import type { BindContext } from "@/di/bind-context";
import type { RateLimitBudget } from "@/rate-limit/rate-limit.interface";
function makeTracer(): ITracer {
return {
@@ -396,4 +397,126 @@ describe("assertFeatureConformance", () => {
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).not.toThrow();
});
it("throws when use case declares rateLimit but binding is missing __rateLimited brand", () => {
const container = new Container();
const sym = Symbol("test.signIn");
const ctx = makeCtx();
const wrappedNoRateLimit = withSpan(
ctx.tracer,
{ name: "test.signIn", op: "use-case" },
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x,
),
);
container.bind(sym).toConstantValue(wrappedNoRateLimit);
const rateLimitBudget: RateLimitBudget = {
name: "global",
window: "1m",
budget: 60,
};
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
rateLimit: [rateLimitBudget],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).toThrow(ConformanceError);
expect(() =>
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).toThrow(/__rateLimited/);
});
it("passes when use case declares rateLimit and binding carries __rateLimited brand", () => {
const container = new Container();
const sym = Symbol("test.signIn");
const ctx = makeCtx();
const base = withSpan(
ctx.tracer,
{ name: "test.signIn", op: "use-case" },
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x,
),
);
attachBrand(base, "__rateLimited");
container.bind(sym).toConstantValue(base);
const rateLimitBudget: RateLimitBudget = {
name: "global",
window: "1m",
budget: 60,
};
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
rateLimit: [rateLimitBudget],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).not.toThrow();
});
it("passes when rateLimit is empty and __rateLimited brand is absent", () => {
const container = new Container();
const sym = Symbol("test.signIn");
const ctx = makeCtx();
const bound = withSpan(
ctx.tracer,
{ name: "test.signIn", op: "use-case" },
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x,
),
);
container.bind(sym).toConstantValue(bound);
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
rateLimit: [],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).not.toThrow();
});
});

View File

@@ -1,15 +1,66 @@
import type { Container } from "inversify";
import type { FeatureManifest } from "./define-feature";
import type { FeatureManifest, UseCaseManifest } from "./define-feature";
import {
isInstrumented,
isCaptured,
isAudited,
isAnalyzed,
isConsentChecked,
isRateLimited,
} from "./brand-runtime";
import { ConformanceError } from "./conformance-error";
import type { BindContext } from "../di/bind-context";
function requireBrand(ok: boolean, label: string, message: string): void {
if (!ok) throw new ConformanceError(`${label}: ${message}`);
}
function checkUseCaseBrands(
bound: unknown,
label: string,
useCase: UseCaseManifest,
requiresConsent: boolean,
): void {
requireBrand(
isInstrumented(bound),
label,
"missing __instrumented brand — was withSpan applied at bind time?",
);
requireBrand(
isCaptured(bound),
label,
"missing __captured brand — was withCapture applied at bind time?",
);
if (useCase.mutates && useCase.audits.length > 0) {
requireBrand(
isAudited(bound),
label,
"declares audits but binding is missing __audited brand — was withAudit applied at bind time?",
);
}
if ((useCase.analyticsEvents?.length ?? 0) > 0) {
requireBrand(
isAnalyzed(bound),
label,
"declares analyticsEvents but binding is missing __analyzed brand — was withAnalytics applied at bind time?",
);
}
if (requiresConsent) {
requireBrand(
isConsentChecked(bound),
label,
"feature declares requiresConsent but binding is missing __consentChecked brand — was withConsent applied at bind time?",
);
}
if ((useCase.rateLimit?.length ?? 0) > 0) {
requireBrand(
isRateLimited(bound),
label,
"declares rateLimit but binding is missing __rateLimited brand — was withRateLimit applied at bind time?",
);
}
}
/**
* Runtime check that every manifest-declared use case is bound through the
* brand-attaching wrappers (`withSpan` → `__instrumented`,
@@ -32,6 +83,7 @@ export function assertFeatureConformance(
_ctx: BindContext,
): void {
void _ctx; // future: also check ctx.bus / ctx.auditLog presence vs requiredCores
const requiresConsent = (manifest.requiresConsent?.length ?? 0) > 0;
for (const [name, useCase] of Object.entries(manifest.useCases)) {
const sym = symbols[name];
if (!sym) {
@@ -47,36 +99,11 @@ export function assertFeatureConformance(
`${manifest.name}.${name}: container could not resolve symbol (${String(cause)})`,
);
}
if (!isInstrumented(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: missing __instrumented brand — was withSpan applied at bind time?`,
);
}
if (!isCaptured(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: missing __captured brand — was withCapture applied at bind time?`,
);
}
if (useCase.mutates && useCase.audits.length > 0) {
if (!isAudited(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: declares audits but binding is missing __audited brand — was withAudit applied at bind time?`,
);
}
}
if ((useCase.analyticsEvents?.length ?? 0) > 0) {
if (!isAnalyzed(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: declares analyticsEvents but binding is missing __analyzed brand — was withAnalytics applied at bind time?`,
);
}
}
if ((manifest.requiresConsent?.length ?? 0) > 0) {
if (!isConsentChecked(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: feature declares requiresConsent but binding is missing __consentChecked brand — was withConsent applied at bind time?`,
);
}
}
checkUseCaseBrands(
bound,
`${manifest.name}.${name}`,
useCase,
requiresConsent,
);
}
}

View File

@@ -7,6 +7,7 @@ import {
isCaptured,
isAudited,
isAnalyzed,
isRateLimited,
} from "@/conformance/brand-runtime";
import type {
ITracer,
@@ -15,6 +16,7 @@ import type {
} from "@/instrumentation/tracer.interface";
import type { ILogger } from "@/instrumentation/logger.interface";
import type { AuditLogProtocol, AnalyticsProtocol } from "@/di/bind-protocols";
import { NoopRateLimit } from "@/rate-limit/noop-rate-limit";
function makeTracer() {
const calls: SpanOpts[] = [];
@@ -381,3 +383,130 @@ describe("wireUseCase — idempotent re-bind", () => {
expect(container.get(sym)).toBe(second);
});
});
describe("wireUseCase — rate limit path", () => {
it("attaches RateLimited brand when rateLimit is provided", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const rateLimit = new NoopRateLimit();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
rateLimit,
});
expect(isRateLimited(wired)).toBe(true);
expect(isInstrumented(wired)).toBe(true);
expect(isCaptured(wired)).toBe(true);
});
it("does not attach RateLimited brand when rateLimit is absent", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
});
expect(isRateLimited(wired)).toBe(false);
});
it("executes factory correctly when rateLimit is provided", async () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const rateLimit = new NoopRateLimit();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
rateLimit,
});
await expect(wired(5)).resolves.toBe(10);
});
it("attaches RateLimited + Analyzed brands when both rateLimit and analytics are provided", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const rateLimit = new NoopRateLimit();
const analytics = makeAnalytics();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
rateLimit,
analytics,
});
expect(isRateLimited(wired)).toBe(true);
expect(isAnalyzed(wired)).toBe(true);
expect(isInstrumented(wired)).toBe(true);
expect(isCaptured(wired)).toBe(true);
});
it("attaches RateLimited + Audited brands when both rateLimit and auditLog are provided", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const rateLimit = new NoopRateLimit();
const auditLog = makeAuditLog();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
rateLimit,
auditLog,
});
expect(isRateLimited(wired)).toBe(true);
expect(isAudited(wired)).toBe(true);
expect(isInstrumented(wired)).toBe(true);
expect(isCaptured(wired)).toBe(true);
});
});

View File

@@ -2,8 +2,10 @@ import type { Container } from "inversify";
import type { ITracer } from "../instrumentation/tracer.interface";
import type { ILogger } from "../instrumentation/logger.interface";
import type { AuditLogProtocol, AnalyticsProtocol } from "../di/bind-protocols";
import type { IRateLimit } from "../rate-limit/rate-limit.interface";
import { withSpan } from "../instrumentation/with-span";
import { withCapture } from "../instrumentation/with-capture";
import { withRateLimit } from "../rate-limit/with-rate-limit";
import { attachBrand } from "./brand-runtime";
export type WireUseCaseOptions<
@@ -22,6 +24,7 @@ export type WireUseCaseOptions<
logger: ILogger;
analytics?: AnalyticsProtocol;
auditLog?: AuditLogProtocol;
rateLimit?: IRateLimit;
};
/**
@@ -53,6 +56,7 @@ export function wireUseCase<
logger,
analytics,
auditLog,
rateLimit,
} = opts;
const spanName = `${feature}.${name}`;
@@ -60,17 +64,27 @@ export function wireUseCase<
const raw = factory(...deps);
let toWrap: (...args: FnArgs) => Promise<R>;
// analytics is innermost — wraps raw before audit. withAnalytics lives in
// rateLimit is innermost — wraps raw before analytics/audit. Attaches
// __rateLimited so withCapture + withSpan can propagate it to the outermost binding.
let toWrap: (...args: FnArgs) => Promise<R> =
rateLimit !== undefined ? withRateLimit(rateLimit, raw) : raw;
// analytics wraps rateLimit (or raw) before audit. withAnalytics lives in
// @repo/core-analytics which depends on core-shared (not vice versa), so we
// replicate the forwarding-wrapper semantics inline to avoid a circular dep.
if (analytics !== undefined) {
void analytics; // reserved for future automated event recording from manifest declarations
const analyzed: (...args: FnArgs) => Promise<R> = (...args) => raw(...args);
const prev = toWrap;
const analyzed: (...args: FnArgs) => Promise<R> = (...args) =>
prev(...args);
attachBrand(analyzed, "__analyzed");
// propagate __rateLimited from inner so withCapture/withSpan see it
if (
(prev as unknown as Record<string, unknown>)["__rateLimited"] === true
) {
attachBrand(analyzed, "__rateLimited");
}
toWrap = analyzed;
} else {
toWrap = raw;
}
if (auditLog !== undefined) {
void auditLog; // reserved for future automated audit recording from manifest declarations
@@ -78,11 +92,16 @@ export function wireUseCase<
const prev = toWrap;
const audited: (...args: FnArgs) => Promise<R> = (...args) => prev(...args);
attachBrand(audited, "__audited");
// propagate __analyzed from the analytics layer below so withCapture/withSpan
// can see it on the outermost binding
// propagate __analyzed and __rateLimited from inner so withCapture/withSpan
// can see them on the outermost binding
if ((prev as unknown as Record<string, unknown>)["__analyzed"] === true) {
attachBrand(audited, "__analyzed");
}
if (
(prev as unknown as Record<string, unknown>)["__rateLimited"] === true
) {
attachBrand(audited, "__rateLimited");
}
toWrap = audited;
}

View File

@@ -1,6 +1,7 @@
import type { SanitizedConfig } from "payload";
import type { ITracer, ILogger } from "../instrumentation";
import type { IJobQueue } from "../jobs";
import type { IRateLimit } from "../rate-limit/rate-limit.interface";
import type {
EventBusProtocol,
RealtimeBroadcasterProtocol,
@@ -45,6 +46,7 @@ export type BindContext<
auditLog?: Audit;
analytics?: Analytics;
consentFactory?: ConsentFactoryProtocol;
rateLimit?: IRateLimit;
};
/** Production binders also receive the resolved Payload config. */

View File

@@ -33,6 +33,7 @@ export function withCapture<Args extends unknown[], R>(
"__audited",
"__analyzed",
"__consentChecked",
"__rateLimited",
] as const;
const wrapped: (...args: Args) => Promise<R> = async (...args) => {

View File

@@ -7,6 +7,7 @@ const PROPAGATED_BRANDS = [
"__audited",
"__analyzed",
"__consentChecked",
"__rateLimited",
] as const;
export function withSpan<Args extends unknown[], R, Extra extends object>(

View File

@@ -5,3 +5,4 @@ export type {
} from "./rate-limit.interface";
export { NoopRateLimit } from "./noop-rate-limit";
export { InMemoryRateLimit } from "./in-memory-rate-limit";
export { withRateLimit } from "./with-rate-limit";

View File

@@ -0,0 +1,85 @@
import { describe, it, expect, expectTypeOf } from "vitest";
import { withRateLimit } from "@/rate-limit/with-rate-limit";
import {
isRateLimited,
isInstrumented,
isCaptured,
} from "@/conformance/brand-runtime";
import { attachBrand } from "@/conformance/brand-runtime";
import type { RateLimited } from "@/conformance/brands";
import { NoopRateLimit } from "@/rate-limit/noop-rate-limit";
function makeRateLimit() {
return new NoopRateLimit();
}
describe("withRateLimit — brand", () => {
it("returns a RateLimited<F> type", () => {
const rateLimit = makeRateLimit();
const fn = async (a: number) => a + 1;
const wrapped = withRateLimit(rateLimit, fn);
expectTypeOf(wrapped).toMatchTypeOf<RateLimited<typeof fn>>();
});
it("attaches __rateLimited as a non-enumerable property", () => {
const rateLimit = makeRateLimit();
const wrapped = withRateLimit(rateLimit, async (x: number) => x + 1);
expect(isRateLimited(wrapped)).toBe(true);
expect(Object.keys(wrapped)).not.toContain("__rateLimited");
});
});
describe("withRateLimit — factory passthrough", () => {
it("forwards arguments and return value unchanged on success", async () => {
const rateLimit = makeRateLimit();
const fn = async (a: number, b: number) => a + b;
const wrapped = withRateLimit(rateLimit, fn);
await expect(wrapped(3, 4)).resolves.toBe(7);
});
it("re-throws errors from the inner function", async () => {
const rateLimit = makeRateLimit();
const err = new Error("inner failure");
const fn = async () => {
throw err;
};
const wrapped = withRateLimit(rateLimit, fn);
await expect(wrapped()).rejects.toBe(err);
});
});
describe("withRateLimit — does not attach other brands", () => {
it("does not attach __instrumented or __captured", () => {
const rateLimit = makeRateLimit();
const wrapped = withRateLimit(rateLimit, async (x: number) => x);
expect(isInstrumented(wrapped)).toBe(false);
expect(isCaptured(wrapped)).toBe(false);
});
});
describe("withRateLimit — composition with other wrappers", () => {
it("composed result carries __rateLimited when withRateLimit is innermost", () => {
const rateLimit = makeRateLimit();
const raw = async (x: number) => x * 2;
const rateLimited = withRateLimit(rateLimit, raw);
// Simulate outer wrapper propagating __rateLimited
const outer: typeof raw = (...args) => rateLimited(...args);
if (
(rateLimited as unknown as Record<string, unknown>)["__rateLimited"] ===
true
) {
attachBrand(outer, "__rateLimited");
}
expect(isRateLimited(outer)).toBe(true);
});
it("original fn reference is not mutated", () => {
const rateLimit = makeRateLimit();
const fn = async (x: number) => x;
const wrapped = withRateLimit(rateLimit, fn);
expect(wrapped).not.toBe(fn);
expect(isRateLimited(fn)).toBe(false);
expect(isRateLimited(wrapped)).toBe(true);
});
});

View File

@@ -0,0 +1,25 @@
import type { IRateLimit } from "./rate-limit.interface";
import type { RateLimited } from "../conformance/brands";
import { attachBrand } from "../conformance/brand-runtime";
/**
* Use-case wrapper applied at DI bind time. Attaches the `__rateLimited`
* brand so the boot-time assertion can verify rate-limited use cases were
* bound through the rate-limit-aware path.
*
* The forward closure keeps the brand on a fresh function so the original
* `fn` reference is not mutated — important when the same factory output is
* used elsewhere unwrapped (dev-seed paths, tests).
*
* Composition order (outermost to innermost):
* withSpan → withCapture → withAudit → withAnalytics → withRateLimit → factory(deps)
*/
export function withRateLimit<Args extends unknown[], R>(
rateLimit: IRateLimit,
fn: (...args: Args) => Promise<R>,
): RateLimited<(...args: Args) => Promise<R>> {
void rateLimit; // future: enforce rate limit at runtime per manifest budgets
const wrapped: (...args: Args) => Promise<R> = (...args) => fn(...args);
attachBrand(wrapped, "__rateLimited");
return wrapped as RateLimited<(...args: Args) => Promise<R>>;
}