{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "executor-cloud",
  "compatibility_date": "2025-04-01",
  "compatibility_flags": ["nodejs_compat"],
  "main": "src/server.ts",
  // The vite plugin fills in assets.directory (dist/client); the binding must
  // be declared here or env.ASSETS does not exist at runtime.
  "assets": {
    "binding": "ASSETS",
  },
  "routes": [
    {
      "pattern": "executor.sh",
      "custom_domain": true,
    },
  ],
  "limits": {
    "cpu_ms": 30000,
  },
  // Every minute: replay the WorkOS Events API into the membership mirror
  // (`scheduled` in src/server.ts → auth/workos-events-sync.ts). Changes made
  // in the WorkOS dashboard reach the mirror within this interval; the
  // signed webhook at /api/webhooks/workos shortens it to seconds.
  "triggers": {
    "crons": ["* * * * *"],
  },
  "observability": {
    "enabled": true,
    "redact_query_string": true,
  },
  "ratelimits": [
    {
      "name": "ADMIN_MFA_RATE_LIMITER",
      "namespace_id": "1001",
      "simple": { "limit": 5, "period": 60 },
    },
  ],
  // Script-level logpush feeds the account's workers_trace_events Logpush job
  // (invocation logs, outcomes like exceededMemory, console output) into
  // Axiom. Pinned here because the setting lives on the script: a deploy that
  // omits it can reset it to false, which is how the export silently died in
  // the July deploy-tooling migration.
  // Account-level output fields are configured separately via logpush.json;
  // see docs/platform-logs.md.
  "logpush": true,
  "durable_objects": {
    "bindings": [
      {
        "name": "MCP_SESSION",
        "class_name": "McpSessionDOSqlite",
      },
      {
        "name": "MCP_EXECUTION_OWNER",
        "class_name": "McpExecutionOwnerDirectoryDO",
      },
      // Per-org execution rate-limit counter (abuse backstop). One instance
      // per organization via idFromName(orgId); a single { windowId, count }
      // record per instance, purged by alarm after inactivity.
      {
        "name": "EXECUTION_RATE_LIMITER",
        "class_name": "ExecutionRateLimiterDO",
      },
    ],
  },
  // The MCP session DO moved to the Cloudflare Agents (`McpAgent`) base, which
  // stores state in SQLite. The original `McpSessionDO` was created on the
  // key-value backend (`new_classes`) and cannot be converted in place. Cloudflare
  // also refuses to delete a class in the same deploy that moves its binding (it
  // validates the delete against the live binding), so v2 only CREATES the new
  // SQLite class `McpSessionDOSqlite` and the `MCP_SESSION` binding moves to it.
  // The old KV `McpSessionDO` is left orphaned (unbound, kept as a stub export in
  // server.ts so the migration stays valid); it can be deleted in a later deploy
  // now that nothing binds it. Session state is ephemeral, so nothing is lost.
  "migrations": [
    {
      "tag": "v1",
      "new_classes": ["McpSessionDO"],
    },
    {
      "tag": "v2",
      "new_sqlite_classes": ["McpSessionDOSqlite"],
    },
    {
      "tag": "v3",
      "new_sqlite_classes": ["ExecutionRateLimiterDO"],
    },
    {
      "tag": "v4",
      "new_sqlite_classes": ["McpExecutionOwnerDirectoryDO"],
    },
  ],
  "services": [
    // v1's marketing worker. On executor.sh it now serves only v1's terms of
    // 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",
    },
    // v2's unplaced marketing Worker (the executor-next `v2` stage, same account). The edge
    // (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
      // this binding; this is the only place v1 names it.
      "service": "executor-next-marketing-v2",
    },
    // v2's MCP Worker. The MCP requests of an organization that has moved to
    // v2 are forwarded to it (src/mcp/v2-forward.ts) with a credential signed
    // by the V2_MCP_FORWARD_SECRET secret, which v2 shares. Like the binding
    // above, the name must equal the one v2's deployment pins.
    {
      "binding": "V2_MCP",
      "service": "executor-next-hosted-api-v2-qs32brgjwvt7ytx4",
    },
  ],
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "ca5f68119e4e4277b2ad6298a9048875",
      "localConnectionString": "postgresql://postgres:postgres@127.0.0.1:5433/postgres",
    },
  ],
  // Plugin blob seam backend (multi-MB resolved specs). Bucket must exist
  // before deploy: `wrangler r2 bucket create executor-cloud-blobs`.
  "r2_buckets": [
    {
      "binding": "BLOBS",
      "bucket_name": "executor-cloud-blobs",
    },
  ],
  // Pins the Worker to the Hyperdrive/Postgres region. Added in #420 to fix
  // MCP Hyperdrive connection pressure; dropped in #1693 as a placement
  // experiment, which took `user_store.*` from ~12ms to ~300ms because every
  // query started crossing regions. Restored to measure that against the
  // JWKS verify time the experiment also moved.
  "placement": {
    "region": "aws:us-east-1",
  },
  "worker_loaders": [
    {
      "binding": "LOADER",
    },
  ],
  // Stamps spans (`service.version`) and mem-metrics snapshots with the
  // running Worker version id, so an Axiom step-change maps to the exact
  // deploy without Cloudflare API archaeology. memory-metrics.ts already
  // reads this binding; it was never declared before, so it always fell back.
  "version_metadata": {
    "binding": "CF_VERSION_METADATA",
  },
  "vars": {
    "VITE_PUBLIC_SITE_URL": "https://executor.sh",
    // 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.
    "V2_OAUTH_STATE_PREFIX": "x2.",
    // Origin of v2's MCP endpoint; moved organizations' requests go to
    // `<origin>/mcp` through the V2_MCP binding. The signing secret,
    // V2_MCP_FORWARD_SECRET, is a Worker secret (`wrangler secret put`).
    "V2_MCP_ORIGIN": "https://mcp.executor.sh",
    // 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 below it (`<root>/*`), 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).
    // 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
    // end-to-end short of waiting for a real error. The endpoint only fires
    // when its exact path is requested and the events are explicitly tagged
    // synthetic.
    "SENTRY_OTEL_VERIFY": "true",
    "VITE_PUBLIC_POSTHOG_KEY": "phc_nNLrNMALpRsfrEkZovUkfMxYbcJvHnsJHeoSPavprgLL",
    // Browser OTLP spans → same-origin, forwarded to Axiom by the worker
    // (src/observability/browser-traces.ts). Relative on purpose: the
    // client resolves it against the page origin, so previews work too.
    "VITE_PUBLIC_OTLP_TRACES_URL": "/v1/traces",
    // Per-session sampling: 1 = every signed-in session exports. Dial down
    // (e.g. "0.1") if Axiom volume becomes a cost concern.
    "VITE_PUBLIC_OTLP_SAMPLE_RATIO": "1",
  },
}
