From a2e199529fe0208cfcedede30f3f24d92095b1de Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:27:55 -0700 Subject: [PATCH 1/3] Test analytics limiter production contract --- web/tests/client-config-env.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/tests/client-config-env.test.ts b/web/tests/client-config-env.test.ts index eabd98c1727e..2682a8da7a88 100644 --- a/web/tests/client-config-env.test.ts +++ b/web/tests/client-config-env.test.ts @@ -45,6 +45,18 @@ describe("client config env validation", () => { expect(result.exitCode).toBe(0); }); + + test("requires the analytics limiter id in explicit Vercel production deployments", () => { + const result = importEnv({ + ...requiredEnv, + VERCEL: "1", + VERCEL_ENV: "production", + CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: "client-config-rule", + }); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain("CMUX_ANALYTICS_RATE_LIMIT_ID is required"); + }); }); function importEnv(env: Record): { exitCode: number; stderr: string } { From c3f2f2f68687d68506ad1ae17d2f86f1f0dad8e1 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:28:23 -0700 Subject: [PATCH 2/3] Require analytics limiter in production builds --- web/app/env.ts | 2 +- web/tests/client-config-env.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/app/env.ts b/web/app/env.ts index cd8a16a388e7..c64fcf6a09f3 100644 --- a/web/app/env.ts +++ b/web/app/env.ts @@ -46,7 +46,7 @@ export const env = createEnv({ CMUX_FEEDBACK_FROM_EMAIL: z.string().email(), CMUX_FEEDBACK_RATE_LIMIT_ID: z.string().min(1), CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: requireVercelNonPreviewValue("CMUX_CLIENT_CONFIG_RATE_LIMIT_ID"), - CMUX_ANALYTICS_RATE_LIMIT_ID: z.string().min(1).optional(), + CMUX_ANALYTICS_RATE_LIMIT_ID: requireVercelNonPreviewValue("CMUX_ANALYTICS_RATE_LIMIT_ID"), STACK_SECRET_SERVER_KEY: z.string().min(1), // APNs push (iOS notifications). Optional: the app boots without them; the // push route returns a clear "not configured" error until they are set. diff --git a/web/tests/client-config-env.test.ts b/web/tests/client-config-env.test.ts index 2682a8da7a88..70cb8cc76e48 100644 --- a/web/tests/client-config-env.test.ts +++ b/web/tests/client-config-env.test.ts @@ -35,12 +35,13 @@ describe("client config env validation", () => { expect(result.stderr).toContain("CMUX_CLIENT_CONFIG_RATE_LIMIT_ID is required"); }); - test("accepts explicit Vercel production deployments with the limiter id", () => { + test("accepts explicit Vercel production deployments with both limiter ids", () => { const result = importEnv({ ...requiredEnv, VERCEL: "1", VERCEL_ENV: "production", CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: "client-config-rule", + CMUX_ANALYTICS_RATE_LIMIT_ID: "analytics-rule", }); expect(result.exitCode).toBe(0); From 8eb9f64696cdd75be8908676cc412ad211b3ce36 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:14:50 -0700 Subject: [PATCH 3/3] Keep analytics limiter optional in Vercel development --- web/app/env.ts | 13 ++++++++++++- web/tests/client-config-env.test.ts | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/web/app/env.ts b/web/app/env.ts index c64fcf6a09f3..738db74c02d7 100644 --- a/web/app/env.ts +++ b/web/app/env.ts @@ -21,6 +21,8 @@ const isVercelNonPreviewDeployment = process.env.VERCEL === "1" && typeof process.env.VERCEL_ENV === "string" && process.env.VERCEL_ENV !== "preview"; +const isVercelProductionDeployment = + process.env.VERCEL === "1" && process.env.VERCEL_ENV === "production"; const requireVercelNonPreviewValue = (name: string): z.ZodType => z.string().min(1).optional().superRefine((value, context) => { if (isVercelNonPreviewDeployment && !value) { @@ -30,6 +32,15 @@ const requireVercelNonPreviewValue = (name: string): z.ZodType => + z.string().min(1).optional().superRefine((value, context) => { + if (isVercelProductionDeployment && !value) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: `${name} is required for Vercel production runtimes`, + }); + } + }); const stackEnv = ( value: string | undefined, @@ -46,7 +57,7 @@ export const env = createEnv({ CMUX_FEEDBACK_FROM_EMAIL: z.string().email(), CMUX_FEEDBACK_RATE_LIMIT_ID: z.string().min(1), CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: requireVercelNonPreviewValue("CMUX_CLIENT_CONFIG_RATE_LIMIT_ID"), - CMUX_ANALYTICS_RATE_LIMIT_ID: requireVercelNonPreviewValue("CMUX_ANALYTICS_RATE_LIMIT_ID"), + CMUX_ANALYTICS_RATE_LIMIT_ID: requireVercelProductionValue("CMUX_ANALYTICS_RATE_LIMIT_ID"), STACK_SECRET_SERVER_KEY: z.string().min(1), // APNs push (iOS notifications). Optional: the app boots without them; the // push route returns a clear "not configured" error until they are set. diff --git a/web/tests/client-config-env.test.ts b/web/tests/client-config-env.test.ts index 70cb8cc76e48..76857d861e4a 100644 --- a/web/tests/client-config-env.test.ts +++ b/web/tests/client-config-env.test.ts @@ -58,6 +58,17 @@ describe("client config env validation", () => { expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain("CMUX_ANALYTICS_RATE_LIMIT_ID is required"); }); + + test("allows Vercel development without the analytics limiter id", () => { + const result = importEnv({ + ...requiredEnv, + VERCEL: "1", + VERCEL_ENV: "development", + CMUX_CLIENT_CONFIG_RATE_LIMIT_ID: "client-config-rule", + }); + + expect(result.exitCode).toBe(0); + }); }); function importEnv(env: Record): { exitCode: number; stderr: string } {