Unified · Split
| @@ -7,9 +7,10 @@ streaming sessions: a cutover mid-call drops in-flight MCP tool results. Staging · 6 unmodified lines | |||
| 7 | the promotion bounds that blast radius and keeps a mere merge from disrupting | 7 | the promotion bounds that blast radius and keeps a mere merge from disrupting |
| 8 | anything. | 8 | anything. |
| 9 | 9 | ||
| 10 | The mechanics below were validated empirically on a throwaway lab worker | 10 | The mechanics below were validated empirically on a throwaway staging worker |
| 11 | (`executor-cloud-deploy-lab`, see `scripts/deploy-lab/`); the "what happens" | 11 | that mounted the real session DO and patched transport with stub auth; the |
| 12 | claims are measured, not assumed. The findings are summarized at the end. | 12 | "what happens" claims are measured, not assumed. The findings are summarized |
| 13 | at the end. | ||
| 13 | 14 | ||
| 14 | ## The flow | 15 | ## The flow |
| 15 | 16 | ||
| @@ -66,7 +67,7 @@ The script promotes exactly the version you name (`latest` resolves to the · 50 unmodified lines | |||
| 66 | highest version number) and holds the remainder of a partial promotion on the | 67 | highest version number) and holds the remainder of a partial promotion on the |
| 67 | current-live version. It refuses to run unless the resolved wrangler config | 68 | current-live version. It refuses to run unless the resolved wrangler config |
| 68 | targets the `executor-cloud` worker (set `PROMOTE_CLOUD_ALLOW_NAME` to override | 69 | targets the `executor-cloud` worker (set `PROMOTE_CLOUD_ALLOW_NAME` to override |
| 69 | for a non-production worker such as the deploy lab). | 70 | for a non-production worker). |
| 70 | 71 | ||
| 71 | ## Session version-affinity (zone ruleset, MUST be configured manually) | 72 | ## Session version-affinity (zone ruleset, MUST be configured manually) |
| 72 | 73 | ||
| @@ -104,7 +105,7 @@ session to pin yet. Once the client echoes `mcp-session-id`, every subsequent · 31 unmodified lines | |||
| 104 | request for that session is version-pinned to the worker version its DO landed | 105 | request for that session is version-pinned to the worker version its DO landed |
| 105 | on. | 106 | on. |
| 106 | 107 | ||
| 107 | Validated on the lab worker by sending `Cloudflare-Workers-Version-Key` | 108 | Validated on the staging worker by sending `Cloudflare-Workers-Version-Key` |
| 108 | directly (12 requests per key across a 4-key set during a two-version gradual | 109 | directly (12 requests per key across a 4-key set during a two-version gradual |
| 109 | deployment): each distinct key deterministically mapped to exactly one version | 110 | deployment): each distinct key deterministically mapped to exactly one version |
| 110 | across all its requests, while unkeyed requests split across versions per the | 111 | across all its requests, while unkeyed requests split across versions per the |
| @@ -150,7 +151,7 @@ Constraints: · 39 unmodified lines | |||
| 150 | 151 | ||
| 151 | ## What the staged flow does and does not fix | 152 | ## What the staged flow does and does not fix |
| 152 | 153 | ||
| 153 | Measured on the lab worker: | 154 | Measured on the staging worker: |
| 154 | 155 | ||
| 155 | - **Fixed, merges no longer disrupt anything.** `wrangler versions upload` does | 156 | - **Fixed, merges no longer disrupt anything.** `wrangler versions upload` does |
| 156 | not restart DOs; a session opened before an upload runs straight through it. | 157 | not restart DOs; a session opened before an upload runs straight through it. |
| @@ -180,31 +181,13 @@ Measured on the lab worker: · 23 unmodified lines | |||
| 180 | and even a promotion loses only the single in-flight call, not the session or | 181 | and even a promotion loses only the single in-flight call, not the session or |
| 181 | its history. | 182 | its history. |
| 182 | 183 | ||
| 183 | ## The lab worker (`scripts/deploy-lab/`) | 184 | ## Re-validating the mechanics |
| 184 | 185 | ||
| 185 | `scripts/deploy-lab/` is a self-contained, re-runnable staging worker that | 186 | The measurements above came from a throwaway staging worker (a cut-down worker |
| 186 | mounts the **real** `McpAgentSessionDOBase` + the patched `agents` transport | 187 | mounting the real `McpAgentSessionDOBase` + the patched `agents` transport with |
| 187 | with stub auth and a trivial `execute` tool, so the versions / gradual- | 188 | stub auth and a trivial `execute` tool, deployed to a `workers.dev` subdomain, |
| 188 | deployment / affinity / rollback mechanics can be validated against the real | 189 | never routed). To re-validate after a Cloudflare behavior change, rebuild the |
| 189 | transport with no production secrets. Deploy it as | 190 | same shape: exercise affinity with `Cloudflare-Workers-Version-Key` across a |
| 190 | `executor-cloud-deploy-lab` (workers_dev only, never routed): | 191 | two-version gradual deployment, fire an upload and a promotion mid-call, and |
| 191 | 192 | roll back mid-session. Keep any such worker on `workers.dev` only and delete it | |
| 192 | ```bash | 193 | when done. |
| 193 | # from the repo root (bypasses apps/cloud's dist config redirect) | ||
| 194 | wrangler deploy -c apps/cloud/scripts/deploy-lab/wrangler.jsonc --var LAB_VERSION:v1 | ||
| 195 | ``` | ||
| 196 | |||
| 197 | It exposes `/mcp` (real streamable-HTTP MCP) and `/__lab/version` (echoes the | ||
| 198 | serving worker version, for observing traffic splits and affinity). Delete it | ||
| 199 | when done (`wrangler delete -c apps/cloud/scripts/deploy-lab/wrangler.jsonc`). | ||
| 200 | |||
| 201 | > **workers.dev caveat (lab only).** The lab is reached over its | ||
| 202 | > `*.workers.dev` subdomain, which had to be explicitly enabled once (Workers | ||
| 203 | > dashboard -> the worker -> Settings -> Domains & Routes -> enable the | ||
| 204 | > `workers.dev` route) before requests resolved; the config's `workers_dev: | ||
| 205 | true` alone was not sufficient on first deploy. This does **not** apply to | ||
| 206 | > production executor-cloud, which is served through configured **routes** on | ||
| 207 | > `executor.sh`, not a workers.dev subdomain. | ||
| 208 | |||
| 209 | See `scripts/deploy-lab/README.md` for the full experiment runbook and the | ||
| 210 | measured results. | ||
| @@ -1,86 +0,0 @@ | |||
| 1 | # executor-cloud-deploy-lab | ||
| 2 | |||
| 3 | A throwaway staging worker for validating the executor-cloud | ||
| 4 | **upload -> staged-promotion** deploy mechanics against real Cloudflare | ||
| 5 | deployment machinery (versions, gradual deployments, version affinity, rollback) | ||
| 6 | and the patched `agents` worker<->DO MCP bridge. It mounts the **real** shared | ||
| 7 | MCP session Durable Object base (`McpAgentSessionDOBase`) with the **real** | ||
| 8 | patched transport (`patches/agents@0.17.3.patch`), and stubs only the heavy | ||
| 9 | cloud seams (Postgres/Hyperdrive, WorkOS, Autumn, Sentry/OTEL) so it boots with | ||
| 10 | no production secrets. See `worker.ts` for the stubs. | ||
| 11 | |||
| 12 | **Never point this at production.** `workers_dev` only, no routes, no custom | ||
| 13 | domains, no crons. It is safe to `wrangler delete` at any time. | ||
| 14 | |||
| 15 | The narrative conclusions from these experiments live in | ||
| 16 | [`../../DEPLOY.md`](../../DEPLOY.md); this file is the runbook that produced them. | ||
| 17 | |||
| 18 | ## Deploy / redeploy | ||
| 19 | |||
| 20 | Deploy from the repo root so wrangler picks up this config directly (bypassing | ||
| 21 | `apps/cloud`'s dist redirect). Each version is uploaded with a distinct | ||
| 22 | `LAB_VERSION` var so every response echoes which worker version served it: | ||
| 23 | |||
| 24 | ```bash | ||
| 25 | # Upload a version WITHOUT routing traffic (returns a version id): | ||
| 26 | wrangler versions upload -c apps/cloud/scripts/deploy-lab/wrangler.jsonc --var LAB_VERSION:v6 | ||
| 27 | wrangler versions upload -c apps/cloud/scripts/deploy-lab/wrangler.jsonc --var LAB_VERSION:v7 | ||
| 28 | |||
| 29 | # Promote / split traffic: | ||
| 30 | wrangler versions deploy -c apps/cloud/scripts/deploy-lab/wrangler.jsonc <v6-id>@90% <v7-id>@10% --yes | ||
| 31 | ``` | ||
| 32 | |||
| 33 | > **workers.dev must be enabled once.** `workers_dev: true` in the config was | ||
| 34 | > not enough on first deploy: the `*.workers.dev` route had to be turned on | ||
| 35 | > manually (Workers dashboard -> the worker -> Settings -> Domains & Routes -> | ||
| 36 | > enable workers.dev) before requests resolved. Production is served through | ||
| 37 | > `executor.sh` routes, so this caveat is lab-only. | ||
| 38 | |||
| 39 | ## Endpoints | ||
| 40 | |||
| 41 | - `POST/GET/DELETE /mcp`: the real streamable-HTTP MCP surface (session create, | ||
| 42 | in-flight `tools/call`, POST-stream disconnect + GET replay). The single tool | ||
| 43 | `execute` sleeps `delayMs` then echoes `{ marker, servedByVersion }`, enough | ||
| 44 | to hold a ~30-60s call open across a mid-call deploy. | ||
| 45 | - `GET /__lab/version`: echoes `{ servedByVersion, receivedVersionKey }` and an | ||
| 46 | `x-lab-version` header, for observing which worker version served a request | ||
| 47 | and whether an affinity key was received. | ||
| 48 | |||
| 49 | ## Experiment runbook and measured results | ||
| 50 | |||
| 51 | All results below were measured against real Cloudflare deployments of this | ||
| 52 | worker (2026-07-07). | ||
| 53 | |||
| 54 | - **Exp3a, no affinity key.** 30 fresh connections over a 10/90 two-version | ||
| 55 | split: 28 hit v6, 2 hit v7. Confirms per-request (weighted) routing, a | ||
| 56 | stateless worker with no key does not stick to one version. | ||
| 57 | - **Exp3b, with `Cloudflare-Workers-Version-Key`.** 12 requests per key across a | ||
| 58 | 4-key set (alpha/bravo/charlie/delta) during the same split: each key mapped | ||
| 59 | to exactly one version for all 12 of its requests (alpha/bravo/delta -> v6, | ||
| 60 | charlie -> v7), and keys distributed across both versions. **Perfect per-key | ||
| 61 | stickiness**, this is the affinity mechanism DEPLOY.md's zone rule relies on. | ||
| 62 | - **Exp3c, DO pinning.** One MCP session's DO answered on v6 for every request | ||
| 63 | including while the stateless front-worker hops varied version. DOs are | ||
| 64 | natively version-pinned. | ||
| 65 | - **Exp4, rollback.** `versions deploy old@100` took effect within seconds | ||
| 66 | (10/10 subsequent requests on v6). A session that predated the rollback still | ||
| 67 | worked (`tools/list` -> 200) and its DO storage was intact. | ||
| 68 | - **Exp5, upload mid-call.** `versions upload` fired during a ~30s in-flight | ||
| 69 | call caused **zero disruption**, the call completed and its marker was | ||
| 70 | delivered (`EXP5_UPLOAD_SAFE`). Uploads are free. | ||
| 71 | - **Exp5b, promotion mid-call.** `versions deploy new@100` fired during a ~30s | ||
| 72 | in-flight call **killed the in-flight execution** (client saw the stream end | ||
| 73 | with no result, expected until durable-execution work lands). **But** the | ||
| 74 | session survived, the persisted event store survived the version swap, and a | ||
| 75 | reconnect GET replayed the prior events across the version boundary. | ||
| 76 | |||
| 77 | **Conclusion.** The upload/promote split plus session affinity works as | ||
| 78 | documented. Promotion is the only disruptive moment, and even then it loses only | ||
| 79 | the single in-flight call, not the session or its replayable history. The replay | ||
| 80 | machinery is version-swap-safe. | ||
| 81 | |||
| 82 | ## Cleanup | ||
| 83 | |||
| 84 | ```bash | ||
| 85 | wrangler delete -c apps/cloud/scripts/deploy-lab/wrangler.jsonc | ||
| 86 | ``` | ||
| @@ -1,192 +0,0 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // executor-cloud-deploy-lab, a CUT-DOWN staging worker used ONLY to validate | ||
| 3 | // the upload->staged-promotion deploy mechanics (versions, gradual deployments, | ||
| 4 | // version affinity, rollback) and the patched MCP bridge transport under real | ||
| 5 | // Cloudflare deployment machinery. | ||
| 6 | // | ||
| 7 | // It mounts the REAL shared MCP session Durable Object base | ||
| 8 | // (`McpAgentSessionDOBase` from @executor-js/cloudflare) with the REAL patched | ||
| 9 | // `agents` transport (patches/agents@0.17.3.patch, the worker<->DO websocket | ||
| 10 | // bridge). The heavy cloud seams (Postgres/Hyperdrive, WorkOS, Autumn billing, | ||
| 11 | // Sentry/OTEL) are replaced with trivial stubs so the worker boots with NO | ||
| 12 | // production secrets. The MCP transport path, session create, in-flight | ||
| 13 | // tools/call, POST-stream disconnect + GET replay, runs for real. | ||
| 14 | // | ||
| 15 | // NEVER point this at production. workers_dev only, no routes/domains/crons. | ||
| 16 | // --------------------------------------------------------------------------- | ||
| 17 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| 18 | import { Effect } from "effect"; | ||
| 19 | import * as z from "zod/v4"; | ||
| 20 | |||
| 21 | import { | ||
| 22 | McpAgentSessionDOBase, | ||
| 23 | type BuiltMcpServer, | ||
| 24 | type McpSessionInit, | ||
| 25 | type McpSessionProps, | ||
| 26 | type SessionDbHandle, | ||
| 27 | type SessionMeta, | ||
| 28 | } from "@executor-js/cloudflare/mcp/agent-durable-object"; | ||
| 29 | import { | ||
| 30 | withVerifiedIdentityHeaders, | ||
| 31 | readElicitationMode, | ||
| 32 | } from "@executor-js/cloudflare/mcp/do-headers"; | ||
| 33 | import { defaultMcpResource } from "@executor-js/host-mcp"; | ||
| 34 | |||
| 35 | // The version label is injected per-version via a `vars` override at | ||
| 36 | // `wrangler versions upload` time (`--var LAB_VERSION:vN`). Every response | ||
| 37 | // echoes it so a client can observe WHICH worker version served each request | ||
| 38 | // (the crux of the affinity experiment) without needing dashboard access. | ||
| 39 | interface LabEnv extends Cloudflare.Env { | ||
| 40 | readonly MCP_SESSION: DurableObjectNamespace; | ||
| 41 | readonly LAB_VERSION?: string; | ||
| 42 | } | ||
| 43 | |||
| 44 | // A no-op ExecutionEngine stub. The base only touches the engine on the | ||
| 45 | // pause/resume approval path; a plain tools/call runs the tool handler | ||
| 46 | // directly, so these are never exercised by the deploy experiments. Shape | ||
| 47 | // mirrors packages/core/execution/src/engine.ts::ExecutionEngine. | ||
| 48 | const makeStubEngine = () => | ||
| 49 | // oxlint-disable-next-line executor/no-double-cast -- lab-only test double: the engine is never exercised by the deploy experiments (no pause/resume), so a structural stub standing in for the full ExecutionEngine is intentional. | ||
| 50 | ({ | ||
| 51 | execute: () => Effect.succeed({ result: "lab-execute" }), | ||
| 52 | executeWithPause: () => | ||
| 53 | Effect.succeed({ status: "completed", result: { result: "lab-execute" } }), | ||
| 54 | resume: () => Effect.succeed({ status: "completed", result: { result: "lab-resume" } }), | ||
| 55 | getPausedExecution: () => Effect.succeed(null), | ||
| 56 | pausedExecutionCount: () => Effect.succeed(0), | ||
| 57 | hasPausedExecutions: () => Effect.succeed(false), | ||
| 58 | getDescription: Effect.succeed("lab stub engine"), | ||
| 59 | }) as unknown as BuiltMcpServer["engine"]; | ||
| 60 | |||
| 61 | // The lab MCP server exposes a single `execute` tool that sleeps for a | ||
| 62 | // caller-supplied duration and echoes a marker, enough to hold a ~60s | ||
| 63 | // in-flight tool call open across a mid-call deploy (experiments 1 & 5). | ||
| 64 | const buildLabMcpServer = (version: string): McpServer => { | ||
| 65 | const server = new McpServer({ name: "executor-cloud-deploy-lab", version: "0.0.1" }); | ||
| 66 | server.registerTool( | ||
| 67 | "execute", | ||
| 68 | { | ||
| 69 | description: "Sleep for delayMs then echo marker. Lab-only, no real execution engine.", | ||
| 70 | inputSchema: { | ||
| 71 | code: z | ||
| 72 | .string() | ||
| 73 | .optional() | ||
| 74 | .describe("ignored (compat with the real execute tool signature)"), | ||
| 75 | delayMs: z.number().optional().describe("how long to sleep before responding"), | ||
| 76 | marker: z.string().optional().describe("string echoed back in the result"), | ||
| 77 | }, | ||
| 78 | }, | ||
| 79 | async (args: { code?: string; delayMs?: number; marker?: string }) => { | ||
| 80 | const delayMs = typeof args.delayMs === "number" ? args.delayMs : 0; | ||
| 81 | const marker = args.marker ?? "LAB_RESULT"; | ||
| 82 | if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs)); | ||
| 83 | return { | ||
| 84 | content: [ | ||
| 85 | { type: "text" as const, text: JSON.stringify({ marker, servedByVersion: version }) }, | ||
| 86 | ], | ||
| 87 | }; | ||
| 88 | }, | ||
| 89 | ); | ||
| 90 | return server; | ||
| 91 | }; | ||
| 92 | |||
| 93 | // The lab DO: the REAL base + REAL patched transport, trivial seams. | ||
| 94 | export class McpSessionDOSqlite extends McpAgentSessionDOBase<LabEnv> { | ||
| 95 | protected openSessionDb(): SessionDbHandle { | ||
| 96 | return { end: () => undefined }; | ||
| 97 | } | ||
| 98 | |||
| 99 | protected resolveSessionMeta(token: McpSessionInit): Effect.Effect<SessionMeta> { | ||
| 100 | return Effect.succeed({ | ||
| 101 | organizationId: token.organizationId, | ||
| 102 | organizationName: "Deploy Lab Org", | ||
| 103 | userId: token.userId, | ||
| 104 | elicitationMode: | ||
| 105 | token.elicitationMode === "browser" || token.elicitationMode === "native" | ||
| 106 | ? token.elicitationMode | ||
| 107 | : "model", | ||
| 108 | resource: token.resource, | ||
| 109 | webOrigin: token.webOrigin, | ||
| 110 | }); | ||
| 111 | } | ||
| 112 | |||
| 113 | protected buildMcpServer(): Effect.Effect<BuiltMcpServer> { | ||
| 114 | const version = (this.env as LabEnv).LAB_VERSION ?? "unknown"; | ||
| 115 | return Effect.succeed({ | ||
| 116 | mcpServer: buildLabMcpServer(version), | ||
| 117 | engine: makeStubEngine(), | ||
| 118 | }); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | // A stub auth layer: accepts ANY bearer, mints a fixed principal. This is the | ||
| 123 | // ONLY thing standing in for the real WorkOS auth provider, the session | ||
| 124 | // engine, DO, transport, and bridge below are the real, patched code. | ||
| 125 | const LAB_PRINCIPAL = { accountId: "lab-user", organizationId: "lab-org" } as const; | ||
| 126 | |||
| 127 | const serve = McpAgentSessionDOBase.serve("/mcp", { | ||
| 128 | binding: "MCP_SESSION", | ||
| 129 | transport: "streamable-http", | ||
| 130 | }) as { fetch: (req: Request, env: LabEnv, ctx: ExecutionContext) => Promise<Response> }; | ||
| 131 | |||
| 132 | export default { | ||
| 133 | async fetch(request: Request, env: LabEnv, ctx: ExecutionContext): Promise<Response> { | ||
| 134 | const url = new URL(request.url); | ||
| 135 | const version = env.LAB_VERSION ?? "unknown"; | ||
| 136 | |||
| 137 | // Version-echo endpoint: which worker version is serving THIS request. | ||
| 138 | // Used to observe stateless-worker version affinity during a gradual | ||
| 139 | // deployment. Also echoes the version-affinity header if the client set it. | ||
| 140 | if (url.pathname === "/__lab/version") { | ||
| 141 | return new Response( | ||
| 142 | JSON.stringify({ | ||
| 143 | servedByVersion: version, | ||
| 144 | receivedVersionKey: request.headers.get("Cloudflare-Workers-Version-Key"), | ||
| 145 | }), | ||
| 146 | { headers: { "content-type": "application/json", "x-lab-version": version } }, | ||
| 147 | ); | ||
| 148 | } | ||
| 149 | |||
| 150 | if (url.pathname !== "/mcp") { | ||
| 151 | return new Response("lab worker: only /mcp and /__lab/version", { status: 404 }); | ||
| 152 | } | ||
| 153 | |||
| 154 | if (request.method === "OPTIONS") { | ||
| 155 | return new Response(null, { | ||
| 156 | status: 204, | ||
| 157 | headers: { | ||
| 158 | "access-control-allow-origin": "*", | ||
| 159 | "access-control-allow-methods": "GET, POST, DELETE, OPTIONS", | ||
| 160 | "access-control-allow-headers": | ||
| 161 | "content-type, authorization, mcp-session-id, accept, mcp-protocol-version, last-event-id", | ||
| 162 | "access-control-expose-headers": "mcp-session-id", | ||
| 163 | }, | ||
| 164 | }); | ||
| 165 | } | ||
| 166 | |||
| 167 | // Stub auth: any bearer -> fixed principal. Build props exactly like the | ||
| 168 | // real agent-handler so the DO's onStart receives a valid McpSessionProps. | ||
| 169 | const props: McpSessionProps = { | ||
| 170 | session: { | ||
| 171 | organizationId: LAB_PRINCIPAL.organizationId, | ||
| 172 | userId: LAB_PRINCIPAL.accountId, | ||
| 173 | elicitationMode: readElicitationMode(request), | ||
| 174 | resource: defaultMcpResource, | ||
| 175 | webOrigin: url.origin, | ||
| 176 | }, | ||
| 177 | }; | ||
| 178 | (ctx as ExecutionContext & { props?: McpSessionProps }).props = props; | ||
| 179 | |||
| 180 | const forwarded = withVerifiedIdentityHeaders(request, LAB_PRINCIPAL, defaultMcpResource); | ||
| 181 | const response = await serve.fetch(forwarded, env, ctx); | ||
| 182 | // Stamp the serving worker version on every MCP response header too, so the | ||
| 183 | // client can log per-request affinity even for streamed responses. | ||
| 184 | const headers = new Headers(response.headers); | ||
| 185 | headers.set("x-lab-version", version); | ||
| 186 | return new Response(response.body, { | ||
| 187 | status: response.status, | ||
| 188 | statusText: response.statusText, | ||
| 189 | headers, | ||
| 190 | }); | ||
| 191 | }, | ||
| 192 | }; | ||
| @@ -1,34 +0,0 @@ | |||
| 1 | { | ||
| 2 | // STAGING deploy-mechanics lab worker. NOT production. workers_dev only — | ||
| 3 | // no routes, no custom_domain, no crons, no hyperdrive/r2/service bindings. | ||
| 4 | // See scripts/deploy-lab/README.md for the experiment runbook. | ||
| 5 | "$schema": "../../node_modules/wrangler/config-schema.json", | ||
| 6 | "name": "executor-cloud-deploy-lab", | ||
| 7 | "compatibility_date": "2025-04-01", | ||
| 8 | "compatibility_flags": ["nodejs_compat"], | ||
| 9 | "main": "worker.ts", | ||
| 10 | "workers_dev": true, | ||
| 11 | "observability": { | ||
| 12 | "enabled": true, | ||
| 13 | }, | ||
| 14 | "durable_objects": { | ||
| 15 | "bindings": [ | ||
| 16 | { | ||
| 17 | "name": "MCP_SESSION", | ||
| 18 | "class_name": "McpSessionDOSqlite", | ||
| 19 | }, | ||
| 20 | ], | ||
| 21 | }, | ||
| 22 | "migrations": [ | ||
| 23 | { | ||
| 24 | "tag": "v1", | ||
| 25 | "new_sqlite_classes": ["McpSessionDOSqlite"], | ||
| 26 | }, | ||
| 27 | ], | ||
| 28 | "placement": { | ||
| 29 | "region": "aws:us-east-1", | ||
| 30 | }, | ||
| 31 | "vars": { | ||
| 32 | "LAB_VERSION": "unset", | ||
| 33 | }, | ||
| 34 | } | ||
| @@ -39,12 +39,12 @@ import { spawnSync } from "node:child_process"; · 38 unmodified lines | |||
| 39 | import { readFileSync } from "node:fs"; | 39 | import { readFileSync } from "node:fs"; |
| 40 | 40 | ||
| 41 | // The generated dist config the build produced. Overridable via | 41 | // The generated dist config the build produced. Overridable via |
| 42 | // PROMOTE_CLOUD_CONFIG for local/lab testing against a non-prod worker. | 42 | // PROMOTE_CLOUD_CONFIG for testing against a non-prod worker. |
| 43 | const CONFIG = process.env.PROMOTE_CLOUD_CONFIG ?? "dist/server/wrangler.json"; | 43 | const CONFIG = process.env.PROMOTE_CLOUD_CONFIG ?? "dist/server/wrangler.json"; |
| 44 | 44 | ||
| 45 | // Target-worker guard: refuse to run against anything but executor-cloud unless | 45 | // Target-worker guard: refuse to run against anything but executor-cloud unless |
| 46 | // the operator explicitly names the worker they expect via | 46 | // the operator explicitly names the worker they expect via |
| 47 | // PROMOTE_CLOUD_ALLOW_NAME (for the deploy lab / a non-prod worker). A promotion | 47 | // PROMOTE_CLOUD_ALLOW_NAME (for a non-prod worker). A promotion |
| 48 | // routes live traffic and restarts DOs, so a misconfigured config that points at | 48 | // routes live traffic and restarts DOs, so a misconfigured config that points at |
| 49 | // the wrong worker must fail loudly, not promote the wrong thing. `--yes` does | 49 | // the wrong worker must fail loudly, not promote the wrong thing. `--yes` does |
| 50 | // NOT bypass this: it only skips wrangler's own interactive confirmation. | 50 | // NOT bypass this: it only skips wrangler's own interactive confirmation. |