611 lines · 31.1 KB · TypeScript
RawDownload
import { DurableObject } from "cloudflare:workers";import {  SpanKind,  SpanStatusCode,  context,  trace,  type Span,  type SpanContext,} from "@opentelemetry/api";import {  ATTR_HTTP_REQUEST_METHOD,  ATTR_HTTP_RESPONSE_STATUS_CODE,  ATTR_URL_FULL,  ATTR_URL_PATH,  ATTR_URL_SCHEME,} from "@opentelemetry/semantic-conventions";import * as Sentry from "@sentry/cloudflare"; import { isAppOwnedPath, servedByAppPlane } from "./app-paths";import { marketingProxyRequest } from "./edge/marketing";import { passthroughResponse } from "./edge/passthrough";import { classifyMcpPath, prepareMcpOrgScope } from "./mcp/mount";import { McpSessionDOSqlite as McpSessionDOBase } from "./mcp/session-durable-object";import { parseTraceparent } from "./mcp/traceparent";import {  cloudSentryOptions,  captureCause,  otelCorrelationContextFromOpenTelemetrySpan,  SENTRY_EVENT_ID_ATTRIBUTE,  tagCurrentSentryScopeWithOtelContext,} from "./observability";import { browserTracesResponse } from "./observability/browser-traces";import { flushTracerProvider, installTracerProvider } from "./observability/telemetry"; // ---------------------------------------------------------------------------// Durable Object — wrapped with Sentry so DO errors land in Sentry (inits the// client inside the DO isolate, which plain `Sentry.captureException` cannot// do on its own). OTEL is installed through Effect layers (observability/telemetry),// not a global fetch wrapper.//// This one class is the last heavy STATIC edge out of the Worker entry, and it// is deliberate. Cloudflare requires a DO class to be a top-level export, so// the only way to defer the module is the lazy-shim pattern used for// `ExecutionRateLimiterDO` below: a plain `DurableObject` subclass that// dynamically imports the real class and forwards every entry point to a real// instance built from the same ctx/env. That is safe for a two-method counter.// It is NOT safe here, and the reason is the size of the surface that would// have to be forwarded by hand:////   - the native handlers (`fetch`, `alarm`, `webSocketMessage`,//     `webSocketClose`, `webSocketError`);//   - partyserver's `setName`, which `getAgentByName` RPCs on the stub before//     returning it, so missing it breaks EVERY session lookup;//   - roughly fifteen internal, `@internal`-marked RPC methods the agents SDK//     calls straight on the stub from the Worker isolate//     (`getInitializeRequest`, `setInitializeRequest`, `getStreamRequestIds`,//     `setStreamRequestIds`, `deleteStreamRequestIds`,//     `getStaleEpochStreamRequestIds`, `getUndeliveredStreamIds`,//     `markStreamUndelivered`, `getStreamForRequestId`, `getWebSocket`,//     `getConnections`, `getSessionId`, `onSSEMcpMessage`, `handleMcpMessage`,//     `_cf_scheduleDestroy`, `_cf_initAsFacet`, `__unsafe_ensureInitialized`);//   - this app's own RPC surface (`validateMcpSessionOwner`,//     `requestCapEviction`, `getPausedExecutionForApproval`,//     `resumeExecutionForApproval`, `resumeExecutionForModel`).//// That list is not a public contract. A missed or newly added method fails at// the CALL SITE with "not a function", in production, on one MCP code path —// nothing here or in the type checker would catch it, because stub calls are// dynamic. And `transport: "streamable-http"` still bridges through a// hibernatable WebSocket into the DO (the agents SDK fetches the DO with an// `Upgrade: websocket` header), so the shim would also sit on the hibernation// wake path.//// Measured trade: making this lazy takes the startup closure from 5.25 MB to// 4.20 MB (`node scripts/start-closure.mjs dist/server`) — 1.05 MB, well short// of the ~2 MB it was expected to be, because most of the DO's dependencies are// shared chunks the entry reaches by other static edges anyway. Deferring the// MCP *handler* below already removes the part that can be removed safely. If// the remaining 1.05 MB is worth having, the structural fix is to move the// session DO to its own Worker script (`durable_objects.bindings[].script_name`)// rather than to hand-maintain a mirror of another package's internal RPC// surface.// --------------------------------------------------------------------------- export const McpSessionDOSqlite = Sentry.instrumentDurableObjectWithSentry(  cloudSentryOptions,  McpSessionDOBase,); // Orphaned placeholder for the original key-value `McpSessionDO` class (migration// v1). The live MCP session DO is now `McpSessionDOSqlite` (SQLite); the// `MCP_SESSION` binding moved to it. Cloudflare won't delete `McpSessionDO` in the// same deploy that moves its binding, so the class is left unbound and is kept// exported here only to satisfy the migration. It can be removed in a later deploy// (with a `deleted_classes: ["McpSessionDO"]` migration) now that nothing binds it.export class McpSessionDO extends DurableObject {} // Per-org execution rate-limit counter DO (abuse backstop; migration v3,// `EXECUTION_RATE_LIMITER` binding). Plain counter, no Sentry wrapper needed:// its callers already fail open and report errors themselves.//// Exported as a LAZY SHIM rather than a re-export. Cloudflare requires a DO// class to be a top-level export of the entry module, and a static re-export// pulls the whole `engine/execution-rate-limit` chunk (~416 KB after Rollup// co-locates autumn-js with it) into every cold isolate's startup closure —// including the overwhelming majority that never touch this counter. The real// class is a two-entry-point counter (`increment` RPC and the purge `alarm`)// over `ctx.storage`, with only one in-memory field, so delegating to a real// instance built from the SAME ctx/env is exactly equivalent: the instance is// memoized per DO instance, which is the same lifetime the field had before.let executionRateLimiterModule: Promise<typeof import("./engine/execution-rate-limit")> | undefined; export class ExecutionRateLimiterDO extends DurableObject<Env> {  private real:    | Promise<InstanceType<typeof import("./engine/execution-rate-limit").ExecutionRateLimiterDO>>    | undefined;   private delegate() {    executionRateLimiterModule ??= import("./engine/execution-rate-limit");    this.real ??= executionRateLimiterModule.then(      (module) => new module.ExecutionRateLimiterDO(this.ctx, this.env),    );    return this.real;  }   /** Add one execution to `windowId`'s counter and return the new count. */  async increment(windowId: number): Promise<number> {    return (await this.delegate()).increment(windowId);  }   override async alarm(): Promise<void> {    await (await this.delegate()).alarm();  }} export { McpExecutionOwnerDirectoryDO } from "@executor-js/cloudflare/mcp/execution-owner-directory"; // ---------------------------------------------------------------------------// Worker fetch handler//// We open a single `http.server <METHOD>` span at the worker boundary using// the same WebTracerProvider that `observability/telemetry.ts` already installs for// Effect-driven spans. This restores the per-request envelope span that was// previously emitted by `@microlabs/otel-cf-workers` and lost in the alchemy// migration — without the OTel-SDK version-conflict that package would now// drag in (it pins `@opentelemetry/otlp-* ^0.200.0`, we ship ^0.214.0).//// Almost nothing is reachable from this entry by a STATIC import any more. The// Start server entry, the Effect app plane, the MCP agent handler, the rate-limit// counter DO and the WorkOS events cron runner are all behind dynamic imports// and memoized per isolate, so a cold isolate evaluates only the code the path// it is about to serve actually needs. The MCP session DO is the one deliberate// exception (see the note on its export above).//// App-owned paths (/api/* and /.well-known/* — see app-paths.ts) get their// `http.server` span from Effect's HttpMiddleware tracer. `/mcp` is dispatched// directly and uses `traceCloudMcpRequest` below so the agent handler can skip// the entire span envelope for negative-cache hits. Other paths keep this// worker span. Wrapping Effect-owned paths here too produced duplicate sibling// spans per request.//// SimpleSpanProcessor exports synchronously at span end but the underlying// `fetch()` to Axiom is fire-and-forget; the Worker may terminate before it// completes. `ctx.waitUntil(flushTracerProvider())` keeps the isolate alive// until the in-flight export resolves.// --------------------------------------------------------------------------- // The Start server entry is imported LAZILY, memoized per isolate. Statically// it added ~832 KB (react-dom, the router and their transitive graph) to the// startup closure of EVERY cold isolate — including the many that only ever// serve `/api` or `/mcp` and return long before `fetchHandler` is reached.// The module still resolves on the first page request, so the work is moved,// not removed; it is just no longer charged to isolates that never page-serve.type StartFetchHandler = (  request: Request,  env: Env,  ctx: ExecutionContext,) => Response | Promise<Response>; let startServerEntry: Promise<StartFetchHandler> | undefined; const loadStartFetchHandler = (): Promise<StartFetchHandler> => {  startServerEntry ??= import("@tanstack/react-start/server-entry").then(    (module) => module.default.fetch as StartFetchHandler,  );  return startServerEntry;}; /** * Every entry into TanStack Start goes through here so `startGraphEntered` * reflects whether this isolate has already paid the lazy `loadEntries` * import — the cost that dominates a cold page request. The flag is still set * synchronously on entry, before the server-entry import is awaited, so its * meaning ("something has driven Start in this isolate") is unchanged. */const fetchHandler = async (  request: Request,  env: Env,  ctx: ExecutionContext,): Promise<Response> => {  markStartGraphEntered();  const rawFetchHandler = await loadStartFetchHandler();  return rawFetchHandler(request, env, ctx);}; const tracer = trace.getTracer("executor-cloud-worker"); const traceparentValueFor = (spanContext: SpanContext): string =>  `00-${spanContext.traceId}-${spanContext.spanId}-${(spanContext.traceFlags & 0xff).toString(16).padStart(2, "0")}`; const withTraceparent = (request: Request, spanContext: SpanContext): Request => {  const headers = new Headers(request.headers);  headers.set("traceparent", traceparentValueFor(spanContext));  return new Request(request, { headers });}; const traceCloudMcpRequest = async (  request: Request,  _env: Env,  ctx: ExecutionContext,  handle: (tracedRequest: Request) => Promise<Response>,): Promise<Response> => {  if (!installTracerProvider()) return handle(request);   const url = new URL(request.url);  const inbound = parseTraceparent(request.headers.get("traceparent"), null);  const parentContext = inbound    ? trace.setSpanContext(context.active(), {        traceId: inbound.traceId,        spanId: inbound.spanId,        traceFlags: inbound.traceFlags,        isRemote: true,      })    : context.active();   return tracer.startActiveSpan(    `http.server ${request.method}`,    { kind: SpanKind.SERVER },    parentContext,    async (span) => {      span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);      span.setAttribute(ATTR_URL_FULL, request.url);      span.setAttribute(ATTR_URL_PATH, url.pathname);      span.setAttribute(ATTR_URL_SCHEME, url.protocol.replace(/:$/, ""));      // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; observe response/error for span status, keep trace export alive after the Agents bridge resolves or rejects      try {        const response = await handle(withTraceparent(request, span.spanContext()));        span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);        if (response.status >= 500) {          span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${response.status}` });        }        return response;      } catch (err) {        // oxlint-disable-next-line executor/no-instanceof-error, executor/no-unknown-error-message -- adapter boundary: Cloudflare's fetch callback throws untyped; normalized only for the OTel span record, the original error is rethrown below        const cause = err instanceof Error ? err : String(err);        span.recordException(cause);        // oxlint-disable-next-line executor/no-unknown-error-message -- adapter boundary: same normalization as the recordException line above        const message = typeof cause === "string" ? cause : cause.message;        span.setStatus({ code: SpanStatusCode.ERROR, message });        // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; preserve original error to Cloudflare runtime        throw err;      } finally {        span.end();        ctx.waitUntil(flushTracerProvider());      }    },  );}; // Built on the first /mcp request and memoized per isolate. Constructing it// eagerly at module scope meant every cold isolate — including page- and// API-only ones — evaluated the agents SDK, the MCP SDK and the session DO's// whole dependency graph before it could answer anything.type CloudMcpAgentHandler = ReturnType<  typeof import("./mcp/agent-handler").makeCloudMcpAgentHandler>; let mcpAgentHandler: Promise<CloudMcpAgentHandler> | undefined; const getMcpAgentHandler = (): Promise<CloudMcpAgentHandler> => {  mcpAgentHandler ??= import("./mcp/agent-handler").then((module) =>    module.makeCloudMcpAgentHandler(),  );  return mcpAgentHandler;}; // ---------------------------------------------------------------------------// Isolate lifecycle signals// ---------------------------------------------------------------------------//// The Aug 2026 page-latency hunt kept stalling on one blind spot: every span we// emit starts INSIDE the fetch handler, so nothing could distinguish "this// isolate is cold and paying Start's lazy `loadEntries` import" from "this// isolate came up slowly before our code ran at all". Both look like one slow// span. These three attributes make the distinction queryable:////   executor.isolate.request_seq  - 1 means this request is the isolate's//                                   first; page requests measured 1.04 per//                                   isolate, which is why nearly every one//                                   pays the cold cost.//   executor.start_graph.entered  - whether anything had already driven//                                   TanStack Start in this isolate. /mcp and//                                   the passthrough proxies return before//                                   `fetchHandler`, so an isolate can serve//                                   plenty of traffic and still be cold here.// Measured with a temporary entry-lag probe correlated against `wrangler tail`// event timestamps: a cold isolate (`request_seq` 1) spends **840-1844ms**// coming up before our first line runs, while a reused one spends **0-2ms**.// That startup is invisible to every span we emit, and it sits on top of the// ~3.1s `loadEntries` import — together the 3-5s a signed-in page costs.////   executor.isolate.id           - identifies the isolate itself, so reuse can//                                   be counted directly instead of inferred.//   executor.isolate.age_ms       - ms since this isolate served its first//                                   request.//// The last two exist because the Aug 2026 hunt inferred "isolates stopped being// reused" from a latency cutoff (requests slower than 1s were called cold) and// then built a size-based theory on top of that proxy. The theory was wrong:// reverting the offending packages restored production while moving the// evaluated module closure by 0.02 MB (see scripts/start-closure.mjs). Grouping// by isolate id answers "how many requests did this isolate serve, and were the// slow ones its first?" directly, which no latency threshold can.//// All of it is cheap: two increments, one lazy uuid, and no I/O.let isolateRequestSeq = 0;let startGraphEntered = false;// Minted on first request rather than at module scope: Workers reject random// number generation during global-scope evaluation.let isolateId: string | undefined;let isolateFirstSeenAt = 0; const identifyIsolate = (): { readonly id: string; readonly ageMs: number } => {  if (isolateId === undefined) {    isolateId = crypto.randomUUID();    isolateFirstSeenAt = Date.now();  }  return { id: isolateId, ageMs: Date.now() - isolateFirstSeenAt };}; const markStartGraphEntered = (): void => {  startGraphEntered = true;}; // ---------------------------------------------------------------------------// Serving `/api/*` without entering TanStack Start.// ---------------------------------------------------------------------------//// Everything under `/api` is the Effect app (`ExecutorApp.make`'s web handler)// and uses no part of the router, React, or SSR. But it was dispatched from a// Start *request middleware*, so reaching it meant paying Start's lazy// `loadEntries` import of the whole server graph first. Measured on production// 2026-08-19, splitting `/api/*` by whether the isolate had already loaded that// graph: warm p50 **186ms**, cold p50 **2129ms**, with 28% of API requests cold.// The dashboard fires many `/api/*` calls in parallel and waits for the slowest,// so that cold tail is what the app actually feels like.//// So `/api` joins marketing, `/docs`, the PostHog proxy and `/mcp` at the Worker// entry: classify and dispatch before anything touches Start. The evaluated// closure for an API request drops from the full Start graph to the Worker's own// (see `scripts/start-closure.mjs`).//// `servedByAppPlane` (./app-paths) decides which paths qualify — two under// `/api` are claimed by Start's middleware first and must keep their old route. // Instantiated on the first request that needs it and memoized per isolate,// mirroring `start.ts`'s `getApp`. The import stays dynamic so an isolate that// only serves pages or proxies never evaluates the app graph at all.let appPlane: ReturnType<typeof import("./app").cloudApiHandler> | undefined;let appGraphEntered = false; const getAppPlane = async (): Promise<NonNullable<typeof appPlane>> => {  if (appPlane === undefined) {    const { cloudApiHandler } = await import("./app");    appPlane = cloudApiHandler();    appGraphEntered = true;  }  return appPlane;}; /** * `getAppPlane()` with the cost of its first evaluation recorded on the * dispatch span as `executor.dispatch.graph_import_ms`. * * Why the throwaway cache lookup: workerd FREEZES `Date.now()` until the * isolate performs real I/O, so a measurement taken across purely synchronous * module evaluation reads 0 and the whole cold app-graph cost silently lands on * whatever span happens to straddle the NEXT await that does I/O. The Sep 2026 * investigation chased exactly that ghost: seconds of cold graph evaluation * were being attributed to `workos.session.local_verify`, the first I/O in an * authenticated request. One trivial cache probe before the import unfreezes * the clock so both this attribute and the isolate's cold cost become visible. * * It is done ONLY on a cold app-plane dispatch (once per isolate), so the warm * path — every subsequent request — pays nothing. */const measuredAppPlane = async (  span: Span,  cold: boolean,): Promise<NonNullable<typeof appPlane>> => {  if (!cold) return getAppPlane();  // `caches.default` is a Workers extension the ambient `CacheStorage` type  // does not carry; the lookup is a miss by construction and nothing is stored.  const workerCaches = caches as CacheStorage & { readonly default: Cache };  await workerCaches.default.match(new Request("https://executor.internal/clock"));  const startedAt = Date.now();  const plane = await getAppPlane();  span.setAttribute("executor.dispatch.graph_import_ms", Date.now() - startedAt);  return plane;}; const cloudflareHandler: ExportedHandler<Env> = {  fetch: async (request, env, ctx) => {    isolateRequestSeq += 1;     // Public pages must not enter TanStack Start: its first-request dynamic    // import loads the entire React + Effect server graph and can take seconds    // on a cold isolate. Classify and service-bind marketing at the Worker    // entry, before telemetry or fetchHandler touches that graph.    const marketingRequest = marketingProxyRequest(request);    const marketing: Fetcher | undefined = env.MARKETING;    if (marketingRequest && marketing) return marketing.fetch(marketingRequest);     // Same reasoning, same seam: `/docs` and the PostHog proxy forward to an    // external origin and never touch the router, React, or the Effect app.    // Left in Start's middleware they still paid its lazy `loadEntries` import    // 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.    const passthroughPath = new URL(request.url).pathname;    const passthrough = passthroughResponse(request, passthroughPath);    if (passthrough) return passthrough;     // Browser OTLP ingress — before the server span opens: exporter traffic    // must never trace itself (the browser already excludes /v1/traces from    // its own tracing for the same reason).    const browserTraces = browserTracesResponse(request, env);    if (browserTraces) return browserTraces;    // The MCP dispatch is classified up front, independent of whether    // telemetry installs — an unset `AXIOM_TOKEN` (tracer not installed) must    // never take /mcp requests down with it. See `installTracerProvider`'s    // early return below: the handler invokes it for uncached MCP traffic, and    // this entry invokes it for non-MCP paths.    const url = new URL(request.url);    const mcpRoute = classifyMcpPath(url.pathname);    if (mcpRoute?.kind === "mcp") {      // The Cloudflare Agents MCP bridge needs the platform ExecutionContext      // to pass authenticated session props into the hibernatable DO.      // Discovery docs still flow through the app-level MCP envelope.      const forwarded = prepareMcpOrgScope(request);      // /mcp leaves the Effect app for the Agents bridge, so no downstream      // HttpMiddleware.tracer opens the request envelope — this worker span is      // THE `http.server` span for MCP traffic, and its context is stamped onto      // the forwarded traceparent so the agent handler and session DO parent      // under it instead of exporting orphaned roots.      return traceCloudMcpRequest(forwarded, env, ctx, async (tracedRequest) =>        (await getMcpAgentHandler())(tracedRequest, env, ctx),      );    }    const tracingInstalled = installTracerProvider();    // Join the caller's W3C trace when the request carries one — the web UI    // sends traceparent on every API fetch, so the browser's spans and this    // request share one trace id end to end. Same parsing the DO path does    // in session-durable-object.ts.    const inbound = parseTraceparent(request.headers.get("traceparent"), null);    const parentContext = inbound      ? trace.setSpanContext(context.active(), {          traceId: inbound.traceId,          spanId: inbound.spanId,          traceFlags: inbound.traceFlags,          isRemote: true,        })      : context.active();    if (!tracingInstalled) {      return fetchHandler(request, env, ctx);    }    // Effect-served paths bring their own http.server span (with traceparent    // join) — a second SERVER span here would duplicate it (the header note).    // What they do NOT cover is the time between this invocation starting and    // the Effect router opening its span (Start dispatch, middleware, lazy    // module graph): during the Aug 2026 regression that gap was seconds of    // invisible wall time. `worker.dispatch` is an INTERNAL parent that    // brackets the whole invocation; Effect's http.server span joins under it    // via the injected traceparent, so gap = dispatch minus server span.    if (isAppOwnedPath(url.pathname)) {      return tracer.startActiveSpan(        "worker.dispatch",        { kind: SpanKind.INTERNAL },        parentContext,        async (span) => {          span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);          span.setAttribute(ATTR_URL_PATH, url.pathname);          const isolate = identifyIsolate();          span.setAttribute("executor.isolate.request_seq", isolateRequestSeq);          span.setAttribute("executor.start_graph.entered", startGraphEntered);          span.setAttribute("executor.isolate.id", isolate.id);          span.setAttribute("executor.isolate.age_ms", isolate.ageMs);          // Which plane served this: "app" skipped the Start graph entirely, so          // `start_graph.entered` says nothing about it. `app_graph.entered`          // is the app-plane analogue - false means this request paid for the

Lines 1–500 of 611.