From ff8cf38903c5e62567e3be96ee7c20f06e6a9400 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:41:12 -0700 Subject: [PATCH 01/11] Send sign-up to v2's app host --- apps/cloud/wrangler.jsonc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 9f7b22f258..95aa2eca4f 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -100,6 +100,8 @@ // it with the URL unchanged, so v2 sees host `executor.sh`. { "binding": "V2", + // v2's Worker name. It must equal the name v2's deployment pins for + // this binding; this is the only place v1 names it. "service": "executor-next-hosted-api-v2-qs32brgjwvt7ytx4", }, ], @@ -140,8 +142,8 @@ }, "vars": { "VITE_PUBLIC_SITE_URL": "https://executor.sh", - // Where /sign-up and /signup redirect: v2's sign-up page. - "V2_SIGN_UP_URL": "https://v2.executor.sh/login?mode=signup", + // Where /sign-up and /signup redirect: v2's sign-up page on its app host. + "V2_SIGN_UP_URL": "https://app.executor.sh/login?mode=signup", // v2 starts every connected-account OAuth `state` with this prefix, and // /api/oauth/callback requests carrying it go to v2. It must equal the // prefix v2 mints. From fb2b836681197abdbe3c26498bbb0b91776c9928 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 11:59:12 -0700 Subject: [PATCH 02/11] Test the shipped sign-up URL on app.executor.sh --- apps/cloud/src/edge/production-config.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cloud/src/edge/production-config.test.ts b/apps/cloud/src/edge/production-config.test.ts index 40010143f7..5ec0621d0d 100644 --- a/apps/cloud/src/edge/production-config.test.ts +++ b/apps/cloud/src/edge/production-config.test.ts @@ -38,11 +38,11 @@ describe("production v2 edge settings", () => { expect(bindings[0]?.service).toMatch(/^[a-z0-9-]+$/); }); - it("redirects sign-up to v2's sign-up page on v2.executor.sh", async () => { + it("redirects sign-up to v2's sign-up page on app.executor.sh", async () => { const response = await v2EdgeResponse(new Request("https://executor.sh/sign-up"), shipped); expect(response?.status).toBe(302); - expect(response?.headers.get("location")).toBe("https://v2.executor.sh/login?mode=signup"); + expect(response?.headers.get("location")).toBe("https://app.executor.sh/login?mode=signup"); }); it("ships v2's connected-account state prefix", () => { From eea510b1bbd1825b5d51eccc0b1d1f2ad12acac2 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:48:07 -0700 Subject: [PATCH 03/11] Serve executor.sh marketing from v2 and keep v1's terms --- apps/cloud/src/edge/marketing.ts | 181 +++++++++++++++++------- apps/cloud/src/env-augment.d.ts | 3 + apps/cloud/src/server.ts | 7 +- apps/cloud/wrangler.jsonc | 12 +- apps/marketing/astro.config.mjs | 4 + apps/marketing/src/layouts/Layout.astro | 7 +- apps/marketing/src/middleware.ts | 7 +- 7 files changed, 156 insertions(+), 65 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 31942b1cae..2bbcfe4d29 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -2,14 +2,16 @@ // The `executor.sh` edge — decides which Worker answers a public request. // // On the production domain (`executor.sh`): -// - marketing paths and the unauthenticated landing page go to the separate -// `executor-marketing` worker; +// - v2's marketing site answers the landing page without a v1 session, its +// pages, assets and docs, and its browser telemetry proxies; // - sign-up goes to v2 (a redirect to v2's sign-up page); // - a fixed list of v2 paths (sign-in issuer metadata, social sign-in // callbacks, Git smart HTTP and the agent skills index) is forwarded to // v2's Worker over a service binding; // - so is a connected-account OAuth callback whose `state` carries v2's -// prefix. +// prefix; +// - v1's terms of service (and their assets) stay on the +// `executor-marketing` worker. // v1 owns everything not listed. This module deliberately has no TanStack // Start or cloud application imports: the Worker entry calls it before // loading the Start server graph. @@ -17,49 +19,33 @@ import { parseCookie } from "../auth/cookies"; -const MARKETING_PATHS = [ - "/home", - "/setup", - "/privacy", - "/terms", - "/pricing", - "/about-executor", - "/google-oauth", - "/google-workspace", - "/blog", - "/llms.txt", - "/index.md", - "/setup-prompt.md", - "/pricing.md", - "/api/detect", - "/_astro", - "/authors", - "/og-image.png", - "/pattern-graph-paper.svg", -]; +const PRODUCTION_HOST = "executor.sh"; +/** v1's session cookie. `/` with it is v1's dashboard; without it, marketing. */ const SESSION_COOKIE = "wos-session"; -const PRODUCTION_HOST = "executor.sh"; +// --------------------------------------------------------------------------- +// v1's terms on the `executor-marketing` worker +// --------------------------------------------------------------------------- + +/** v1's terms of service stay on v1's marketing worker: v2's terms describe + * only v2's billing, and v1's cover the plans v1 customers are on. The + * worker builds its assets under `/_v1-marketing`, apart from v2's + * `/_astro`. Each path matches itself and everything below it. */ +const V1_MARKETING_PATHS: ReadonlyArray = ["/terms", "/_v1-marketing"]; -/** Whether an exact pathname belongs to the public marketing worker. */ +/** Whether a pathname belongs to v1's marketing worker on `executor.sh`. */ export const isMarketingPath = (pathname: string): boolean => - MARKETING_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`)); + V1_MARKETING_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`)); /** - * Project a production request onto the marketing service-binding request. - * Returns `null` when the cloud application owns the request instead. + * Project a production request for v1's terms (or their assets) onto the + * `executor-marketing` service-binding request. Returns `null` for every + * other request. */ export const marketingProxyRequest = (request: Request): Request | null => { const url = new URL(request.url); - if (url.hostname !== PRODUCTION_HOST) return null; - - const shouldProxy = - isMarketingPath(url.pathname) || - (url.pathname === "/" && !parseCookie(request.headers.get("cookie"), SESSION_COOKIE)); - if (!shouldProxy) return null; - - if (url.pathname === "/home") url.pathname = "/"; + if (url.hostname !== PRODUCTION_HOST || !isMarketingPath(url.pathname)) return null; return new Request(url, request); }; @@ -103,6 +89,51 @@ export const isV2Path = (pathname: string): boolean => V2_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)) || isSocialCallbackPath(pathname); +/** v2's marketing site on `executor.sh`: each path and everything below it. + * These are the pages, files and assets v2's marketing build publishes, and + * its docs. Paths that are also valid v1 organization slugs and not + * reserved (v2's `/apps`, `/demo` and `/experiments`) stay with v1. The + * favicons stay with v1 too: v1's dashboard serves identical files. */ +const V2_MARKETING_PATHS: ReadonlyArray = [ + "/home", + "/about-executor", + "/blog", + "/pricing", + "/privacy", + "/google-oauth", + "/google-workspace", + "/index.md", + "/llms.txt", + "/pricing.md", + "/setup-prompt.md", + "/_astro", + "/authors", + "/og-image.png", + "/pattern-graph-paper.svg", + "/docs", +]; + +/** Whether a pathname belongs to v2's marketing site on `executor.sh`. `/` + * also does, without a v1 session; see {@link isV2Homepage}. */ +export const isV2MarketingPath = (pathname: string): boolean => + V2_MARKETING_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`)); + +/** The landing page without a v1 session is v2's marketing homepage. */ +const isV2Homepage = (url: URL, request: Request): boolean => + url.pathname === "/" && parseCookie(request.headers.get("cookie"), SESSION_COOKIE) === null; + +/** The one cookie v2's marketing reads: the anonymous visitor ID that keeps + * a visitor's homepage experiment assignment stable. v2 sets it on + * `executor.sh` itself. Every other cookie, v1's session included, stays + * with v1. */ +const V2_MARKETING_COOKIES: ReadonlyArray = ["executor_visitor"]; + +/** v2's browser telemetry proxies (product analytics and error reporting) + * live at `/api/<16 hex>` roots, which v1's API never uses. The configured + * roots (`V2_TELEMETRY_PATHS`) say which ones are v2's. */ +const TELEMETRY_ROOT_PATTERN = /^\/api\/[a-f0-9]{16}$/; +const TELEMETRY_PATH_SHAPE = /^\/api\/[a-f0-9]{16}(?:\/|$)/; + /** Request headers v1 never passes to v2. The cookie header carries v1's * `wos-session` (v2's cookies are host-only on its own hosts, so nothing of * v2's travels on `executor.sh`). Client-sent forwarding headers are dropped @@ -114,12 +145,22 @@ const V2_STRIPPED_HEADERS = ["cookie", "x-forwarded-host", "x-forwarded-proto"] * * Keeps the URL (so v2 sees host `executor.sh`, path and query unchanged), * method, body stream and every header except {@link V2_STRIPPED_HEADERS}, - * including `Authorization`. Redirects are returned to the client, not + * including `Authorization`. The cookie header is rebuilt from `keptCookies` + * only, and dropped when none of them is present. Redirects are returned to the client, not * followed: v2 answers callbacks with a redirect to its own host. */ -export const v2ForwardRequest = (request: Request): Request => { +export const v2ForwardRequest = ( + request: Request, + keptCookies: ReadonlyArray = [], +): Request => { + const cookieHeader = request.headers.get("cookie"); + const cookies = keptCookies.flatMap((name) => { + const value = parseCookie(cookieHeader, name); + return value === null ? [] : [`${name}=${value}`]; + }); const headers = new Headers(request.headers); for (const name of V2_STRIPPED_HEADERS) headers.delete(name); + if (cookies.length > 0) headers.set("cookie", cookies.join("; ")); return new Request(request, { headers, redirect: "manual" }); }; @@ -145,6 +186,8 @@ export interface V2EdgeEnv { readonly V2_SIGN_UP_URL?: string; /** The prefix v2 puts on every connected-account OAuth `state`. */ readonly V2_OAUTH_STATE_PREFIX?: string; + /** Comma-separated `/api/<16 hex>` roots of v2's browser telemetry proxies. */ + readonly V2_TELEMETRY_PATHS?: string; } /** What the edge needs to hand requests to v2. */ @@ -155,24 +198,37 @@ export interface V2Edge { readonly signUpUrl: URL; /** v2's connected-account OAuth state prefix (the `V2_OAUTH_STATE_PREFIX` var). */ readonly oauthStatePrefix: string; + /** Roots of v2's browser telemetry proxies (the `V2_TELEMETRY_PATHS` var). */ + readonly telemetryPaths: ReadonlyArray; } /** * Parse the edge's v2 settings from the Worker environment. Returns `null` * when none is set (local dev, test workers), so v1 serves everything, and * the reason as a string when the deployment is broken: only some of them - * set, a sign-up URL that is not absolute, or a state prefix that could - * match a v1 state. + * set, a sign-up URL that is not absolute, a state prefix that could match a + * v1 state, or a telemetry root outside `/api/<16 hex>`. */ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { const service = env.V2; const signUpUrl = env.V2_SIGN_UP_URL; const oauthStatePrefix = env.V2_OAUTH_STATE_PREFIX; - if (service === undefined && signUpUrl === undefined && oauthStatePrefix === undefined) { + const telemetryPaths = env.V2_TELEMETRY_PATHS; + if ( + service === undefined && + signUpUrl === undefined && + oauthStatePrefix === undefined && + telemetryPaths === undefined + ) { return null; } - if (service === undefined || signUpUrl === undefined || oauthStatePrefix === undefined) { - return "The V2 binding, V2_SIGN_UP_URL and V2_OAUTH_STATE_PREFIX must be set together"; + if ( + service === undefined || + signUpUrl === undefined || + oauthStatePrefix === undefined || + telemetryPaths === undefined + ) { + return "The V2 binding, V2_SIGN_UP_URL, V2_OAUTH_STATE_PREFIX and V2_TELEMETRY_PATHS must be set together"; } const parsed = URL.parse(signUpUrl); if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { @@ -181,7 +237,11 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { if (!OAUTH_STATE_PREFIX_PATTERN.test(oauthStatePrefix)) { return "V2_OAUTH_STATE_PREFIX must be URL-safe and contain '.' or '~'"; } - return { service, signUpUrl: parsed, oauthStatePrefix }; + const roots = telemetryPaths.split(",").map((path) => path.trim()); + if (!roots.every((root) => TELEMETRY_ROOT_PATTERN.test(root))) { + return "V2_TELEMETRY_PATHS must list /api/<16 hex> paths separated by commas"; + } + return { service, signUpUrl: parsed, oauthStatePrefix, telemetryPaths: roots }; }; /** Whether a connected-account callback carries v2's state prefix. Reads the @@ -189,32 +249,45 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { const isV2OAuthCallback = (url: URL, prefix: string): boolean => url.searchParams.get("state")?.startsWith(prefix) === true; +/** Whether a pathname is under one of v2's telemetry proxy roots. */ +const isV2TelemetryPath = (pathname: string, roots: ReadonlyArray): boolean => + roots.some((root) => pathname === root || pathname.startsWith(`${root}/`)); + /** - * Answer a production request that belongs to v2: redirect sign-up (`GET`, - * any query) to v2's sign-up page, or forward a {@link isV2Path} request, or - * a connected-account callback whose `state` starts with v2's prefix, to - * v2's Worker and return its response unchanged (status, headers and body - * stream). Returns `null` when v1 owns the request. + * Answer a production request that belongs to v2 and return `null` when v1 + * owns it: + * - sign-up (`GET`, any query) redirects to v2's sign-up page; + * - a {@link isV2Path} request, a connected-account callback whose `state` + * starts with v2's prefix, or a request under one of v2's telemetry roots + * is forwarded to v2's Worker without cookies; + * - a {@link isV2MarketingPath} request, or `/` without a v1 session, is + * forwarded with only v2's marketing visitor cookie. + * v2's response comes back unchanged (status, headers and body stream). * - * Broken settings answer the requests the edge owns with a 500 and leave the - * rest of v1 serving. The callback is the exception: which callbacks are v2's - * depends on the settings, so v1 keeps every callback until they parse. + * Broken settings answer the requests the edge always owns with a 500 and + * leave the rest of v1 serving. Which callbacks and `/api/<16 hex>` requests + * are v2's depends on the settings, so v1 keeps those until they parse. */ export const v2EdgeResponse = (request: Request, env: V2EdgeEnv): Promise | null => { const url = new URL(request.url); if (url.hostname !== PRODUCTION_HOST) return null; const signUp = isSignUpPath(url.pathname) && request.method === "GET"; + const marketing = isV2MarketingPath(url.pathname) || isV2Homepage(url, request); + const owned = signUp || marketing || isV2Path(url.pathname); const callback = url.pathname === OAUTH_CALLBACK_PATH; - if (!signUp && !callback && !isV2Path(url.pathname)) return null; + const telemetry = TELEMETRY_PATH_SHAPE.test(url.pathname); + if (!owned && !callback && !telemetry) return null; const edge = parseV2Edge(env); if (edge === null) return null; if (typeof edge === "string") { + if (!owned) return null; console.error(`executor.sh v2 edge misconfigured: ${edge}`); - if (callback) return null; return Promise.resolve(new Response("Service misconfigured", { status: 500 })); } if (signUp) return Promise.resolve(Response.redirect(edge.signUpUrl.href, 302)); + if (marketing) return edge.service.fetch(v2ForwardRequest(request, V2_MARKETING_COOKIES)); if (callback && !isV2OAuthCallback(url, edge.oauthStatePrefix)) return null; + if (telemetry && !isV2TelemetryPath(url.pathname, edge.telemetryPaths)) return null; return edge.service.fetch(v2ForwardRequest(request)); }; diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index e733d119fe..b0d684206a 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -146,6 +146,9 @@ declare global { /** Prefix of v2's connected-account OAuth `state`; `/api/oauth/callback` * requests whose state starts with it go to v2. */ V2_OAUTH_STATE_PREFIX?: string; + /** Comma-separated `/api/<16 hex>` roots of v2's browser telemetry + * proxies, which v2's marketing pages call on executor.sh. */ + V2_TELEMETRY_PATHS?: string; // Shared with frontend VITE_PUBLIC_SITE_URL?: string; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 1a1a713c2d..6faff79d3b 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -318,8 +318,9 @@ const cloudflareHandler = { prewarmAppPlane(ctx); } - // Sign-up, the fixed list of v2 paths and v2's connected-account - // callbacks on `executor.sh` go to v2. + // On `executor.sh`, v2 answers marketing (including `/docs` and its + // telemetry proxies), sign-up, the fixed list of v2 paths and v2's + // connected-account callbacks; v1's terms stay on v1's marketing worker. const v2 = v2EdgeResponse(request, env); if (v2) return v2; @@ -333,6 +334,8 @@ const cloudflareHandler = { // first — measured at p50 3.1s on a cold isolate, against p50 33ms for the // request's own work, on a Worker where 1,666 dispatches spread across // 1,608 isolates (so nearly every request is cold). Forward before Start. + // On `executor.sh` v2 answers `/docs` above, so this serves it on other + // hosts; v1's own PostHog proxy is served here on every host. const passthroughPath = new URL(request.url).pathname; const passthrough = passthroughResponse(request, passthroughPath); if (passthrough) return passthrough; diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 95aa2eca4f..12119057fe 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -91,13 +91,17 @@ }, ], "services": [ + // v1's marketing worker. On executor.sh it now serves only v1's terms of + // service and their assets (src/edge/marketing.ts); v2 serves the rest + // of marketing. { "binding": "MARKETING", "service": "executor-marketing", }, // v2's API Worker (the executor-next `v2` stage, same account). The edge - // (src/edge/marketing.ts) forwards a fixed list of executor.sh paths to - // it with the URL unchanged, so v2 sees host `executor.sh`. + // (src/edge/marketing.ts) forwards marketing and a fixed list of + // executor.sh paths to it with the URL unchanged, so v2 sees host + // `executor.sh`. { "binding": "V2", // v2's Worker name. It must equal the name v2's deployment pins for @@ -148,6 +152,10 @@ // /api/oauth/callback requests carrying it go to v2. It must equal the // prefix v2 mints. "V2_OAUTH_STATE_PREFIX": "x2.", + // Roots of v2's browser telemetry proxies (product analytics, then error + // reporting) that v2's marketing pages call on executor.sh. They must + // equal the proxy paths v2's build uses. + "V2_TELEMETRY_PATHS": "/api/00e2e1f082a6ef17,/api/fd6fab1fbb4883e1", // Keeps the /__sentry-otel-verify probe live in production: Sentry // delivery failed silently for weeks (zero events after ~Jul 30 with an // active DSN), and without this there is no way to test the pipeline diff --git a/apps/marketing/astro.config.mjs b/apps/marketing/astro.config.mjs index 877af5b812..4537b81615 100644 --- a/apps/marketing/astro.config.mjs +++ b/apps/marketing/astro.config.mjs @@ -30,6 +30,10 @@ export default defineConfig({ site: "https://executor.sh", output: "server", integrations: [react()], + // On executor.sh this worker now serves only v1's terms of service; v2's + // marketing site owns `/_astro`. Build assets under their own root, which + // the cloud edge (apps/cloud/src/edge/marketing.ts) forwards here. + build: { assets: "_v1-marketing" }, vite: { plugins: [tailwindcss()], define: wranglerPublicDefine(), diff --git a/apps/marketing/src/layouts/Layout.astro b/apps/marketing/src/layouts/Layout.astro index d791ecda8c..35b4f689a1 100644 --- a/apps/marketing/src/layouts/Layout.astro +++ b/apps/marketing/src/layouts/Layout.astro @@ -62,11 +62,12 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? Astro.url).toString( // `history_change` is for SPAs like apps/cloud. const phKey = import.meta.env.PUBLIC_POSTHOG_KEY; if (phKey) { - // api_host rides the `/_astro` prefix that the cloud edge forwards to - // this worker on executor.sh; src/middleware.ts proxies it to PostHog. + // api_host rides the `/_v1-marketing` asset root that the cloud edge + // forwards to this worker on executor.sh; src/middleware.ts proxies it + // to PostHog. const posthogReady = import("posthog-js").then(({ default: posthog }) => { posthog.init(phKey, { - api_host: `${window.location.origin}/_astro/_ph`, + api_host: `${window.location.origin}/_v1-marketing/_ph`, ui_host: import.meta.env.PUBLIC_POSTHOG_HOST ?? "https://us.posthog.com", autocapture: false, capture_pageview: true, diff --git a/apps/marketing/src/middleware.ts b/apps/marketing/src/middleware.ts index dbbd339e7f..b676eba7af 100644 --- a/apps/marketing/src/middleware.ts +++ b/apps/marketing/src/middleware.ts @@ -7,12 +7,11 @@ import type { MiddlewareHandler } from "astro"; // // The path MUST sit under a prefix that the cloud worker's edge forwards to // this worker. On the production apex (executor.sh) the cloud worker owns the -// custom domain and only proxies an allow-list to executor-marketing; `/api/*` -// stays in cloud (it has its own posthog proxy on a randomized path), so a -// top-level `/api/...` path here 404s. `/_astro` IS forwarded, so we ride it. +// custom domain and only proxies `/terms` and the `/_v1-marketing` asset root +// to executor-marketing, so we ride the asset root. const POSTHOG_INGEST_HOST = "us.i.posthog.com"; const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com"; -const POSTHOG_PROXY_PATH = "/_astro/_ph"; +const POSTHOG_PROXY_PATH = "/_v1-marketing/_ph"; export const onRequest: MiddlewareHandler = async (context, next) => { const { pathname } = new URL(context.request.url); From 30e163628988f10bf7d9293ea13f9e2dbd63f96a Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:54:18 -0700 Subject: [PATCH 04/11] Describe the visitor cookie the edge passes to v2 --- apps/cloud/src/edge/marketing.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 2bbcfe4d29..8db8b7d2ff 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -134,10 +134,11 @@ const V2_MARKETING_COOKIES: ReadonlyArray = ["executor_visitor"]; const TELEMETRY_ROOT_PATTERN = /^\/api\/[a-f0-9]{16}$/; const TELEMETRY_PATH_SHAPE = /^\/api\/[a-f0-9]{16}(?:\/|$)/; -/** Request headers v1 never passes to v2. The cookie header carries v1's - * `wos-session` (v2's cookies are host-only on its own hosts, so nothing of - * v2's travels on `executor.sh`). Client-sent forwarding headers are dropped - * so v2 sees no host claim other than the request URL's. */ +/** Request headers v1 never passes to v2 as sent. The cookie header carries + * v1's `wos-session`; v2's own cookies are host-only on its own hosts, + * except marketing's visitor cookie, which is passed on by name. Client-sent + * forwarding headers are dropped so v2 sees no host claim other than the + * request URL's. */ const V2_STRIPPED_HEADERS = ["cookie", "x-forwarded-host", "x-forwarded-proto"] as const; /** @@ -146,8 +147,9 @@ const V2_STRIPPED_HEADERS = ["cookie", "x-forwarded-host", "x-forwarded-proto"] * Keeps the URL (so v2 sees host `executor.sh`, path and query unchanged), * method, body stream and every header except {@link V2_STRIPPED_HEADERS}, * including `Authorization`. The cookie header is rebuilt from `keptCookies` - * only, and dropped when none of them is present. Redirects are returned to the client, not - * followed: v2 answers callbacks with a redirect to its own host. + * only, and dropped when none of them is present. Redirects are returned to + * the client, not followed: v2 answers callbacks with a redirect to its own + * host. */ export const v2ForwardRequest = ( request: Request, From 2ba1b77a8873c2938bcdd3e5f1ea05f02477106b Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:35:42 -0700 Subject: [PATCH 05/11] Keep v1's privacy and Google OAuth pages, match v2's contract and pin its telemetry paths --- apps/cloud/src/edge/marketing.ts | 128 +++++++++++++++++++------------ apps/cloud/src/env-augment.d.ts | 9 ++- apps/cloud/src/server.ts | 3 +- apps/cloud/wrangler.jsonc | 21 +++-- apps/marketing/astro.config.mjs | 2 +- apps/marketing/src/middleware.ts | 4 +- 6 files changed, 107 insertions(+), 60 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index 8db8b7d2ff..e7a464de44 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -10,8 +10,8 @@ // v2's Worker over a service binding; // - so is a connected-account OAuth callback whose `state` carries v2's // prefix; -// - v1's terms of service (and their assets) stay on the -// `executor-marketing` worker. +// - v1's terms of service, privacy policy and Google OAuth disclosure (and +// their assets) stay on the `executor-marketing` worker. // v1 owns everything not listed. This module deliberately has no TanStack // Start or cloud application imports: the Worker entry calls it before // loading the Start server graph. @@ -25,21 +25,28 @@ const PRODUCTION_HOST = "executor.sh"; const SESSION_COOKIE = "wos-session"; // --------------------------------------------------------------------------- -// v1's terms on the `executor-marketing` worker +// v1's legal pages on the `executor-marketing` worker // --------------------------------------------------------------------------- -/** v1's terms of service stay on v1's marketing worker: v2's terms describe - * only v2's billing, and v1's cover the plans v1 customers are on. The - * worker builds its assets under `/_v1-marketing`, apart from v2's - * `/_astro`. Each path matches itself and everything below it. */ -const V1_MARKETING_PATHS: ReadonlyArray = ["/terms", "/_v1-marketing"]; +/** v1's legal pages stay on v1's marketing worker. v2's terms describe only + * v2's billing, and v1's cover the plans v1 customers are on; v1's privacy + * policy and Google OAuth disclosure describe v1's own data handling and + * Google OAuth app. The worker builds its assets under `/_v1-marketing`, + * apart from v2's `/_astro`. Each path matches itself and everything below + * it. */ +const V1_MARKETING_PATHS: ReadonlyArray = [ + "/terms", + "/privacy", + "/google-oauth", + "/_v1-marketing", +]; /** Whether a pathname belongs to v1's marketing worker on `executor.sh`. */ export const isMarketingPath = (pathname: string): boolean => V1_MARKETING_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`)); /** - * Project a production request for v1's terms (or their assets) onto the + * Project a production request for v1's legal pages (or their assets) onto the * `executor-marketing` service-binding request. Returns `null` for every * other request. */ @@ -89,34 +96,40 @@ export const isV2Path = (pathname: string): boolean => V2_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)) || isSocialCallbackPath(pathname); -/** v2's marketing site on `executor.sh`: each path and everything below it. - * These are the pages, files and assets v2's marketing build publishes, and - * its docs. Paths that are also valid v1 organization slugs and not - * reserved (v2's `/apps`, `/demo` and `/experiments`) stay with v1. The +/** v2's marketing site on `executor.sh`, as path patterns (see + * {@link matchesPathPattern}): the pages, files and assets v2's marketing + * build publishes, and its docs. `/pricing` and `/home` match only + * themselves. `apps` and `experiments` are reserved org slugs, so `/apps`, + * `/apps/*` and `/experiments/*` are v2's; v2 publishes nothing at a bare + * `/experiments`, which stays with v1. `/demo` is a valid, unreserved v1 + * organization slug, so it and everything below it stay with v1. The * favicons stay with v1 too: v1's dashboard serves identical files. */ const V2_MARKETING_PATHS: ReadonlyArray = [ "/home", - "/about-executor", - "/blog", "/pricing", - "/privacy", - "/google-oauth", - "/google-workspace", - "/index.md", - "/llms.txt", "/pricing.md", + "/index.md", "/setup-prompt.md", - "/_astro", - "/authors", + "/llms.txt", + "/about-executor", + "/google-workspace", + "/blog", + "/blog/*", + "/docs", + "/docs/*", + "/apps", + "/apps/*", + "/experiments/*", + "/_astro/*", + "/authors/*", "/og-image.png", "/pattern-graph-paper.svg", - "/docs", ]; /** Whether a pathname belongs to v2's marketing site on `executor.sh`. `/` * also does, without a v1 session; see {@link isV2Homepage}. */ export const isV2MarketingPath = (pathname: string): boolean => - V2_MARKETING_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`)); + V2_MARKETING_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)); /** The landing page without a v1 session is v2's marketing homepage. */ const isV2Homepage = (url: URL, request: Request): boolean => @@ -128,10 +141,19 @@ const isV2Homepage = (url: URL, request: Request): boolean => * with v1. */ const V2_MARKETING_COOKIES: ReadonlyArray = ["executor_visitor"]; -/** v2's browser telemetry proxies (product analytics and error reporting) - * live at `/api/<16 hex>` roots, which v1's API never uses. The configured - * roots (`V2_TELEMETRY_PATHS`) say which ones are v2's. */ -const TELEMETRY_ROOT_PATTERN = /^\/api\/[a-f0-9]{16}$/; +/** v2's browser telemetry lives under `/api/<16 lowercase hex>`, which v1's + * API never uses. Two settings say which paths are v2's. Both are outputs + * of v2's production infrastructure (stage `v2`; random, retained values), + * which v2 pins in its edge contract; v2 refuses to deploy stage `v2` if + * its outputs differ: + * - `V2_ANALYTICS_PROXY_PATH`, the PostHog browser proxy root (output + * `proxyPath` of v2's `executor-next-posthog` stack), forwarded with + * everything below it; + * - `V2_ERROR_TUNNEL_PATH`, the Sentry browser error tunnel (output + * `browserTunnel` of v2's `executor-next-sentry` stack), forwarded + * exactly. */ +const ANALYTICS_PROXY_PATH_PATTERN = /^\/api\/[a-f0-9]{16}$/; +const ERROR_TUNNEL_PATH_PATTERN = /^\/api\/[a-f0-9]{16}\/submit$/; const TELEMETRY_PATH_SHAPE = /^\/api\/[a-f0-9]{16}(?:\/|$)/; /** Request headers v1 never passes to v2 as sent. The cookie header carries @@ -188,8 +210,10 @@ export interface V2EdgeEnv { readonly V2_SIGN_UP_URL?: string; /** The prefix v2 puts on every connected-account OAuth `state`. */ readonly V2_OAUTH_STATE_PREFIX?: string; - /** Comma-separated `/api/<16 hex>` roots of v2's browser telemetry proxies. */ - readonly V2_TELEMETRY_PATHS?: string; + /** Root of v2's PostHog browser proxy, `/api/<16 hex>`. */ + readonly V2_ANALYTICS_PROXY_PATH?: string; + /** v2's Sentry browser error tunnel, `/api/<16 hex>/submit`. */ + readonly V2_ERROR_TUNNEL_PATH?: string; } /** What the edge needs to hand requests to v2. */ @@ -200,8 +224,10 @@ export interface V2Edge { readonly signUpUrl: URL; /** v2's connected-account OAuth state prefix (the `V2_OAUTH_STATE_PREFIX` var). */ readonly oauthStatePrefix: string; - /** Roots of v2's browser telemetry proxies (the `V2_TELEMETRY_PATHS` var). */ - readonly telemetryPaths: ReadonlyArray; + /** Root of v2's PostHog browser proxy (the `V2_ANALYTICS_PROXY_PATH` var). */ + readonly analyticsProxyPath: string; + /** v2's Sentry browser error tunnel (the `V2_ERROR_TUNNEL_PATH` var). */ + readonly errorTunnelPath: string; } /** @@ -209,18 +235,20 @@ export interface V2Edge { * when none is set (local dev, test workers), so v1 serves everything, and * the reason as a string when the deployment is broken: only some of them * set, a sign-up URL that is not absolute, a state prefix that could match a - * v1 state, or a telemetry root outside `/api/<16 hex>`. + * v1 state, or a telemetry path of the wrong shape. */ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { const service = env.V2; const signUpUrl = env.V2_SIGN_UP_URL; const oauthStatePrefix = env.V2_OAUTH_STATE_PREFIX; - const telemetryPaths = env.V2_TELEMETRY_PATHS; + const analyticsProxyPath = env.V2_ANALYTICS_PROXY_PATH; + const errorTunnelPath = env.V2_ERROR_TUNNEL_PATH; if ( service === undefined && signUpUrl === undefined && oauthStatePrefix === undefined && - telemetryPaths === undefined + analyticsProxyPath === undefined && + errorTunnelPath === undefined ) { return null; } @@ -228,9 +256,10 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { service === undefined || signUpUrl === undefined || oauthStatePrefix === undefined || - telemetryPaths === undefined + analyticsProxyPath === undefined || + errorTunnelPath === undefined ) { - return "The V2 binding, V2_SIGN_UP_URL, V2_OAUTH_STATE_PREFIX and V2_TELEMETRY_PATHS must be set together"; + return "The V2 binding, V2_SIGN_UP_URL, V2_OAUTH_STATE_PREFIX, V2_ANALYTICS_PROXY_PATH and V2_ERROR_TUNNEL_PATH must be set together"; } const parsed = URL.parse(signUpUrl); if (parsed === null || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) { @@ -239,11 +268,13 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { if (!OAUTH_STATE_PREFIX_PATTERN.test(oauthStatePrefix)) { return "V2_OAUTH_STATE_PREFIX must be URL-safe and contain '.' or '~'"; } - const roots = telemetryPaths.split(",").map((path) => path.trim()); - if (!roots.every((root) => TELEMETRY_ROOT_PATTERN.test(root))) { - return "V2_TELEMETRY_PATHS must list /api/<16 hex> paths separated by commas"; + if (!ANALYTICS_PROXY_PATH_PATTERN.test(analyticsProxyPath)) { + return "V2_ANALYTICS_PROXY_PATH must be /api/<16 lowercase hex>"; + } + if (!ERROR_TUNNEL_PATH_PATTERN.test(errorTunnelPath)) { + return "V2_ERROR_TUNNEL_PATH must be /api/<16 lowercase hex>/submit"; } - return { service, signUpUrl: parsed, oauthStatePrefix, telemetryPaths: roots }; + return { service, signUpUrl: parsed, oauthStatePrefix, analyticsProxyPath, errorTunnelPath }; }; /** Whether a connected-account callback carries v2's state prefix. Reads the @@ -251,17 +282,20 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { const isV2OAuthCallback = (url: URL, prefix: string): boolean => url.searchParams.get("state")?.startsWith(prefix) === true; -/** Whether a pathname is under one of v2's telemetry proxy roots. */ -const isV2TelemetryPath = (pathname: string, roots: ReadonlyArray): boolean => - roots.some((root) => pathname === root || pathname.startsWith(`${root}/`)); +/** Whether a pathname is v2's analytics proxy root or below it, or exactly + * v2's error tunnel. */ +const isV2TelemetryPath = (pathname: string, edge: V2Edge): boolean => + matchesPathPattern(pathname, edge.analyticsProxyPath) || + matchesPathPattern(pathname, `${edge.analyticsProxyPath}/*`) || + pathname === edge.errorTunnelPath; /** * Answer a production request that belongs to v2 and return `null` when v1 * owns it: * - sign-up (`GET`, any query) redirects to v2's sign-up page; * - a {@link isV2Path} request, a connected-account callback whose `state` - * starts with v2's prefix, or a request under one of v2's telemetry roots - * is forwarded to v2's Worker without cookies; + * starts with v2's prefix, or a request for v2's analytics proxy or error + * tunnel is forwarded to v2's Worker without cookies; * - a {@link isV2MarketingPath} request, or `/` without a v1 session, is * forwarded with only v2's marketing visitor cookie. * v2's response comes back unchanged (status, headers and body stream). @@ -290,6 +324,6 @@ export const v2EdgeResponse = (request: Request, env: V2EdgeEnv): Promise` roots of v2's browser telemetry - * proxies, which v2's marketing pages call on executor.sh. */ - V2_TELEMETRY_PATHS?: string; + /** Root of v2's PostHog browser proxy (`/api/<16 hex>`), which v2's + * marketing pages call on executor.sh; forwarded with its subpaths. */ + V2_ANALYTICS_PROXY_PATH?: string; + /** v2's Sentry browser error tunnel (`/api/<16 hex>/submit`), forwarded + * exactly. */ + V2_ERROR_TUNNEL_PATH?: string; // Shared with frontend VITE_PUBLIC_SITE_URL?: string; diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 6faff79d3b..6e3cad473a 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -320,7 +320,8 @@ const cloudflareHandler = { // On `executor.sh`, v2 answers marketing (including `/docs` and its // telemetry proxies), sign-up, the fixed list of v2 paths and v2's - // connected-account callbacks; v1's terms stay on v1's marketing worker. + // connected-account callbacks; v1's legal pages stay on v1's marketing + // worker. const v2 = v2EdgeResponse(request, env); if (v2) return v2; diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index 12119057fe..baf10baffa 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -92,8 +92,8 @@ ], "services": [ // v1's marketing worker. On executor.sh it now serves only v1's terms of - // service and their assets (src/edge/marketing.ts); v2 serves the rest - // of marketing. + // service, privacy policy and Google OAuth disclosure, and their assets + // (src/edge/marketing.ts); v2 serves the rest of marketing. { "binding": "MARKETING", "service": "executor-marketing", @@ -152,10 +152,19 @@ // /api/oauth/callback requests carrying it go to v2. It must equal the // prefix v2 mints. "V2_OAUTH_STATE_PREFIX": "x2.", - // Roots of v2's browser telemetry proxies (product analytics, then error - // reporting) that v2's marketing pages call on executor.sh. They must - // equal the proxy paths v2's build uses. - "V2_TELEMETRY_PATHS": "/api/00e2e1f082a6ef17,/api/fd6fab1fbb4883e1", + // v2's browser telemetry on executor.sh. Both values are outputs of v2's + // production infrastructure (stage `v2`; random and retained, so they do + // not change between deploys). v2 pins them in its edge contract and + // refuses to deploy stage `v2` if its outputs differ; change them here + // only together with that contract. + // PostHog browser proxy root: output `proxyPath` of v2's + // `executor-next-posthog` stack (apps/hosted/cloud/alchemy.posthog.ts). + // Forwarded with everything below it. + "V2_ANALYTICS_PROXY_PATH": "/api/00e2e1f082a6ef17", + // Sentry browser error tunnel: output `browserTunnel` of v2's + // `executor-next-sentry` stack (apps/hosted/cloud/alchemy.sentry.ts). + // Forwarded exactly, not its root or other subpaths. + "V2_ERROR_TUNNEL_PATH": "/api/fd6fab1fbb4883e1/submit", // Keeps the /__sentry-otel-verify probe live in production: Sentry // delivery failed silently for weeks (zero events after ~Jul 30 with an // active DSN), and without this there is no way to test the pipeline diff --git a/apps/marketing/astro.config.mjs b/apps/marketing/astro.config.mjs index 4537b81615..9d3f13f420 100644 --- a/apps/marketing/astro.config.mjs +++ b/apps/marketing/astro.config.mjs @@ -30,7 +30,7 @@ export default defineConfig({ site: "https://executor.sh", output: "server", integrations: [react()], - // On executor.sh this worker now serves only v1's terms of service; v2's + // On executor.sh this worker now serves only v1's legal pages; v2's // marketing site owns `/_astro`. Build assets under their own root, which // the cloud edge (apps/cloud/src/edge/marketing.ts) forwards here. build: { assets: "_v1-marketing" }, diff --git a/apps/marketing/src/middleware.ts b/apps/marketing/src/middleware.ts index b676eba7af..a673f0b165 100644 --- a/apps/marketing/src/middleware.ts +++ b/apps/marketing/src/middleware.ts @@ -7,8 +7,8 @@ import type { MiddlewareHandler } from "astro"; // // The path MUST sit under a prefix that the cloud worker's edge forwards to // this worker. On the production apex (executor.sh) the cloud worker owns the -// custom domain and only proxies `/terms` and the `/_v1-marketing` asset root -// to executor-marketing, so we ride the asset root. +// custom domain and only proxies v1's legal pages and the `/_v1-marketing` +// asset root to executor-marketing, so we ride the asset root. const POSTHOG_INGEST_HOST = "us.i.posthog.com"; const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com"; const POSTHOG_PROXY_PATH = "/_v1-marketing/_ph"; From 3fb5bfda7589ed00ab90e8a14b7232100d6061b6 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:50:34 -0700 Subject: [PATCH 06/11] Forward only below v2's analytics proxy root --- apps/cloud/src/edge/marketing.ts | 12 +++++------- apps/cloud/wrangler.jsonc | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index e7a464de44..dbb4d27a29 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -147,8 +147,8 @@ const V2_MARKETING_COOKIES: ReadonlyArray = ["executor_visitor"]; * which v2 pins in its edge contract; v2 refuses to deploy stage `v2` if * its outputs differ: * - `V2_ANALYTICS_PROXY_PATH`, the PostHog browser proxy root (output - * `proxyPath` of v2's `executor-next-posthog` stack), forwarded with - * everything below it; + * `proxyPath` of v2's `executor-next-posthog` stack), forwarded below + * it (`/*`), not the root itself, which v2 does not serve; * - `V2_ERROR_TUNNEL_PATH`, the Sentry browser error tunnel (output * `browserTunnel` of v2's `executor-next-sentry` stack), forwarded * exactly. */ @@ -282,12 +282,10 @@ export const parseV2Edge = (env: V2EdgeEnv): V2Edge | string | null => { const isV2OAuthCallback = (url: URL, prefix: string): boolean => url.searchParams.get("state")?.startsWith(prefix) === true; -/** Whether a pathname is v2's analytics proxy root or below it, or exactly - * v2's error tunnel. */ +/** Whether a pathname is below v2's analytics proxy root, or exactly v2's + * error tunnel. */ const isV2TelemetryPath = (pathname: string, edge: V2Edge): boolean => - matchesPathPattern(pathname, edge.analyticsProxyPath) || - matchesPathPattern(pathname, `${edge.analyticsProxyPath}/*`) || - pathname === edge.errorTunnelPath; + matchesPathPattern(pathname, `${edge.analyticsProxyPath}/*`) || pathname === edge.errorTunnelPath; /** * Answer a production request that belongs to v2 and return `null` when v1 diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc index baf10baffa..3fc0f540a1 100644 --- a/apps/cloud/wrangler.jsonc +++ b/apps/cloud/wrangler.jsonc @@ -159,7 +159,7 @@ // only together with that contract. // PostHog browser proxy root: output `proxyPath` of v2's // `executor-next-posthog` stack (apps/hosted/cloud/alchemy.posthog.ts). - // Forwarded with everything below it. + // Forwarded below it (`/*`), not the root itself. "V2_ANALYTICS_PROXY_PATH": "/api/00e2e1f082a6ef17", // Sentry browser error tunnel: output `browserTunnel` of v2's // `executor-next-sentry` stack (apps/hosted/cloud/alchemy.sentry.ts). From 82bdd8cd9a4a904a59d2e0fa71045561f3af8eb2 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:23:25 -0700 Subject: [PATCH 07/11] Redirect slashed v2 pages on executor.sh to the page --- apps/cloud/src/edge/marketing.ts | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/edge/marketing.ts b/apps/cloud/src/edge/marketing.ts index dbb4d27a29..9f19e58128 100644 --- a/apps/cloud/src/edge/marketing.ts +++ b/apps/cloud/src/edge/marketing.ts @@ -131,6 +131,27 @@ const V2_MARKETING_PATHS: ReadonlyArray = [ export const isV2MarketingPath = (pathname: string): boolean => V2_MARKETING_PATHS.some((pattern) => matchesPathPattern(pathname, pattern)); +/** v2's pages that match only themselves (`/pricing`, `/home`, + * `/about-executor`, `/google-workspace`): exact patterns that are not + * files and have no `/x/*` beside them. Their slashed form (`/pricing/`) + * matches no pattern, so v1's sign-in gate would answer it. */ +const V2_EXACT_PAGES: ReadonlySet = new Set( + V2_MARKETING_PATHS.filter( + (pattern) => + !pattern.endsWith("/*") && + !pattern.includes(".") && + !V2_MARKETING_PATHS.includes(`${pattern}/*`), + ), +); + +/** The page a slashed v2 page (`/pricing/`) canonicalizes to (`/pricing`), + * or `null`. Deeper paths (`/pricing/extra`) stay with v1. */ +export const v2PageForSlashedPath = (pathname: string): string | null => { + if (!pathname.endsWith("/")) return null; + const page = pathname.slice(0, -1); + return V2_EXACT_PAGES.has(page) ? page : null; +}; + /** The landing page without a v1 session is v2's marketing homepage. */ const isV2Homepage = (url: URL, request: Request): boolean => url.pathname === "/" && parseCookie(request.headers.get("cookie"), SESSION_COOKIE) === null; @@ -291,6 +312,8 @@ const isV2TelemetryPath = (pathname: string, edge: V2Edge): boolean => * Answer a production request that belongs to v2 and return `null` when v1 * owns it: * - sign-up (`GET`, any query) redirects to v2's sign-up page; + * - a `GET` or `HEAD` for a slashed v2 page (`/pricing/`) redirects (308) + * to the page, query kept, as v2's site does on its own hosts; * - a {@link isV2Path} request, a connected-account callback whose `state` * starts with v2's prefix, or a request for v2's analytics proxy or error * tunnel is forwarded to v2's Worker without cookies; @@ -307,8 +330,12 @@ export const v2EdgeResponse = (request: Request, env: V2EdgeEnv): Promise Date: Thu, 8 Oct 2026 01:49:38 -0700 Subject: [PATCH 08/11] Test v2 marketing, telemetry proxies and v1's terms on executor.sh --- apps/cloud/src/edge/marketing.test.ts | 400 ++++++++++++++---- apps/cloud/src/edge/production-config.test.ts | 11 + 2 files changed, 337 insertions(+), 74 deletions(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index 66645162dd..9c77fbfd9b 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from "@effect/vitest"; import { isMarketingPath, isSignUpPath, + isV2MarketingPath, isV2Path, marketingProxyRequest, parseV2Edge, @@ -11,88 +12,38 @@ import { type V2Service, } from "./marketing"; -// On executor.sh the marketing middleware proxies an allow-list of paths to the -// `executor-marketing` worker; everything else falls through to the auth-gated -// cloud app (the sign-in page). `/blog` and `/llms.txt` are public content, so -// they must be on the allow-list: without it an unauthenticated visit redirects -// to `/login?returnTo=...` and the reader bounces. +// On executor.sh only v1's terms of service (and their asset root) still go to +// v1's `executor-marketing` worker; v2 serves the rest of marketing. describe("isMarketingPath", () => { - const marketing = [ + const v1Marketing = ["/terms", "/terms/", "/_v1-marketing/Layout.css", "/_v1-marketing/_ph/e"]; + for (const pathname of v1Marketing) { + it(`sends ${pathname} to v1's marketing worker`, () => { + expect(isMarketingPath(pathname)).toBe(true); + }); + } + + const notV1Marketing = [ + "/", "/home", "/privacy", - "/terms", "/pricing", - "/about-executor", - "/google-oauth", - "/google-workspace", "/blog", - "/blog/", - "/blog/some-post", - "/llms.txt", - "/index.md", - "/setup-prompt.md", - "/pricing.md", - "/og-image.png", "/_astro/app.css", - // The blog author card loads its avatar from marketing's public/authors; - // without this the pfp 404s on every post. - "/authors/rhys-sullivan.png", + "/_astro/_ph/e", + "/termsandconditions", + "/_v1-marketingx", + "/login", ]; - for (const pathname of marketing) { - it(`proxies ${pathname} to marketing`, () => { - expect(isMarketingPath(pathname)).toBe(true); - }); - } - - // App-owned routes must reach the Effect handler, not marketing. `/blogger` - // guards against a bare `startsWith("/blog")` swallowing unrelated words. - const notMarketing = ["/", "/login", "/cloud", "/mcp", "/dashboard", "/blogger"]; - for (const pathname of notMarketing) { - it(`leaves ${pathname} alone`, () => { + for (const pathname of notV1Marketing) { + it(`does not send ${pathname} to v1's marketing worker`, () => { expect(isMarketingPath(pathname)).toBe(false); }); } }); describe("marketingProxyRequest", () => { - it("routes a signed-out homepage request", () => { - const request = new Request("https://executor.sh/?source=test"); - - const proxied = marketingProxyRequest(request); - - expect(proxied?.url).toBe("https://executor.sh/?source=test"); - }); - - it("leaves the signed-in homepage with the cloud application", () => { - const request = new Request("https://executor.sh/", { - headers: { cookie: "other=value; wos-session=sealed" }, - }); - - expect(marketingProxyRequest(request)).toBeNull(); - }); - - it("routes public content even when a session cookie is present", () => { - const request = new Request("https://executor.sh/blog/post", { - headers: { cookie: "wos-session=sealed" }, - }); - - expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/blog/post"); - }); - - it("routes /pricing to the marketing worker", () => { - const request = new Request("https://executor.sh/pricing"); - - expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/pricing"); - }); - - it("rewrites the public home alias to the marketing root", () => { - const request = new Request("https://executor.sh/home?source=test"); - - expect(marketingProxyRequest(request)?.url).toBe("https://executor.sh/?source=test"); - }); - - it("preserves the request method, headers, and body", async () => { - const request = new Request("https://executor.sh/_astro/_ph/capture", { + it("routes v1's terms, method, headers and body unchanged", async () => { + const request = new Request("https://executor.sh/_v1-marketing/_ph/capture?ip=1", { method: "POST", headers: { "content-type": "application/json", "x-request-id": "request-1" }, body: JSON.stringify({ event: "test" }), @@ -100,14 +51,23 @@ describe("marketingProxyRequest", () => { const proxied = marketingProxyRequest(request); + expect(proxied?.url).toBe("https://executor.sh/_v1-marketing/_ph/capture?ip=1"); expect(proxied?.method).toBe("POST"); expect(proxied?.headers.get("x-request-id")).toBe("request-1"); await expect(proxied?.json()).resolves.toEqual({ event: "test" }); + expect(marketingProxyRequest(new Request("https://executor.sh/terms"))?.url).toBe( + "https://executor.sh/terms", + ); }); - it("does not proxy non-production hosts or app-owned paths", () => { - expect(marketingProxyRequest(new Request("http://executor-cloud.localhost/"))).toBeNull(); - expect(marketingProxyRequest(new Request("https://executor.sh/login"))).toBeNull(); + it("leaves the homepage and v2's marketing to the v2 edge", () => { + expect(marketingProxyRequest(new Request("https://executor.sh/"))).toBeNull(); + expect(marketingProxyRequest(new Request("https://executor.sh/home"))).toBeNull(); + expect(marketingProxyRequest(new Request("https://executor.sh/pricing"))).toBeNull(); + }); + + it("does not proxy non-production hosts", () => { + expect(marketingProxyRequest(new Request("http://executor-cloud.localhost/terms"))).toBeNull(); }); }); @@ -152,9 +112,9 @@ describe("isV2Path", () => { "/github", "/.well-known/agent-skills", "/.well-known/agent-skillset/index.json", - // The connected-account callback goes by its state, not its path. + // The connected-account callback goes by its state, not its path, and + // marketing has its own list. "/api/oauth/callback", - // Not yet: marketing. "/pricing", // v1 dashboard, org pages and MCP, including org slugs that look similar. "/", @@ -190,6 +150,7 @@ describe("parseV2Edge", () => { V2: service, V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", V2_OAUTH_STATE_PREFIX: "x2.", + V2_TELEMETRY_PATHS: "/api/0123456789abcdef,/api/fedcba9876543210", }; it("is off when no setting is present", () => { @@ -200,6 +161,7 @@ describe("parseV2Edge", () => { expect(typeof parseV2Edge({ ...settings, V2: undefined })).toBe("string"); expect(typeof parseV2Edge({ ...settings, V2_SIGN_UP_URL: undefined })).toBe("string"); expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: undefined })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_TELEMETRY_PATHS: undefined })).toBe("string"); expect(typeof parseV2Edge({ V2_OAUTH_STATE_PREFIX: "x2." })).toBe("string"); }); @@ -220,24 +182,52 @@ describe("parseV2Edge", () => { } }); + it("refuses telemetry roots outside /api/<16 hex>", () => { + for (const paths of [ + "", + "/api/0123456789abcde", + "/api/0123456789abcdef0", + "/api/0123456789ABCDEF", + "/api/0123456789abcdef/", + "/api/0123456789abcdef/submit", + "/api/connections", + "/0123456789abcdef", + "/api/0123456789abcdef,", + "/api/0123456789abcdef;/api/fedcba9876543210", + ]) { + expect(typeof parseV2Edge({ ...settings, V2_TELEMETRY_PATHS: paths })).toBe("string"); + } + }); + it("parses all settings", () => { const edge = parseV2Edge(settings); if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); expect(edge.signUpUrl.href).toBe("https://app.executor.sh/login?mode=signup"); expect(edge.oauthStatePrefix).toBe("x2."); + expect(edge.telemetryPaths).toEqual(["/api/0123456789abcdef", "/api/fedcba9876543210"]); expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: "v2~" })).toBe("object"); + const spaced = parseV2Edge({ + ...settings, + V2_TELEMETRY_PATHS: " /api/0123456789abcdef , /api/fedcba9876543210 ", + }); + if (spaced === null || typeof spaced === "string") { + return expect.unreachable("settings must parse"); + } + expect(spaced.telemetryPaths).toEqual(["/api/0123456789abcdef", "/api/fedcba9876543210"]); }); }); describe("v2EdgeResponse", () => { const SIGN_UP_URL = "https://v2.executor.sh/login?mode=signup"; const STATE_PREFIX = "x2."; + const TELEMETRY_PATHS = "/api/0123456789abcdef"; /** The edge's settings with `service` as v2's Worker. */ const settings = (service: V2Service, signUpUrl = SIGN_UP_URL): V2EdgeEnv => ({ V2: service, V2_SIGN_UP_URL: signUpUrl, V2_OAUTH_STATE_PREFIX: STATE_PREFIX, + V2_TELEMETRY_PATHS: TELEMETRY_PATHS, }); /** A v2 service that records what it receives and answers with `respond`. */ @@ -440,6 +430,7 @@ describe("v2EdgeResponse connected-account callback", () => { V2: service, V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", V2_OAUTH_STATE_PREFIX: "x2.", + V2_TELEMETRY_PATHS: "/api/0123456789abcdef", }); const recordingService = () => { @@ -571,3 +562,264 @@ describe("v2EdgeResponse connected-account callback", () => { expect(received).toHaveLength(0); }); }); + +// v2's marketing site answers executor.sh: the landing page without a v1 +// session, its pages, files, assets and docs, and its telemetry proxies. +describe("isV2MarketingPath", () => { + const v2Marketing = [ + "/home", + "/about-executor", + "/blog", + "/blog/", + "/blog/some-post", + "/pricing", + "/privacy", + "/google-oauth", + "/google-workspace", + "/index.md", + "/llms.txt", + "/pricing.md", + "/setup-prompt.md", + "/_astro/app.Cld-QA3g.css", + "/authors/author.png", + "/og-image.png", + "/pattern-graph-paper.svg", + "/docs", + "/docs/", + "/docs/quickstart", + "/docs/llms.txt", + ]; + for (const pathname of v2Marketing) { + it(`sends ${pathname} to v2`, () => { + expect(isV2MarketingPath(pathname)).toBe(true); + }); + } + + const v1Owned = [ + // The homepage depends on the session; see v2EdgeResponse. + "/", + // v2 marketing paths that are unreserved v1 organization slugs. + "/apps", + "/apps/acme/tools", + "/demo", + "/demo/workflows", + "/experiments/hero/b", + // v1's terms, and its dashboard's favicons. + "/terms", + "/favicon.ico", + "/favicon-32.png", + "/apple-touch-icon.png", + // Lookalikes and v1 routes. + "/blogger", + "/docsearch", + "/_astrox/app.css", + "/home-team/policies", + "/pricing-team/mcp", + "/setup", + "/login", + "/api/docs", + "/acme/docs", + ]; + for (const pathname of v1Owned) { + it(`leaves ${pathname} with v1`, () => { + expect(isV2MarketingPath(pathname)).toBe(false); + }); + } +}); + +describe("v2EdgeResponse marketing", () => { + const settings = (service: V2Service): V2EdgeEnv => ({ + V2: service, + V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", + V2_OAUTH_STATE_PREFIX: "x2.", + V2_TELEMETRY_PATHS: "/api/0123456789abcdef,/api/fedcba9876543210", + }); + + const recordingService = (respond: () => Response = () => new Response("v2")) => { + const received: Request[] = []; + const service: V2Service = { + fetch: async (request) => { + received.push(request); + return respond(); + }, + }; + return { received, service }; + }; + + it("sends the signed-out homepage to v2 with the URL unchanged", async () => { + const { received, service } = recordingService(); + + const response = await v2EdgeResponse( + new Request("https://executor.sh/?hero=b&utm_source=x"), + settings(service), + ); + + expect(await response?.text()).toBe("v2"); + expect(received[0]?.url).toBe("https://executor.sh/?hero=b&utm_source=x"); + expect(received[0]?.redirect).toBe("manual"); + }); + + it("keeps the signed-in homepage with v1's dashboard", () => { + const { received, service } = recordingService(); + + expect( + v2EdgeResponse( + new Request("https://executor.sh/", { + headers: { cookie: "executor_visitor=v; wos-session=sealed" }, + }), + settings(service), + ), + ).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("passes only v2's visitor cookie, never v1's session", async () => { + const { received, service } = recordingService(); + + await v2EdgeResponse( + new Request("https://executor.sh/blog/post", { + headers: { + cookie: + "ph_id=1; executor_visitor=0f1e2d3c-4b5a-4987-a6b5-c4d3e2f1a0b9; wos-session=sealed", + "x-forwarded-host": "attacker.example", + }, + }), + settings(service), + ); + await v2EdgeResponse( + new Request("https://executor.sh/", { headers: { cookie: "ph_id=1; executor_hero=x" } }), + settings(service), + ); + + expect(received[0]?.headers.get("cookie")).toBe( + "executor_visitor=0f1e2d3c-4b5a-4987-a6b5-c4d3e2f1a0b9", + ); + expect(received[0]?.headers.has("x-forwarded-host")).toBe(false); + expect(received[1]?.headers.has("cookie")).toBe(false); + }); + + it("serves /home, docs and assets from v2 without rewriting the path", async () => { + const { received, service } = recordingService(); + + for (const url of [ + "https://executor.sh/home?ref=x", + "https://executor.sh/docs/quickstart", + "https://executor.sh/_astro/app.css", + "https://executor.sh/llms.txt", + ]) { + await v2EdgeResponse(new Request(url), settings(service)); + } + + expect(received.map((request) => request.url)).toEqual([ + "https://executor.sh/home?ref=x", + "https://executor.sh/docs/quickstart", + "https://executor.sh/_astro/app.css", + "https://executor.sh/llms.txt", + ]); + }); + + it("returns v2's response unchanged, its cookies included", async () => { + const upstream = new Response("", { + status: 200, + headers: { + "content-type": "text/html", + "set-cookie": "executor_visitor=v; Path=/; SameSite=Lax; Secure", + vary: "Cookie", + }, + }); + const { service } = recordingService(() => upstream); + + expect(await v2EdgeResponse(new Request("https://executor.sh/"), settings(service))).toBe( + upstream, + ); + }); + + it("answers marketing with a 500 on broken settings", async () => { + const { received, service } = recordingService(); + + const response = await v2EdgeResponse(new Request("https://executor.sh/pricing"), { + ...settings(service), + V2_TELEMETRY_PATHS: "/api/nothex", + }); + + expect(response?.status).toBe(500); + expect(received).toHaveLength(0); + }); + + it("is off without settings and off executor.sh", () => { + const { received, service } = recordingService(); + + expect(v2EdgeResponse(new Request("https://executor.sh/"), {})).toBeNull(); + expect(v2EdgeResponse(new Request("https://executor.sh/pricing"), {})).toBeNull(); + expect( + v2EdgeResponse(new Request("http://executor-cloud.localhost/"), settings(service)), + ).toBeNull(); + expect( + v2EdgeResponse(new Request("http://executor-cloud.localhost/docs"), settings(service)), + ).toBeNull(); + expect(received).toHaveLength(0); + }); + + it("forwards v2's telemetry proxies without cookies, body and method intact", async () => { + const { received, service } = recordingService(); + + await v2EdgeResponse( + new Request("https://executor.sh/api/0123456789abcdef/e/?ip=1", { + method: "POST", + headers: { cookie: "wos-session=sealed", "content-type": "text/plain" }, + body: "event", + }), + settings(service), + ); + await v2EdgeResponse( + new Request("https://executor.sh/api/fedcba9876543210/submit", { method: "POST" }), + settings(service), + ); + await v2EdgeResponse( + new Request("https://executor.sh/api/0123456789abcdef"), + settings(service), + ); + + expect(received.map((request) => request.url)).toEqual([ + "https://executor.sh/api/0123456789abcdef/e/?ip=1", + "https://executor.sh/api/fedcba9876543210/submit", + "https://executor.sh/api/0123456789abcdef", + ]); + expect(received[0]?.method).toBe("POST"); + expect(received[0]?.headers.has("cookie")).toBe(false); + expect(await received[0]?.text()).toBe("event"); + }); + + const v1Api = [ + // Another 16-hex root, and v1's own PostHog proxy (8 hex). + "https://executor.sh/api/aaaaaaaaaaaaaaaa/e/", + "https://executor.sh/api/0a1b2c3d/e/", + // Lookalikes of a configured root. + "https://executor.sh/api/0123456789abcdef0/e/", + "https://executor.sh/api/0123456789abcdefx", + "https://executor.sh/0123456789abcdef/e/", + "https://executor.sh/acme/api/0123456789abcdef/e/", + // v1's API. + "https://executor.sh/api/connections", + "https://executor.sh/api/docs", + ]; + for (const url of v1Api) { + it(`leaves ${new URL(url).pathname} with v1`, () => { + const { received, service } = recordingService(); + expect(v2EdgeResponse(new Request(url), settings(service))).toBeNull(); + expect(received).toHaveLength(0); + }); + } + + it("leaves telemetry roots with v1 when the settings are broken", () => { + const { received, service } = recordingService(); + + expect( + v2EdgeResponse(new Request("https://executor.sh/api/0123456789abcdef/e/"), { + ...settings(service), + V2_SIGN_UP_URL: "/relative", + }), + ).toBeNull(); + expect(received).toHaveLength(0); + }); +}); diff --git a/apps/cloud/src/edge/production-config.test.ts b/apps/cloud/src/edge/production-config.test.ts index 5ec0621d0d..e2b737b000 100644 --- a/apps/cloud/src/edge/production-config.test.ts +++ b/apps/cloud/src/edge/production-config.test.ts @@ -29,6 +29,7 @@ const shipped: V2EdgeEnv = { V2: standIn, V2_SIGN_UP_URL: stringVar("V2_SIGN_UP_URL"), V2_OAUTH_STATE_PREFIX: stringVar("V2_OAUTH_STATE_PREFIX"), + V2_TELEMETRY_PATHS: stringVar("V2_TELEMETRY_PATHS"), }; describe("production v2 edge settings", () => { @@ -50,4 +51,14 @@ describe("production v2 edge settings", () => { if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); expect(edge.oauthStatePrefix).toBe("x2."); }); + + it("ships v2's analytics and error-reporting proxy roots", () => { + const edge = parseV2Edge(shipped); + if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); + expect(edge.telemetryPaths).toHaveLength(2); + }); + + it("keeps the MARKETING binding for v1's terms", () => { + expect(config.services.filter((service) => service.binding === "MARKETING")).toHaveLength(1); + }); }); From b7a974b5cb43dcbe13a95cb318eb97fd385c63c9 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:39:01 -0700 Subject: [PATCH 09/11] Test v1's edge against v2's pinned edge contract --- .oxfmtrc.json | 3 +- apps/cloud/src/edge/marketing.test.ts | 131 +++++++++++++----- apps/cloud/src/edge/production-config.test.ts | 87 +++++++++++- apps/cloud/src/edge/v2-edge-contract.json | 107 ++++++++++++++ 4 files changed, 286 insertions(+), 42 deletions(-) create mode 100644 apps/cloud/src/edge/v2-edge-contract.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 73f7255814..5662270268 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -14,6 +14,7 @@ "executor-*.tgz", "**/routeTree.gen.ts", "**/smoke-harness-bundle.gen.ts", - "apps/cloud/src/services/executor-schema.ts" + "apps/cloud/src/services/executor-schema.ts", + "apps/cloud/src/edge/v2-edge-contract.json" ] } diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index 9c77fbfd9b..a61f393524 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -12,10 +12,18 @@ import { type V2Service, } from "./marketing"; -// On executor.sh only v1's terms of service (and their asset root) still go to -// v1's `executor-marketing` worker; v2 serves the rest of marketing. +// On executor.sh only v1's legal pages (terms, privacy policy and Google OAuth +// disclosure) and their asset root still go to v1's `executor-marketing` +// worker; v2 serves the rest of marketing. describe("isMarketingPath", () => { - const v1Marketing = ["/terms", "/terms/", "/_v1-marketing/Layout.css", "/_v1-marketing/_ph/e"]; + const v1Marketing = [ + "/terms", + "/terms/", + "/privacy", + "/google-oauth", + "/_v1-marketing/Layout.css", + "/_v1-marketing/_ph/e", + ]; for (const pathname of v1Marketing) { it(`sends ${pathname} to v1's marketing worker`, () => { expect(isMarketingPath(pathname)).toBe(true); @@ -25,12 +33,14 @@ describe("isMarketingPath", () => { const notV1Marketing = [ "/", "/home", - "/privacy", "/pricing", "/blog", + "/google-workspace", "/_astro/app.css", "/_astro/_ph/e", "/termsandconditions", + "/privacy-team/mcp", + "/google-oauthx", "/_v1-marketingx", "/login", ]; @@ -55,9 +65,11 @@ describe("marketingProxyRequest", () => { expect(proxied?.method).toBe("POST"); expect(proxied?.headers.get("x-request-id")).toBe("request-1"); await expect(proxied?.json()).resolves.toEqual({ event: "test" }); - expect(marketingProxyRequest(new Request("https://executor.sh/terms"))?.url).toBe( - "https://executor.sh/terms", - ); + for (const path of ["/terms", "/privacy", "/google-oauth"]) { + expect(marketingProxyRequest(new Request(`https://executor.sh${path}`))?.url).toBe( + `https://executor.sh${path}`, + ); + } }); it("leaves the homepage and v2's marketing to the v2 edge", () => { @@ -150,7 +162,8 @@ describe("parseV2Edge", () => { V2: service, V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", V2_OAUTH_STATE_PREFIX: "x2.", - V2_TELEMETRY_PATHS: "/api/0123456789abcdef,/api/fedcba9876543210", + V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef", + V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit", }; it("is off when no setting is present", () => { @@ -161,7 +174,8 @@ describe("parseV2Edge", () => { expect(typeof parseV2Edge({ ...settings, V2: undefined })).toBe("string"); expect(typeof parseV2Edge({ ...settings, V2_SIGN_UP_URL: undefined })).toBe("string"); expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: undefined })).toBe("string"); - expect(typeof parseV2Edge({ ...settings, V2_TELEMETRY_PATHS: undefined })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_ANALYTICS_PROXY_PATH: undefined })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_ERROR_TUNNEL_PATH: undefined })).toBe("string"); expect(typeof parseV2Edge({ V2_OAUTH_STATE_PREFIX: "x2." })).toBe("string"); }); @@ -182,8 +196,8 @@ describe("parseV2Edge", () => { } }); - it("refuses telemetry roots outside /api/<16 hex>", () => { - for (const paths of [ + it("refuses an analytics proxy path other than /api/<16 lowercase hex>", () => { + for (const path of [ "", "/api/0123456789abcde", "/api/0123456789abcdef0", @@ -192,10 +206,27 @@ describe("parseV2Edge", () => { "/api/0123456789abcdef/submit", "/api/connections", "/0123456789abcdef", - "/api/0123456789abcdef,", - "/api/0123456789abcdef;/api/fedcba9876543210", + " /api/0123456789abcdef", + "/api/0123456789abcdef,/api/fedcba9876543210", + ]) { + expect(typeof parseV2Edge({ ...settings, V2_ANALYTICS_PROXY_PATH: path })).toBe("string"); + } + }); + + it("refuses an error tunnel path other than /api/<16 lowercase hex>/submit", () => { + for (const path of [ + "", + "/api/fedcba9876543210", + "/api/fedcba9876543210/", + "/api/fedcba9876543210/submit/", + "/api/fedcba9876543210/submitx", + "/api/FEDCBA9876543210/submit", + "/api/fedcba987654321/submit", + "/api/fedcba9876543210/e", + "/fedcba9876543210/submit", + " /api/fedcba9876543210/submit", ]) { - expect(typeof parseV2Edge({ ...settings, V2_TELEMETRY_PATHS: paths })).toBe("string"); + expect(typeof parseV2Edge({ ...settings, V2_ERROR_TUNNEL_PATH: path })).toBe("string"); } }); @@ -204,30 +235,23 @@ describe("parseV2Edge", () => { if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); expect(edge.signUpUrl.href).toBe("https://app.executor.sh/login?mode=signup"); expect(edge.oauthStatePrefix).toBe("x2."); - expect(edge.telemetryPaths).toEqual(["/api/0123456789abcdef", "/api/fedcba9876543210"]); + expect(edge.analyticsProxyPath).toBe("/api/0123456789abcdef"); + expect(edge.errorTunnelPath).toBe("/api/fedcba9876543210/submit"); expect(typeof parseV2Edge({ ...settings, V2_OAUTH_STATE_PREFIX: "v2~" })).toBe("object"); - const spaced = parseV2Edge({ - ...settings, - V2_TELEMETRY_PATHS: " /api/0123456789abcdef , /api/fedcba9876543210 ", - }); - if (spaced === null || typeof spaced === "string") { - return expect.unreachable("settings must parse"); - } - expect(spaced.telemetryPaths).toEqual(["/api/0123456789abcdef", "/api/fedcba9876543210"]); }); }); describe("v2EdgeResponse", () => { const SIGN_UP_URL = "https://v2.executor.sh/login?mode=signup"; const STATE_PREFIX = "x2."; - const TELEMETRY_PATHS = "/api/0123456789abcdef"; /** The edge's settings with `service` as v2's Worker. */ const settings = (service: V2Service, signUpUrl = SIGN_UP_URL): V2EdgeEnv => ({ V2: service, V2_SIGN_UP_URL: signUpUrl, V2_OAUTH_STATE_PREFIX: STATE_PREFIX, - V2_TELEMETRY_PATHS: TELEMETRY_PATHS, + V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef", + V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit", }); /** A v2 service that records what it receives and answers with `respond`. */ @@ -430,7 +454,8 @@ describe("v2EdgeResponse connected-account callback", () => { V2: service, V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", V2_OAUTH_STATE_PREFIX: "x2.", - V2_TELEMETRY_PATHS: "/api/0123456789abcdef", + V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef", + V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit", }); const recordingService = () => { @@ -564,7 +589,8 @@ describe("v2EdgeResponse connected-account callback", () => { }); // v2's marketing site answers executor.sh: the landing page without a v1 -// session, its pages, files, assets and docs, and its telemetry proxies. +// session, its pages, files, assets and docs, and its telemetry proxies. A +// pattern is exact, or `/x/*` for everything that starts with `/x/`. describe("isV2MarketingPath", () => { const v2Marketing = [ "/home", @@ -573,8 +599,6 @@ describe("isV2MarketingPath", () => { "/blog/", "/blog/some-post", "/pricing", - "/privacy", - "/google-oauth", "/google-workspace", "/index.md", "/llms.txt", @@ -588,6 +612,12 @@ describe("isV2MarketingPath", () => { "/docs/", "/docs/quickstart", "/docs/llms.txt", + // `apps` and `experiments` are reserved v1 organization slugs. + "/apps", + "/apps/", + "/apps/detail", + "/experiments/", + "/experiments/hero/b", ]; for (const pathname of v2Marketing) { it(`sends ${pathname} to v2`, () => { @@ -598,19 +628,31 @@ describe("isV2MarketingPath", () => { const v1Owned = [ // The homepage depends on the session; see v2EdgeResponse. "/", - // v2 marketing paths that are unreserved v1 organization slugs. - "/apps", - "/apps/acme/tools", + // `/demo` is an unreserved v1 organization slug; v2 publishes nothing at + // a bare `/experiments`. "/demo", "/demo/workflows", - "/experiments/hero/b", - // v1's terms, and its dashboard's favicons. + "/experiments", + // Exact pages match only themselves, and directories only what is below + // them. + "/pricing/extra", + "/home/extra", + "/home/", + "/llms.txt/x", + "/_astro", + "/authors", + "/google-workspace/x", + // v1's legal pages, and its dashboard's favicons. "/terms", + "/privacy", + "/google-oauth", "/favicon.ico", "/favicon-32.png", "/apple-touch-icon.png", // Lookalikes and v1 routes. "/blogger", + "/appsmith", + "/experimentsx/a", "/docsearch", "/_astrox/app.css", "/home-team/policies", @@ -632,7 +674,8 @@ describe("v2EdgeResponse marketing", () => { V2: service, V2_SIGN_UP_URL: "https://app.executor.sh/login?mode=signup", V2_OAUTH_STATE_PREFIX: "x2.", - V2_TELEMETRY_PATHS: "/api/0123456789abcdef,/api/fedcba9876543210", + V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef", + V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit", }); const recordingService = (respond: () => Response = () => new Response("v2")) => { @@ -737,12 +780,17 @@ describe("v2EdgeResponse marketing", () => { it("answers marketing with a 500 on broken settings", async () => { const { received, service } = recordingService(); - const response = await v2EdgeResponse(new Request("https://executor.sh/pricing"), { + const brokenAnalytics = await v2EdgeResponse(new Request("https://executor.sh/pricing"), { ...settings(service), - V2_TELEMETRY_PATHS: "/api/nothex", + V2_ANALYTICS_PROXY_PATH: "/api/nothex", + }); + const brokenTunnel = await v2EdgeResponse(new Request("https://executor.sh/pricing"), { + ...settings(service), + V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210", }); - expect(response?.status).toBe(500); + expect(brokenAnalytics?.status).toBe(500); + expect(brokenTunnel?.status).toBe(500); expect(received).toHaveLength(0); }); @@ -793,6 +841,13 @@ describe("v2EdgeResponse marketing", () => { const v1Api = [ // Another 16-hex root, and v1's own PostHog proxy (8 hex). "https://executor.sh/api/aaaaaaaaaaaaaaaa/e/", + "https://executor.sh/api/aaaaaaaaaaaaaaaa/submit", + // The error tunnel is forwarded exactly: not its root or other subpaths. + "https://executor.sh/api/fedcba9876543210", + "https://executor.sh/api/fedcba9876543210/", + "https://executor.sh/api/fedcba9876543210/e/", + "https://executor.sh/api/fedcba9876543210/submit/", + "https://executor.sh/api/fedcba9876543210/submit/x", "https://executor.sh/api/0a1b2c3d/e/", // Lookalikes of a configured root. "https://executor.sh/api/0123456789abcdef0/e/", diff --git a/apps/cloud/src/edge/production-config.test.ts b/apps/cloud/src/edge/production-config.test.ts index e2b737b000..c9dac4cc9f 100644 --- a/apps/cloud/src/edge/production-config.test.ts +++ b/apps/cloud/src/edge/production-config.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "@effect/vitest"; @@ -29,7 +30,8 @@ const shipped: V2EdgeEnv = { V2: standIn, V2_SIGN_UP_URL: stringVar("V2_SIGN_UP_URL"), V2_OAUTH_STATE_PREFIX: stringVar("V2_OAUTH_STATE_PREFIX"), - V2_TELEMETRY_PATHS: stringVar("V2_TELEMETRY_PATHS"), + V2_ANALYTICS_PROXY_PATH: stringVar("V2_ANALYTICS_PROXY_PATH"), + V2_ERROR_TUNNEL_PATH: stringVar("V2_ERROR_TUNNEL_PATH"), }; describe("production v2 edge settings", () => { @@ -52,13 +54,92 @@ describe("production v2 edge settings", () => { expect(edge.oauthStatePrefix).toBe("x2."); }); - it("ships v2's analytics and error-reporting proxy roots", () => { + it("ships v2's analytics proxy root and error tunnel", () => { const edge = parseV2Edge(shipped); if (edge === null || typeof edge === "string") return expect.unreachable("settings must parse"); - expect(edge.telemetryPaths).toHaveLength(2); + expect(edge.analyticsProxyPath).toBe("/api/00e2e1f082a6ef17"); + expect(edge.errorTunnelPath).toBe("/api/fd6fab1fbb4883e1/submit"); }); it("keeps the MARKETING binding for v1's terms", () => { expect(config.services.filter((service) => service.binding === "MARKETING")).toHaveLength(1); }); }); + +// v2 publishes the exact set of executor.sh requests v1's edge forwards to it. +// `v2-edge-contract.json` is a verbatim copy of v2's edge contract; update it +// only together with v2's contract, never by hand here. Every case runs +// through v1's real edge decision with the shipped settings. +const EdgeContract = Schema.Struct({ + origin: Schema.String, + oauthStatePrefix: Schema.String, + telemetry: Schema.Struct({ analyticsProxy: Schema.String, errorTunnel: Schema.String }), + cases: Schema.Array( + Schema.Struct({ method: Schema.String, target: Schema.String, forwards: Schema.Boolean }), + ), +}); +const contract = Schema.decodeUnknownSync(Schema.fromJsonString(EdgeContract))( + readFileSync(fileURLToPath(new URL("./v2-edge-contract.json", import.meta.url)), "utf8"), +); + +const SIGN_UP_TARGETS: ReadonlySet = new Set(["/sign-up", "/signup"]); + +/** Run one contract case through v1's edge with the shipped settings and a + * stand-in for v2's Worker that records every call. */ +const runCase = async (method: string, target: string) => { + const calls: Request[] = []; + const v2: V2Service = { + fetch: (request) => { + calls.push(request); + return Promise.resolve(new Response("v2")); + }, + }; + const request = new Request(`${contract.origin}${target}`, { method }); + const response = await v2EdgeResponse(request, { ...shipped, V2: v2 }); + return { request, calls, response }; +}; + +const forwarded = contract.cases.filter((c) => c.forwards); +const signUps = contract.cases.filter((c) => !c.forwards && SIGN_UP_TARGETS.has(c.target)); +const kept = contract.cases.filter((c) => !c.forwards && !SIGN_UP_TARGETS.has(c.target)); + +describe("v2's edge contract", () => { + it("pins the telemetry paths and state prefix v1 ships", () => { + expect(contract.origin).toBe("https://executor.sh"); + expect(contract.oauthStatePrefix).toBe(stringVar("V2_OAUTH_STATE_PREFIX")); + expect(contract.telemetry.analyticsProxy).toBe(stringVar("V2_ANALYTICS_PROXY_PATH")); + expect(contract.telemetry.errorTunnel).toBe(stringVar("V2_ERROR_TUNNEL_PATH")); + }); + + for (const { method, target } of forwarded) { + it(`forwards ${method} ${target} to v2`, async () => { + const { request, calls, response } = await runCase(method, target); + expect(calls).toHaveLength(1); + expect(calls[0]?.url).toBe(request.url); + expect(calls[0]?.method).toBe(method); + expect(await response?.text()).toBe("v2"); + }); + } + + for (const { method, target } of kept) { + it(`keeps ${method} ${target} with v1`, async () => { + const { calls, response } = await runCase(method, target); + expect(calls).toHaveLength(0); + expect(response).toBeNull(); + }); + } + + // Sign-up is not forwarded: the edge redirects it to v2's sign-up page. + it("lists both sign-up paths as not forwarded", () => { + expect(signUps.map((c) => c.target).toSorted()).toEqual(["/sign-up", "/signup"]); + }); + + for (const { method, target } of signUps) { + it(`redirects ${method} ${target} to v2's sign-up page without forwarding`, async () => { + const { calls, response } = await runCase(method, target); + expect(calls).toHaveLength(0); + expect(response?.status).toBe(302); + expect(response?.headers.get("location")).toBe(stringVar("V2_SIGN_UP_URL")); + }); + } +}); diff --git a/apps/cloud/src/edge/v2-edge-contract.json b/apps/cloud/src/edge/v2-edge-contract.json new file mode 100644 index 0000000000..c6080f276f --- /dev/null +++ b/apps/cloud/src/edge/v2-edge-contract.json @@ -0,0 +1,107 @@ +{ + "source": "UsefulSoftwareCo/executor-next apps/hosted/cloud/src/contracts/edge-contract.json, generated from edgeForwards in apps/hosted/cloud/src/contracts/edge-paths.ts", + "origin": "https://executor.sh", + "oauthStatePrefix": "x2.", + "telemetry": { + "analyticsProxy": "/api/00e2e1f082a6ef17", + "errorTunnel": "/api/fd6fab1fbb4883e1/submit" + }, + "rules": [ + "/.well-known/oauth-authorization-server/api/auth", + "/api/auth/callback/", + "/api/oauth/callback, when state starts with x2.", + "/git/*", + "/ (without a v1 session)", + "/home", + "/pricing", + "/pricing.md", + "/index.md", + "/setup-prompt.md", + "/llms.txt", + "/about-executor", + "/google-workspace", + "/blog", + "/blog/*", + "/docs", + "/docs/*", + "/apps", + "/apps/*", + "/experiments/*", + "/_astro/*", + "/authors/*", + "/og-image.png", + "/pattern-graph-paper.svg", + "/.well-known/agent-skills/*", + "/api/00e2e1f082a6ef17, /api/00e2e1f082a6ef17/*", + "/api/fd6fab1fbb4883e1/submit" + ], + "cases": [ + { "method": "GET", "target": "/.well-known/oauth-authorization-server/api/auth", "forwards": true }, + { "method": "GET", "target": "/api/auth/callback/google?code=c&state=s", "forwards": true }, + { "method": "GET", "target": "/api/auth/callback/github", "forwards": true }, + { "method": "GET", "target": "/api/oauth/callback?code=c&state=x2.abc", "forwards": true }, + { "method": "GET", "target": "/git/acme/site.git/info/refs?service=git-upload-pack", "forwards": true }, + { "method": "POST", "target": "/git/acme/site.git/git-receive-pack", "forwards": true }, + { "method": "GET", "target": "/", "forwards": true }, + { "method": "GET", "target": "/home", "forwards": true }, + { "method": "GET", "target": "/pricing", "forwards": true }, + { "method": "GET", "target": "/pricing.md", "forwards": true }, + { "method": "GET", "target": "/index.md", "forwards": true }, + { "method": "GET", "target": "/setup-prompt.md", "forwards": true }, + { "method": "GET", "target": "/llms.txt", "forwards": true }, + { "method": "GET", "target": "/about-executor", "forwards": true }, + { "method": "GET", "target": "/google-workspace", "forwards": true }, + { "method": "GET", "target": "/blog", "forwards": true }, + { "method": "GET", "target": "/blog/a-post", "forwards": true }, + { "method": "GET", "target": "/docs", "forwards": true }, + { "method": "GET", "target": "/docs/quickstart", "forwards": true }, + { "method": "GET", "target": "/apps", "forwards": true }, + { "method": "GET", "target": "/apps/detail", "forwards": true }, + { "method": "GET", "target": "/experiments/hero/a", "forwards": true }, + { "method": "GET", "target": "/experiments/demo/posthog", "forwards": true }, + { "method": "GET", "target": "/_astro/page.abc123.js", "forwards": true }, + { "method": "GET", "target": "/authors/author.png", "forwards": true }, + { "method": "GET", "target": "/og-image.png", "forwards": true }, + { "method": "GET", "target": "/pattern-graph-paper.svg", "forwards": true }, + { "method": "GET", "target": "/.well-known/agent-skills/index.json", "forwards": true }, + { "method": "GET", "target": "/api/00e2e1f082a6ef17", "forwards": true }, + { "method": "POST", "target": "/api/00e2e1f082a6ef17/e/?ip=0", "forwards": true }, + { "method": "GET", "target": "/api/00e2e1f082a6ef17/static/array.js", "forwards": true }, + { "method": "POST", "target": "/api/fd6fab1fbb4883e1/submit", "forwards": true }, + + { "method": "GET", "target": "/api/auth/.well-known/openid-configuration", "forwards": false }, + { "method": "GET", "target": "/.well-known/oauth-authorization-server", "forwards": false }, + { "method": "GET", "target": "/.well-known/oauth-protected-resource", "forwards": false }, + { "method": "GET", "target": "/api/auth/callback?code=c&state=s", "forwards": false }, + { "method": "GET", "target": "/api/auth/callback/google/extra", "forwards": false }, + { "method": "GET", "target": "/api/auth/login", "forwards": false }, + { "method": "GET", "target": "/api/oauth/callback?code=c&state=abc", "forwards": false }, + { "method": "GET", "target": "/api/oauth/callback?code=c", "forwards": false }, + { "method": "GET", "target": "/oauth/client-metadata.json", "forwards": false }, + { "method": "GET", "target": "/oauth/client-id-metadata.json", "forwards": false }, + { "method": "GET", "target": "/git", "forwards": false }, + { "method": "GET", "target": "/gitlab/repo", "forwards": false }, + { "method": "GET", "target": "/demo", "forwards": false }, + { "method": "GET", "target": "/demo/posthog", "forwards": false }, + { "method": "GET", "target": "/experiments", "forwards": false }, + { "method": "GET", "target": "/privacy", "forwards": false }, + { "method": "GET", "target": "/terms", "forwards": false }, + { "method": "GET", "target": "/google-oauth", "forwards": false }, + { "method": "GET", "target": "/_v1-marketing/style.css", "forwards": false }, + { "method": "GET", "target": "/favicon-32.png", "forwards": false }, + { "method": "GET", "target": "/favicon-192.png", "forwards": false }, + { "method": "GET", "target": "/apple-touch-icon.png", "forwards": false }, + { "method": "GET", "target": "/favicon.ico", "forwards": false }, + { "method": "GET", "target": "/pricing/extra", "forwards": false }, + { "method": "GET", "target": "/home/extra", "forwards": false }, + { "method": "GET", "target": "/login", "forwards": false }, + { "method": "GET", "target": "/sign-up", "forwards": false }, + { "method": "GET", "target": "/signup", "forwards": false }, + { "method": "POST", "target": "/mcp", "forwards": false }, + { "method": "POST", "target": "/acme/mcp", "forwards": false }, + { "method": "GET", "target": "/acme", "forwards": false }, + { "method": "GET", "target": "/api/fd6fab1fbb4883e1", "forwards": false }, + { "method": "GET", "target": "/api/0123456789abcdef/e/", "forwards": false }, + { "method": "POST", "target": "/api/webhooks/workos", "forwards": false } + ] +} From 8d494a137d23f8ff4ea3f6af5877c5de5d4d0832 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:50:49 -0700 Subject: [PATCH 10/11] Test the analytics proxy root stays with v1 and update the pinned contract --- apps/cloud/src/edge/marketing.test.ts | 8 +- apps/cloud/src/edge/v2-edge-contract.json | 406 ++++++++++++++++++---- 2 files changed, 339 insertions(+), 75 deletions(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index a61f393524..a9761f578b 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -823,15 +823,9 @@ describe("v2EdgeResponse marketing", () => { new Request("https://executor.sh/api/fedcba9876543210/submit", { method: "POST" }), settings(service), ); - await v2EdgeResponse( - new Request("https://executor.sh/api/0123456789abcdef"), - settings(service), - ); - expect(received.map((request) => request.url)).toEqual([ "https://executor.sh/api/0123456789abcdef/e/?ip=1", "https://executor.sh/api/fedcba9876543210/submit", - "https://executor.sh/api/0123456789abcdef", ]); expect(received[0]?.method).toBe("POST"); expect(received[0]?.headers.has("cookie")).toBe(false); @@ -842,6 +836,8 @@ describe("v2EdgeResponse marketing", () => { // Another 16-hex root, and v1's own PostHog proxy (8 hex). "https://executor.sh/api/aaaaaaaaaaaaaaaa/e/", "https://executor.sh/api/aaaaaaaaaaaaaaaa/submit", + // The analytics proxy is forwarded below its root, not the root itself. + "https://executor.sh/api/0123456789abcdef", // The error tunnel is forwarded exactly: not its root or other subpaths. "https://executor.sh/api/fedcba9876543210", "https://executor.sh/api/fedcba9876543210/", diff --git a/apps/cloud/src/edge/v2-edge-contract.json b/apps/cloud/src/edge/v2-edge-contract.json index c6080f276f..b85420b8d5 100644 --- a/apps/cloud/src/edge/v2-edge-contract.json +++ b/apps/cloud/src/edge/v2-edge-contract.json @@ -1,5 +1,5 @@ { - "source": "UsefulSoftwareCo/executor-next apps/hosted/cloud/src/contracts/edge-contract.json, generated from edgeForwards in apps/hosted/cloud/src/contracts/edge-paths.ts", + "source": "Executor v2: apps/hosted/cloud/src/contracts/edge-contract.json, generated by apps/hosted/cloud/scripts/edge-contract.ts from productionEdgeForwards", "origin": "https://executor.sh", "oauthStatePrefix": "x2.", "telemetry": { @@ -32,76 +32,344 @@ "/og-image.png", "/pattern-graph-paper.svg", "/.well-known/agent-skills/*", - "/api/00e2e1f082a6ef17, /api/00e2e1f082a6ef17/*", + "/api/00e2e1f082a6ef17/*", "/api/fd6fab1fbb4883e1/submit" ], "cases": [ - { "method": "GET", "target": "/.well-known/oauth-authorization-server/api/auth", "forwards": true }, - { "method": "GET", "target": "/api/auth/callback/google?code=c&state=s", "forwards": true }, - { "method": "GET", "target": "/api/auth/callback/github", "forwards": true }, - { "method": "GET", "target": "/api/oauth/callback?code=c&state=x2.abc", "forwards": true }, - { "method": "GET", "target": "/git/acme/site.git/info/refs?service=git-upload-pack", "forwards": true }, - { "method": "POST", "target": "/git/acme/site.git/git-receive-pack", "forwards": true }, - { "method": "GET", "target": "/", "forwards": true }, - { "method": "GET", "target": "/home", "forwards": true }, - { "method": "GET", "target": "/pricing", "forwards": true }, - { "method": "GET", "target": "/pricing.md", "forwards": true }, - { "method": "GET", "target": "/index.md", "forwards": true }, - { "method": "GET", "target": "/setup-prompt.md", "forwards": true }, - { "method": "GET", "target": "/llms.txt", "forwards": true }, - { "method": "GET", "target": "/about-executor", "forwards": true }, - { "method": "GET", "target": "/google-workspace", "forwards": true }, - { "method": "GET", "target": "/blog", "forwards": true }, - { "method": "GET", "target": "/blog/a-post", "forwards": true }, - { "method": "GET", "target": "/docs", "forwards": true }, - { "method": "GET", "target": "/docs/quickstart", "forwards": true }, - { "method": "GET", "target": "/apps", "forwards": true }, - { "method": "GET", "target": "/apps/detail", "forwards": true }, - { "method": "GET", "target": "/experiments/hero/a", "forwards": true }, - { "method": "GET", "target": "/experiments/demo/posthog", "forwards": true }, - { "method": "GET", "target": "/_astro/page.abc123.js", "forwards": true }, - { "method": "GET", "target": "/authors/author.png", "forwards": true }, - { "method": "GET", "target": "/og-image.png", "forwards": true }, - { "method": "GET", "target": "/pattern-graph-paper.svg", "forwards": true }, - { "method": "GET", "target": "/.well-known/agent-skills/index.json", "forwards": true }, - { "method": "GET", "target": "/api/00e2e1f082a6ef17", "forwards": true }, - { "method": "POST", "target": "/api/00e2e1f082a6ef17/e/?ip=0", "forwards": true }, - { "method": "GET", "target": "/api/00e2e1f082a6ef17/static/array.js", "forwards": true }, - { "method": "POST", "target": "/api/fd6fab1fbb4883e1/submit", "forwards": true }, - - { "method": "GET", "target": "/api/auth/.well-known/openid-configuration", "forwards": false }, - { "method": "GET", "target": "/.well-known/oauth-authorization-server", "forwards": false }, - { "method": "GET", "target": "/.well-known/oauth-protected-resource", "forwards": false }, - { "method": "GET", "target": "/api/auth/callback?code=c&state=s", "forwards": false }, - { "method": "GET", "target": "/api/auth/callback/google/extra", "forwards": false }, - { "method": "GET", "target": "/api/auth/login", "forwards": false }, - { "method": "GET", "target": "/api/oauth/callback?code=c&state=abc", "forwards": false }, - { "method": "GET", "target": "/api/oauth/callback?code=c", "forwards": false }, - { "method": "GET", "target": "/oauth/client-metadata.json", "forwards": false }, - { "method": "GET", "target": "/oauth/client-id-metadata.json", "forwards": false }, - { "method": "GET", "target": "/git", "forwards": false }, - { "method": "GET", "target": "/gitlab/repo", "forwards": false }, - { "method": "GET", "target": "/demo", "forwards": false }, - { "method": "GET", "target": "/demo/posthog", "forwards": false }, - { "method": "GET", "target": "/experiments", "forwards": false }, - { "method": "GET", "target": "/privacy", "forwards": false }, - { "method": "GET", "target": "/terms", "forwards": false }, - { "method": "GET", "target": "/google-oauth", "forwards": false }, - { "method": "GET", "target": "/_v1-marketing/style.css", "forwards": false }, - { "method": "GET", "target": "/favicon-32.png", "forwards": false }, - { "method": "GET", "target": "/favicon-192.png", "forwards": false }, - { "method": "GET", "target": "/apple-touch-icon.png", "forwards": false }, - { "method": "GET", "target": "/favicon.ico", "forwards": false }, - { "method": "GET", "target": "/pricing/extra", "forwards": false }, - { "method": "GET", "target": "/home/extra", "forwards": false }, - { "method": "GET", "target": "/login", "forwards": false }, - { "method": "GET", "target": "/sign-up", "forwards": false }, - { "method": "GET", "target": "/signup", "forwards": false }, - { "method": "POST", "target": "/mcp", "forwards": false }, - { "method": "POST", "target": "/acme/mcp", "forwards": false }, - { "method": "GET", "target": "/acme", "forwards": false }, - { "method": "GET", "target": "/api/fd6fab1fbb4883e1", "forwards": false }, - { "method": "GET", "target": "/api/0123456789abcdef/e/", "forwards": false }, - { "method": "POST", "target": "/api/webhooks/workos", "forwards": false } + { + "method": "GET", + "target": "/.well-known/oauth-authorization-server/api/auth", + "forwards": true + }, + { + "method": "GET", + "target": "/api/auth/callback/google?code=c&state=s", + "forwards": true + }, + { + "method": "GET", + "target": "/api/auth/callback/github", + "forwards": true + }, + { + "method": "GET", + "target": "/api/oauth/callback?code=c&state=x2.abc", + "forwards": true + }, + { + "method": "GET", + "target": "/git/acme/site.git/info/refs?service=git-upload-pack", + "forwards": true + }, + { + "method": "POST", + "target": "/git/acme/site.git/git-receive-pack", + "forwards": true + }, + { + "method": "GET", + "target": "/", + "forwards": true + }, + { + "method": "GET", + "target": "/home", + "forwards": true + }, + { + "method": "GET", + "target": "/pricing", + "forwards": true + }, + { + "method": "GET", + "target": "/pricing.md", + "forwards": true + }, + { + "method": "GET", + "target": "/index.md", + "forwards": true + }, + { + "method": "GET", + "target": "/setup-prompt.md", + "forwards": true + }, + { + "method": "GET", + "target": "/llms.txt", + "forwards": true + }, + { + "method": "GET", + "target": "/about-executor", + "forwards": true + }, + { + "method": "GET", + "target": "/google-workspace", + "forwards": true + }, + { + "method": "GET", + "target": "/blog", + "forwards": true + }, + { + "method": "GET", + "target": "/blog/a-post", + "forwards": true + }, + { + "method": "GET", + "target": "/docs", + "forwards": true + }, + { + "method": "GET", + "target": "/docs/quickstart", + "forwards": true + }, + { + "method": "GET", + "target": "/apps", + "forwards": true + }, + { + "method": "GET", + "target": "/apps/detail", + "forwards": true + }, + { + "method": "GET", + "target": "/experiments/hero/a", + "forwards": true + }, + { + "method": "GET", + "target": "/experiments/demo/posthog", + "forwards": true + }, + { + "method": "GET", + "target": "/_astro/page.abc123.js", + "forwards": true + }, + { + "method": "GET", + "target": "/authors/author.png", + "forwards": true + }, + { + "method": "GET", + "target": "/og-image.png", + "forwards": true + }, + { + "method": "GET", + "target": "/pattern-graph-paper.svg", + "forwards": true + }, + { + "method": "GET", + "target": "/.well-known/agent-skills/index.json", + "forwards": true + }, + { + "method": "POST", + "target": "/api/00e2e1f082a6ef17/e/?ip=0", + "forwards": true + }, + { + "method": "GET", + "target": "/api/00e2e1f082a6ef17/static/array.js", + "forwards": true + }, + { + "method": "POST", + "target": "/api/fd6fab1fbb4883e1/submit", + "forwards": true + }, + { + "method": "GET", + "target": "/api/auth/.well-known/openid-configuration", + "forwards": false + }, + { + "method": "GET", + "target": "/.well-known/oauth-authorization-server", + "forwards": false + }, + { + "method": "GET", + "target": "/.well-known/oauth-protected-resource", + "forwards": false + }, + { + "method": "GET", + "target": "/api/auth/callback?code=c&state=s", + "forwards": false + }, + { + "method": "GET", + "target": "/api/auth/callback/google/extra", + "forwards": false + }, + { + "method": "GET", + "target": "/api/auth/login", + "forwards": false + }, + { + "method": "GET", + "target": "/api/oauth/callback?code=c&state=abc", + "forwards": false + }, + { + "method": "GET", + "target": "/api/oauth/callback?code=c", + "forwards": false + }, + { + "method": "GET", + "target": "/oauth/client-metadata.json", + "forwards": false + }, + { + "method": "GET", + "target": "/oauth/client-id-metadata.json", + "forwards": false + }, + { + "method": "GET", + "target": "/git", + "forwards": false + }, + { + "method": "GET", + "target": "/gitlab/repo", + "forwards": false + }, + { + "method": "GET", + "target": "/demo", + "forwards": false + }, + { + "method": "GET", + "target": "/demo/posthog", + "forwards": false + }, + { + "method": "GET", + "target": "/experiments", + "forwards": false + }, + { + "method": "GET", + "target": "/privacy", + "forwards": false + }, + { + "method": "GET", + "target": "/terms", + "forwards": false + }, + { + "method": "GET", + "target": "/google-oauth", + "forwards": false + }, + { + "method": "GET", + "target": "/_v1-marketing/style.css", + "forwards": false + }, + { + "method": "GET", + "target": "/favicon-32.png", + "forwards": false + }, + { + "method": "GET", + "target": "/favicon-192.png", + "forwards": false + }, + { + "method": "GET", + "target": "/apple-touch-icon.png", + "forwards": false + }, + { + "method": "GET", + "target": "/favicon.ico", + "forwards": false + }, + { + "method": "GET", + "target": "/pricing/extra", + "forwards": false + }, + { + "method": "GET", + "target": "/home/extra", + "forwards": false + }, + { + "method": "GET", + "target": "/login", + "forwards": false + }, + { + "method": "GET", + "target": "/sign-up", + "forwards": false + }, + { + "method": "GET", + "target": "/signup", + "forwards": false + }, + { + "method": "POST", + "target": "/mcp", + "forwards": false + }, + { + "method": "POST", + "target": "/acme/mcp", + "forwards": false + }, + { + "method": "GET", + "target": "/acme", + "forwards": false + }, + { + "method": "GET", + "target": "/api/00e2e1f082a6ef17", + "forwards": false + }, + { + "method": "GET", + "target": "/api/fd6fab1fbb4883e1", + "forwards": false + }, + { + "method": "POST", + "target": "/api/fd6fab1fbb4883e1/submit/extra", + "forwards": false + }, + { + "method": "GET", + "target": "/api/0123456789abcdef/e/", + "forwards": false + }, + { + "method": "POST", + "target": "/api/webhooks/workos", + "forwards": false + } ] } From cccbf8df67c19de92b57f8a04d407c3742b5c3e6 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:26:05 -0700 Subject: [PATCH 11/11] Test that slashed v2 pages redirect and update the pinned contract --- apps/cloud/src/edge/marketing.test.ts | 73 +++++++++++++++++++ apps/cloud/src/edge/production-config.test.ts | 25 +++++++ apps/cloud/src/edge/v2-edge-contract.json | 53 +++++++++++++- 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/edge/marketing.test.ts b/apps/cloud/src/edge/marketing.test.ts index a9761f578b..2fdaff85c3 100644 --- a/apps/cloud/src/edge/marketing.test.ts +++ b/apps/cloud/src/edge/marketing.test.ts @@ -8,6 +8,7 @@ import { marketingProxyRequest, parseV2Edge, v2EdgeResponse, + v2PageForSlashedPath, type V2EdgeEnv, type V2Service, } from "./marketing"; @@ -145,6 +146,18 @@ describe("isV2Path", () => { } }); +describe("v2PageForSlashedPath", () => { + it("canonicalizes only v2's exact pages", () => { + expect(v2PageForSlashedPath("/pricing/")).toBe("/pricing"); + expect(v2PageForSlashedPath("/google-workspace/")).toBe("/google-workspace"); + expect(v2PageForSlashedPath("/pricing")).toBeNull(); + expect(v2PageForSlashedPath("/pricing.md/")).toBeNull(); + expect(v2PageForSlashedPath("/blog/")).toBeNull(); + expect(v2PageForSlashedPath("/terms/")).toBeNull(); + expect(v2PageForSlashedPath("/")).toBeNull(); + }); +}); + describe("isSignUpPath", () => { it("claims /sign-up and /signup only", () => { expect(isSignUpPath("/sign-up")).toBe(true); @@ -689,6 +702,66 @@ describe("v2EdgeResponse marketing", () => { return { received, service }; }; + // A signed-out visitor at `/pricing/` would otherwise reach v1's sign-in + // gate, which redirects to login. + it("redirects v2's slashed exact pages to the page, query kept, without forwarding", async () => { + const { received, service } = recordingService(); + + for (const [target, location] of [ + ["/pricing/", "/pricing"], + ["/pricing/?ref=hn", "/pricing?ref=hn"], + ["/home/", "/home"], + ["/about-executor/", "/about-executor"], + ["/google-workspace/", "/google-workspace"], + ] as const) { + for (const method of ["GET", "HEAD"]) { + const response = await v2EdgeResponse( + new Request(`https://executor.sh${target}`, { + method, + headers: { cookie: "wos-session=sealed" }, + }), + settings(service), + ); + expect(response?.status, `${method} ${target}`).toBe(308); + expect(response?.headers.get("location"), `${method} ${target}`).toBe( + `https://executor.sh${location}`, + ); + } + } + expect(received).toHaveLength(0); + }); + + it("forwards the slashed form of a page v2 owns below, such as /blog/", async () => { + const { received, service } = recordingService(); + + const response = await v2EdgeResponse( + new Request("https://executor.sh/docs/"), + settings(service), + ); + + expect(await response?.text()).toBe("v2"); + expect(received[0]?.url).toBe("https://executor.sh/docs/"); + }); + + it("keeps deeper paths, other methods and v1's own slashed pages with v1", () => { + const { received, service } = recordingService(); + + for (const [method, target] of [ + ["GET", "/pricing/extra"], + ["GET", "/home/extra/"], + ["GET", "/pricing//"], + ["POST", "/pricing/"], + ["GET", "/terms/"], + ["GET", "/demo/"], + ] as const) { + expect( + v2EdgeResponse(new Request(`https://executor.sh${target}`, { method }), settings(service)), + `${method} ${target}`, + ).toBeNull(); + } + expect(received).toHaveLength(0); + }); + it("sends the signed-out homepage to v2 with the URL unchanged", async () => { const { received, service } = recordingService(); diff --git a/apps/cloud/src/edge/production-config.test.ts b/apps/cloud/src/edge/production-config.test.ts index c9dac4cc9f..700e3634ff 100644 --- a/apps/cloud/src/edge/production-config.test.ts +++ b/apps/cloud/src/edge/production-config.test.ts @@ -77,6 +77,10 @@ const EdgeContract = Schema.Struct({ cases: Schema.Array( Schema.Struct({ method: Schema.String, target: Schema.String, forwards: Schema.Boolean }), ), + slashRedirects: Schema.Struct({ + pages: Schema.Array(Schema.String), + cases: Schema.Array(Schema.Struct({ target: Schema.String, location: Schema.String })), + }), }); const contract = Schema.decodeUnknownSync(Schema.fromJsonString(EdgeContract))( readFileSync(fileURLToPath(new URL("./v2-edge-contract.json", import.meta.url)), "utf8"), @@ -134,6 +138,27 @@ describe("v2's edge contract", () => { expect(signUps.map((c) => c.target).toSorted()).toEqual(["/sign-up", "/signup"]); }); + // A slashed exact page is not forwarded: the edge redirects it to the page, + // which is. Every page v2 lists has an example. + it("covers every slashed page v2 lists", () => { + expect( + contract.slashRedirects.cases + .map((c) => new URL(c.target, contract.origin).pathname) + .toSorted(), + ).toEqual(expect.arrayContaining(contract.slashRedirects.pages.map((page) => `${page}/`))); + }); + + for (const { target, location } of contract.slashRedirects.cases) { + for (const method of ["GET", "HEAD"]) { + it(`redirects ${method} ${target} to ${location} without forwarding`, async () => { + const { calls, response } = await runCase(method, target); + expect(calls).toHaveLength(0); + expect(response?.status).toBe(308); + expect(response?.headers.get("location")).toBe(`${contract.origin}${location}`); + }); + } + } + for (const { method, target } of signUps) { it(`redirects ${method} ${target} to v2's sign-up page without forwarding`, async () => { const { calls, response } = await runCase(method, target); diff --git a/apps/cloud/src/edge/v2-edge-contract.json b/apps/cloud/src/edge/v2-edge-contract.json index b85420b8d5..8ad182e0e2 100644 --- a/apps/cloud/src/edge/v2-edge-contract.json +++ b/apps/cloud/src/edge/v2-edge-contract.json @@ -116,6 +116,11 @@ "target": "/blog", "forwards": true }, + { + "method": "GET", + "target": "/blog/", + "forwards": true + }, { "method": "GET", "target": "/blog/a-post", @@ -126,6 +131,11 @@ "target": "/docs", "forwards": true }, + { + "method": "GET", + "target": "/docs/", + "forwards": true + }, { "method": "GET", "target": "/docs/quickstart", @@ -316,6 +326,21 @@ "target": "/home/extra", "forwards": false }, + { + "method": "GET", + "target": "/about-executor/extra", + "forwards": false + }, + { + "method": "GET", + "target": "/google-workspace/extra", + "forwards": false + }, + { + "method": "POST", + "target": "/pricing/", + "forwards": false + }, { "method": "GET", "target": "/login", @@ -371,5 +396,31 @@ "target": "/api/webhooks/workos", "forwards": false } - ] + ], + "slashRedirects": { + "rule": "GET or HEAD / answers 308 to , query kept, for each of pages", + "pages": ["/home", "/pricing", "/about-executor", "/google-workspace"], + "cases": [ + { + "target": "/pricing/", + "location": "/pricing" + }, + { + "target": "/pricing/?ref=hn", + "location": "/pricing?ref=hn" + }, + { + "target": "/home/", + "location": "/home" + }, + { + "target": "/about-executor/", + "location": "/about-executor" + }, + { + "target": "/google-workspace/", + "location": "/google-workspace" + } + ] + } }