void 0.21.9 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/{account-cmd-DZZGK80W.mjs → account-cmd-CjyxcGkM.mjs} +4 -4
  2. package/dist/{auth-link-B_CeugBw.mjs → auth-link-BLO4ptzo.mjs} +4 -4
  3. package/dist/{auth-router-BgEFRuvZ.mjs → auth-router-ZUg9MF_U.mjs} +5 -5
  4. package/dist/{build-cmd-Dpt0jd-x.mjs → build-cmd-BxR5FROK.mjs} +3 -3
  5. package/dist/{cache-7_UeZdTk.mjs → cache-D0sWhgKI.mjs} +3 -3
  6. package/dist/{cancel-deploy-D23R1RXT.mjs → cancel-deploy-D4NUqFbi.mjs} +3 -3
  7. package/dist/{cf-build-output-CGT03qCD.mjs → cf-build-output-BJ6yGEIS.mjs} +139 -62
  8. package/dist/cli/cf-compat.mjs +164 -50
  9. package/dist/cli/cli.mjs +73 -44
  10. package/dist/cli/env-schema-probe.d.mts +1 -1
  11. package/dist/{client-QTl6ko_D.mjs → client-BTZ3XkrB.mjs} +25 -0
  12. package/dist/client-Czz8o5jP.mjs +2 -0
  13. package/dist/{cloudflare-auth-C4_GPZr0.mjs → cloudflare-auth-DQkqoMYa.mjs} +1 -6
  14. package/dist/{cloudflare-cmd-bSmZ1d5L.mjs → cloudflare-cmd-BKlfGHAy.mjs} +6 -7
  15. package/dist/{cloudflare-connect-jAwacxxP.mjs → cloudflare-connect-Ctmrw579.mjs} +3 -3
  16. package/dist/{cloudflare-operations-sMZXpk_S.mjs → cloudflare-operations-B9tzgjmf.mjs} +10 -39
  17. package/dist/{cloudflare-operations-BipGMJ5O.mjs → cloudflare-operations-BT6OWFBk.mjs} +1 -1
  18. package/dist/{config-CafTW6Cz.mjs → config-Br_JZD6u.mjs} +2 -7
  19. package/dist/{config-s7Xj7tPb.mjs → config-CyQ-wVd7.mjs} +1 -1
  20. package/dist/{config-CF69HgXc.d.mts → config-VavjpDnp.d.mts} +1 -2
  21. package/dist/config-entry.d.mts +1 -1
  22. package/dist/{config-write-BSduPMY8.mjs → config-write-B1f88wJA.mjs} +1 -1
  23. package/dist/{connect-BsUSRzln.mjs → connect-Dg-WkW-C.mjs} +5 -5
  24. package/dist/{create-project-BniEV0OW.mjs → create-project-DdoqwFLF.mjs} +1 -1
  25. package/dist/{create-project-DBfFSZKY.mjs → create-project-l9J7pbtm.mjs} +8 -4
  26. package/dist/{db-hRrvZaq_.mjs → db-gp2sCXyN.mjs} +14 -14
  27. package/dist/{delete-D6dZ9B6B.mjs → delete-TTedbH6B.mjs} +3 -3
  28. package/dist/{deploy-DDz7c8LK.mjs → deploy-B19L2Bra.mjs} +1 -1
  29. package/dist/{deploy-WaAQez1O.mjs → deploy-CH-o2CaZ.mjs} +47 -36
  30. package/dist/{dist-BR1quN_w.mjs → dist-AoCzRTJE.mjs} +226 -60
  31. package/dist/{dist-C5fND3R0.mjs → dist-BuDuKZJv.mjs} +1 -1
  32. package/dist/{dist-Dn6nn2IU.mjs → dist-CTBk70IR.mjs} +42 -42
  33. package/dist/{domain-Dmhvb2oU.mjs → domain-JRF59P_r.mjs} +4 -4
  34. package/dist/{email-DFi-s2t4.mjs → email-Bo6G9LOZ.mjs} +6 -6
  35. package/dist/{env-Csi-tMbT.mjs → env-BYOrWQnv.mjs} +4 -4
  36. package/dist/{env-public-D_6u46fX.d.mts → env-public-BxU_0yTL.d.mts} +2 -1
  37. package/dist/gen-CNJ62MM7.mjs +2 -0
  38. package/dist/{gen-Dz3X1Qab.mjs → gen-sCtlCOdA.mjs} +3 -3
  39. package/dist/{github-cmd-BED3_9JD.mjs → github-cmd-0-7PexDq.mjs} +5 -7
  40. package/dist/{handler-BXJTXd02.d.mts → handler-HEcZsaij.d.mts} +2 -1
  41. package/dist/{headers-BOg_velo.mjs → headers-DWi2IXWx.mjs} +1 -1
  42. package/dist/help-CmZzxUba.mjs +2 -0
  43. package/dist/{help-DC7gdz7L.mjs → help-GKtwl07I.mjs} +124 -4
  44. package/dist/index.d.mts +1 -1
  45. package/dist/index.mjs +53 -24
  46. package/dist/{init-WO0JlPx8.mjs → init-FZx3Elvz.mjs} +13 -13
  47. package/dist/{link-CsHOinF7.mjs → link-BrQfb_CU.mjs} +4 -4
  48. package/dist/{list-CDb-4bZ1.mjs → list-D44jmIAM.mjs} +4 -4
  49. package/dist/{live-CKiJilLr.d.mts → live-CKJlvlNp.d.mts} +1 -1
  50. package/dist/{local-d1-CC8sKFGu.mjs → local-d1-Bg9OzEEO.mjs} +1 -1
  51. package/dist/{login-DvqXsfGs.mjs → login-CAsAsQ-_.mjs} +4 -4
  52. package/dist/login-CGcRKEoi.mjs +2 -0
  53. package/dist/{logs-BdfiOezj.mjs → logs-BjKvFnVM.mjs} +4 -4
  54. package/dist/{migrate-B8KuoYsO.mjs → migrate-BPITvDJN.mjs} +4 -4
  55. package/dist/migrate-CYfbKkXh.mjs +2 -0
  56. package/dist/{node-BM43oz4G.mjs → node-Dt7z256D.mjs} +1 -1
  57. package/dist/{operator-cmd-03819ATr.mjs → operator-cmd-BK5CCiT9.mjs} +4 -4
  58. package/dist/output-Dm_A4Tbv.mjs +81 -0
  59. package/dist/pages/client.d.mts +1 -1
  60. package/dist/pages/index.d.mts +1 -1
  61. package/dist/pages/index.mjs +2 -2
  62. package/dist/pages/islands-plugin.d.mts +16 -6
  63. package/dist/pages/islands-plugin.mjs +1 -1
  64. package/dist/pages/protocol.d.mts +2 -2
  65. package/dist/{output-CCH48AMM.mjs → picocolors-BTps1_gs.mjs} +2 -76
  66. package/dist/{platform-auth-config-z92h63q5.mjs → platform-auth-config-BlN8xTdD.mjs} +3 -3
  67. package/dist/{platform-auth-protection-DeE3yr2R.mjs → platform-auth-protection-7d5aV2Jg.mjs} +3 -3
  68. package/dist/{platform-auth-recovery-o_340Ixx.mjs → platform-auth-recovery-Dxij8ZbR.mjs} +3 -3
  69. package/dist/{platform-cmd-DNl8WosH.mjs → platform-cmd-B3hwKrFK.mjs} +5 -5
  70. package/dist/{platform-cmd-C4ZV3Vpy.mjs → platform-cmd-E0FuL212.mjs} +1 -1
  71. package/dist/{platform-domain-CU1JtJkW.mjs → platform-domain-D6Xcy9ZX.mjs} +36 -9
  72. package/dist/{platform-lifecycle-Bs2qx1E3.mjs → platform-lifecycle-DMh_qrry.mjs} +1 -1
  73. package/dist/{platform-lifecycle-Bc047IeT.mjs → platform-lifecycle-k0E0xoxx.mjs} +548 -86
  74. package/dist/{platform-management-CVpGI9Y5.mjs → platform-management-Brz_BDiT.mjs} +54 -9
  75. package/dist/{platform-management-C8tt6D8h.mjs → platform-management-DOtss0BN.mjs} +1 -1
  76. package/dist/{platform-recovery-dvUaptLr.mjs → platform-recovery-BvtcXON5.mjs} +2 -2
  77. package/dist/prepare-Bm3iq-u4.mjs +2 -0
  78. package/dist/{prepare-DyZ-Yok5.mjs → prepare-C-6YZyyg.mjs} +3 -3
  79. package/dist/{prepare-C3kt3Rst.mjs → prepare-CRhJbVrG.mjs} +1 -1
  80. package/dist/{project-cmd-B44I8J_W.mjs → project-cmd-BZLCqOqw.mjs} +29 -15
  81. package/dist/{project-team-BkbYIsXN.mjs → project-team-BvgM0WoX.mjs} +3 -3
  82. package/dist/{project-token-C8xEEQnB.mjs → project-token-l5DqK6Az.mjs} +3 -3
  83. package/dist/project-zero-trust-v6Mvpp8y.mjs +63 -0
  84. package/dist/{protocol-ZH3jP4a7.d.mts → protocol-BBa6cstI.d.mts} +1 -1
  85. package/dist/{provision-BhreDAOS.mjs → provision-C4IGqkBf.mjs} +24 -19
  86. package/dist/{provision-DNtrtaVD.mjs → provision-Fr9pRqce.mjs} +1 -1
  87. package/dist/{requests-DFyhBMaf.mjs → requests-DN-BbNiM.mjs} +3 -3
  88. package/dist/{rollback-DHHxXZiS.mjs → rollback-D7rzSaZM.mjs} +4 -4
  89. package/dist/runtime/env-public.d.mts +1 -1
  90. package/dist/runtime/handler.d.mts +1 -1
  91. package/dist/runtime/live-client.d.mts +1 -1
  92. package/dist/runtime/live.d.mts +1 -1
  93. package/dist/runtime/sandbox-container.d.mts +3 -0
  94. package/dist/runtime/sandbox-container.mjs +2 -0
  95. package/dist/runtime/sandbox.d.mts +4 -32
  96. package/dist/runtime/sandbox.mjs +118 -72
  97. package/dist/runtime/validator.d.mts +1 -1
  98. package/dist/runtime/ws-server.d.mts +1 -1
  99. package/dist/runtime/ws.d.mts +1 -1
  100. package/dist/sandbox-container-4fdqLnyb.mjs +279 -0
  101. package/dist/sandbox-container-DdNEBfCc.d.mts +72 -0
  102. package/dist/sandbox-qpNBT8a3.d.mts +49 -0
  103. package/dist/{secret-DN9sSNiV.mjs → secret-BOtOl_cb.mjs} +4 -4
  104. package/dist/{skills-O6FUaizK.mjs → skills-D1II1Juz.mjs} +1 -1
  105. package/dist/{subcommand-prompt-BuGYkAkC.mjs → subcommand-prompt-CY1C4fvl.mjs} +2 -2
  106. package/dist/{wrangler-BymcxrRa.mjs → wrangler-DQF1vKyf.mjs} +37 -27
  107. package/dist/{ws-BwcqizuH.d.mts → ws-BoY7vQML.d.mts} +1 -1
  108. package/package.json +40 -32
  109. package/sandbox.Dockerfile +4 -0
  110. package/schema.json +10 -22
  111. package/skills/void/SKILL.md +2 -0
  112. package/skills/void/docs/guide/deployment.md +1 -1
  113. package/skills/void/docs/guide/edge/revalidation.md +1 -1
  114. package/skills/void/docs/guide/edge/static-assets.md +1 -0
  115. package/skills/void/docs/guide/platform/administration/zero-trust.md +318 -0
  116. package/skills/void/docs/guide/platform/development/local.md +2 -2
  117. package/skills/void/docs/guide/platform/installation/domains.md +1 -1
  118. package/skills/void/docs/guide/platform/installation/first-deployment.md +4 -0
  119. package/skills/void/docs/guide/platform/installation/prerequisites.md +1 -1
  120. package/skills/void/docs/guide/platform/installation/uninstall.md +17 -2
  121. package/skills/void/docs/guide/platform-administration.md +1 -0
  122. package/skills/void/docs/guide/sandboxes.md +70 -41
  123. package/skills/void/docs/integrations/cloudflare.md +1 -1
  124. package/skills/void/docs/reference/api.md +35 -35
  125. package/skills/void/docs/reference/cli.md +68 -6
  126. package/skills/void/docs/reference/config.md +16 -16
  127. package/dist/client-4cDVv7BO.mjs +0 -2
  128. package/dist/gen-Vnv2f65C.mjs +0 -2
  129. package/dist/help-DQMfeKMz.mjs +0 -2
  130. package/dist/login-DFQk7rbW.mjs +0 -2
  131. package/dist/migrate-TsHGBnDA.mjs +0 -2
  132. package/dist/prepare-BZXkjdNe.mjs +0 -2
@@ -1,20 +1,21 @@
1
1
  import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
2
2
  import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
3
- import { c as import_picocolors$1, d as require_picocolors, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-CCH48AMM.mjs";
3
+ import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
4
4
  import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
5
5
  import { d as openBrowser } from "./auth-DLNN0D3Z.mjs";
6
6
  import { a as listInstallationManifests, d as readInstallationRecoverySecrets, f as readPendingInstallationSecrets, g as savePlatformConnection, h as resolveInstallationManifest, i as latestPlatformWorkerCutoverGeneration, l as platformWorkerSecretUpdateIntentState, n as assertPlatformInstallationSlugAvailable, s as normalizePlatformApiUrl, t as acquirePlatformLifecycleLock, u as promoteInstallationSecrets, v as writeInstallationManifest, y as writePendingInstallationSecrets } from "./platform-registry-QBa4DK32.mjs";
7
7
  import { n as ensurePlatformAccessToken } from "./cf-access-P0iZnu0q.mjs";
8
- import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-Dn6nn2IU.mjs";
8
+ import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-CTBk70IR.mjs";
9
9
  import { o as resolveWranglerArgv } from "./preset-UHj9ARyP.mjs";
10
- import { c as runCloudflareAuthCommand, n as announceCloudflareLogin } from "./cloudflare-auth-C4_GPZr0.mjs";
11
- import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-BhreDAOS.mjs";
10
+ import { s as runCloudflareAuthCommand } from "./cloudflare-auth-DQkqoMYa.mjs";
11
+ import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-C4IGqkBf.mjs";
12
+ import { c as import_picocolors$1, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
12
13
  import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
13
14
  import { fileURLToPath } from "node:url";
14
15
  import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
15
16
  import { tmpdir } from "node:os";
16
17
  import { execFile } from "node:child_process";
17
- import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_SANDBOX_DRAIN_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
18
+ import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_SANDBOX_DRAIN_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
18
19
  import { stripVTControlCharacters } from "node:util";
19
20
  import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
20
21
  import { readFile } from "node:fs/promises";
@@ -652,7 +653,7 @@ function parsePostgresConnectionString(value) {
652
653
  let mtls;
653
654
  if (sslmode) {
654
655
  if (sslmode !== "require" && sslmode !== "verify-ca" && sslmode !== "verify-full") throw new Error("platform: Unsupported sslmode. Cloudflare Hyperdrive supports \"require\", \"verify-ca\", or \"verify-full\".");
655
- mtls = { sslmode };
656
+ mtls = { sslmode: "require" };
656
657
  }
657
658
  return {
658
659
  database,
@@ -756,6 +757,11 @@ var PlatformAccessClient = class {
756
757
  throw new Error("platform: Access inventory exceeded the supported pagination limit.");
757
758
  }
758
759
  };
760
+ /**
761
+ * Project Zero Trust keeps its own, broader copy of this list in
762
+ * platform/packages/api/src/zero-trust/cloudflare.ts; review both when adding
763
+ * a rule type.
764
+ */
759
765
  function identityRule(rule) {
760
766
  if (rule.email_domain) {
761
767
  const domain = object(rule.email_domain, "email domain").domain;
@@ -1411,7 +1417,6 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
1411
1417
  input,
1412
1418
  credential: result.credential
1413
1419
  };
1414
- announceCloudflareLogin();
1415
1420
  const login = await runWrangler(["login"], false);
1416
1421
  assertNotCancelled();
1417
1422
  if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
@@ -1500,7 +1505,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
1500
1505
  }
1501
1506
  const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
1502
1507
  if (existingWorkers.length) {
1503
- const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-DeE3yr2R.mjs");
1508
+ const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-7d5aV2Jg.mjs");
1504
1509
  await verifyProtectionWorkerOrigins({
1505
1510
  request: input.client.request.bind(input.client),
1506
1511
  listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
@@ -1831,6 +1836,18 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1831
1836
  };
1832
1837
  }
1833
1838
  async function revalidatePlatformInstallAction(client, original, manifest, action, options) {
1839
+ if (options?.stableWorkerUpdate && !options.repairMissing && action.kind === "worker" && action.action === "update" && original.actions.some((entry) => entry.key === action.key && entry.kind === action.kind && entry.action === action.action && entry.id === action.id && entry.name === action.name) && original.actions.every((entry) => {
1840
+ const resource = manifest.resources.find((candidate) => candidate.key === entry.key);
1841
+ return (entry.kind === "worker" ? entry.action === "update" : entry.action === "reuse") && resource?.kind === entry.kind && resource.state === "ready" && resource.id === entry.id && resource.name === entry.name;
1842
+ })) {
1843
+ const recorded = manifest.resources.find((resource) => resource.key === action.key);
1844
+ if (!recorded || recorded.kind !== "worker" || recorded.state !== "ready" || recorded.id !== action.name || recorded.name !== action.name) throw new Error(`platform: Worker ${JSON.stringify(action.name)} no longer matches the approved installation plan. Review a new plan before retrying.`);
1845
+ if (!matchesResourceMarker((await client.getWorkerPlainTextBindings(original.account.id, action.name)).PLATFORM_OWNERSHIP_MARKER, manifest, action.key, "worker", {
1846
+ id: action.name,
1847
+ name: action.name
1848
+ })) throw new Error(`platform: Cloudflare Worker ${JSON.stringify(action.name)} no longer has this installation's ownership marker. Review a new plan before retrying.`);
1849
+ return;
1850
+ }
1834
1851
  const deployment = original.deployment;
1835
1852
  const current = (await createPlatformInstallPlan(client, {
1836
1853
  accountId: original.account.id,
@@ -2216,6 +2233,284 @@ function recordPlatformWorkerTraffic(manifest, workerName, inventory, updatedAt)
2216
2233
  return next;
2217
2234
  }
2218
2235
  //#endregion
2236
+ //#region src/cli/platform-trigger-proof.ts
2237
+ function record(value) {
2238
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2239
+ }
2240
+ function sameStrings(actual, expected) {
2241
+ const sortedExpected = [...expected].sort();
2242
+ return actual.length === expected.length && [...actual].sort().every((value, index) => value === sortedExpected[index]);
2243
+ }
2244
+ function desiredQueue(value) {
2245
+ const queue = record(value);
2246
+ if (!queue || typeof queue.queue !== "string" || Object.keys(queue).some((key) => ![
2247
+ "queue",
2248
+ "max_batch_size",
2249
+ "max_batch_timeout",
2250
+ "max_retries",
2251
+ "max_concurrency",
2252
+ "retry_delay",
2253
+ "dead_letter_queue"
2254
+ ].includes(key)) || [
2255
+ "max_batch_size",
2256
+ "max_batch_timeout",
2257
+ "max_retries",
2258
+ "max_concurrency",
2259
+ "retry_delay"
2260
+ ].some((key) => queue[key] !== void 0 && typeof queue[key] !== "number") || queue.dead_letter_queue !== void 0 && typeof queue.dead_letter_queue !== "string") return null;
2261
+ return queue;
2262
+ }
2263
+ function matchesQueueConsumer(liveValue, desired, workerName) {
2264
+ const live = record(liveValue);
2265
+ const settings = record(live?.settings);
2266
+ if (!live || live.type !== "worker" || live.script_name !== void 0 && live.script_name !== workerName || live.queue_name !== desired.queue || !settings) return false;
2267
+ const expected = {
2268
+ batch_size: desired.max_batch_size ?? 10,
2269
+ max_wait_time_ms: Number(desired.max_batch_timeout ?? 5) * 1e3,
2270
+ max_retries: desired.max_retries ?? 3,
2271
+ max_concurrency: desired.max_concurrency ?? null,
2272
+ retry_delay: desired.retry_delay ?? 0
2273
+ };
2274
+ return Object.entries(expected).every(([key, value]) => key === "max_concurrency" ? (settings[key] ?? null) === value : settings[key] === value) && (live.dead_letter_queue ?? "") === (desired.dead_letter_queue ?? "");
2275
+ }
2276
+ /**
2277
+ * Read every surface that `triggers deploy` can mutate for a platform Worker. An incomplete
2278
+ * response, unsupported trigger kind, or read failure requires normal trigger publication.
2279
+ */
2280
+ async function platformWorkerLiveTriggersMatch(options) {
2281
+ const { accountId, workerName, desired, readCloudflare } = options;
2282
+ if (desired.preserve || desired.addresses !== void 0 || desired.workflows.length > 0 || !desired.crons.every((cron) => typeof cron === "string")) return false;
2283
+ const queues = desired.queueConsumers.map(desiredQueue);
2284
+ if (queues.some((queue) => queue === null)) return false;
2285
+ const base = `/accounts/${encodeURIComponent(accountId)}`;
2286
+ const script = `${base}/workers/scripts/${encodeURIComponent(workerName)}`;
2287
+ try {
2288
+ const [workerValue, schedulesValue, workflowsValue, nextWorkflowsValue] = await Promise.all([
2289
+ readCloudflare(`${base}/workers/workers/${encodeURIComponent(workerName)}`),
2290
+ readCloudflare(`${script}/schedules`),
2291
+ readCloudflare(`${base}/workflows?page=1&per_page=100`),
2292
+ readCloudflare(`${base}/workflows?page=2&per_page=100`)
2293
+ ]);
2294
+ const worker = record(workerValue);
2295
+ const subdomain = record(worker?.subdomain);
2296
+ const schedules = record(schedulesValue)?.schedules;
2297
+ if (worker?.name !== workerName || !subdomain || !desired.preserveWorkersDev && subdomain.enabled !== desired.workers_dev || !desired.preservePreviewUrls && subdomain.previews_enabled !== desired.preview_urls || !Array.isArray(schedules) || !Array.isArray(worker.references && record(worker.references)?.queues) || !Array.isArray(workflowsValue) || workflowsValue.length >= 100 || !Array.isArray(nextWorkflowsValue) || nextWorkflowsValue.length !== 0) return false;
2298
+ const liveCrons = schedules.map((entry) => record(entry)?.cron);
2299
+ if (!liveCrons.every((cron) => typeof cron === "string") || !sameStrings(liveCrons, desired.crons) || workflowsValue.some((entry) => {
2300
+ const workflow = record(entry);
2301
+ return !workflow || typeof workflow.script_name !== "string" || workflow.script_name === workerName;
2302
+ })) return false;
2303
+ const references = record(worker.references).queues;
2304
+ if (references.length !== queues.length) return false;
2305
+ const matched = /* @__PURE__ */ new Set();
2306
+ const consumers = await Promise.all(references.map(async (value) => {
2307
+ const reference = record(value);
2308
+ if (typeof reference?.queue_id !== "string" || typeof reference.queue_consumer_id !== "string" || typeof reference.queue_name !== "string") return null;
2309
+ return {
2310
+ queueName: reference.queue_name,
2311
+ consumer: await readCloudflare(`${base}/queues/${encodeURIComponent(reference.queue_id)}/consumers/${encodeURIComponent(reference.queue_consumer_id)}`)
2312
+ };
2313
+ }));
2314
+ for (const entry of consumers) {
2315
+ if (!entry || matched.has(entry.queueName)) return false;
2316
+ const desiredQueueConfig = queues.find((queue) => queue?.queue === entry.queueName);
2317
+ if (!desiredQueueConfig || !matchesQueueConsumer(entry.consumer, desiredQueueConfig, workerName)) return false;
2318
+ matched.add(entry.queueName);
2319
+ }
2320
+ return matched.size === queues.length;
2321
+ } catch {
2322
+ return false;
2323
+ }
2324
+ }
2325
+ //#endregion
2326
+ //#region src/cli/platform-zero-trust-uninstall.ts
2327
+ /**
2328
+ * Uninstall turns off the platform API, and only that API can remove the
2329
+ * Cloudflare Access applications project Zero Trust created. The shared
2330
+ * project application covers every hostname under the application domain, so
2331
+ * a left-over one keeps asking for sign-in on hosts Void no longer serves.
2332
+ * Uninstall therefore reads the saved Zero Trust state straight from
2333
+ * PostgreSQL, which works whether or not the API still answers, and refuses
2334
+ * while anything remains to clean up or the state cannot be read.
2335
+ */
2336
+ const ZERO_TRUST_SETTING = "zeroTrust.configuration";
2337
+ const ZERO_TRUST_LOCK = "hashtext('zero-trust:configuration')";
2338
+ const UNINSTALL_SETTING = "platform.uninstall";
2339
+ const LISTED_APPLICATIONS = 20;
2340
+ function isRecord(value) {
2341
+ return !!value && typeof value === "object" && !Array.isArray(value);
2342
+ }
2343
+ function optionalText(value, label) {
2344
+ if (value === null || value === void 0) return null;
2345
+ if (typeof value !== "string") throw new Error(`the saved ${label} is malformed`);
2346
+ return value;
2347
+ }
2348
+ function savedApplication(value, label) {
2349
+ if (value === null || value === void 0) return null;
2350
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.name !== "string") throw new Error(`the saved ${label} is malformed`);
2351
+ const destinations = Array.isArray(value.destinations) ? value.destinations.filter((entry) => typeof entry === "string") : [];
2352
+ return {
2353
+ id: value.id,
2354
+ name: value.name,
2355
+ destinations
2356
+ };
2357
+ }
2358
+ /**
2359
+ * Interpret the saved configuration and project rows. Anything that does not
2360
+ * have the expected shape throws, so the caller refuses instead of guessing.
2361
+ */
2362
+ function projectZeroTrustUninstallState(setting, projectRows) {
2363
+ const applications = [];
2364
+ let active = false;
2365
+ let destinations = [];
2366
+ if (setting !== null && setting !== void 0) {
2367
+ if (!isRecord(setting) || typeof setting.enabled !== "boolean") throw new Error("the saved Zero Trust configuration is malformed");
2368
+ const operation = setting.operation ?? null;
2369
+ if (operation !== null && !isRecord(operation)) throw new Error("the saved Zero Trust operation is malformed");
2370
+ active = setting.enabled || operation !== null;
2371
+ const wildcard = savedApplication(setting.wildcardApplication, "shared project application");
2372
+ const health = savedApplication(setting.healthApplication, "health check exception");
2373
+ const wildcardName = optionalText(setting.wildcardApplicationName, "application name");
2374
+ const healthName = optionalText(setting.healthApplicationName, "application name");
2375
+ if (wildcard || active && wildcardName) applications.push({
2376
+ role: "shared project application",
2377
+ id: wildcard?.id ?? null,
2378
+ name: wildcard?.name ?? wildcardName
2379
+ });
2380
+ if (health || active && healthName) applications.push({
2381
+ role: "health check exception",
2382
+ id: health?.id ?? null,
2383
+ name: health?.name ?? healthName
2384
+ });
2385
+ destinations = wildcard?.destinations ?? [];
2386
+ }
2387
+ const unfinishedDeletions = [];
2388
+ for (const row of projectRows) {
2389
+ const deleting = row.project_status === "deleting";
2390
+ const owned = applications.length;
2391
+ if (row.bypass_application_id || row.bypass_application_name) applications.push({
2392
+ role: `public exception for ${row.project_id}`,
2393
+ id: row.bypass_application_id,
2394
+ name: row.bypass_application_name,
2395
+ ...deleting ? { deleting } : {}
2396
+ });
2397
+ if (row.protected_application_id || row.protected_application_name) applications.push({
2398
+ role: `custom domain application for ${row.project_id}`,
2399
+ id: row.protected_application_id,
2400
+ name: row.protected_application_name,
2401
+ ...deleting ? { deleting } : {}
2402
+ });
2403
+ if (deleting && applications.length > owned) unfinishedDeletions.push(row.project_slug ?? row.project_id);
2404
+ }
2405
+ return {
2406
+ active,
2407
+ destinations,
2408
+ applications,
2409
+ unfinishedDeletions
2410
+ };
2411
+ }
2412
+ /** Read the project Zero Trust state from the platform database. */
2413
+ async function readPlatformProjectZeroTrustStateConnection(client) {
2414
+ const present = (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "settings",
2415
+ to_regclass('public.project_zero_trust') IS NOT NULL AS "projects"`)).rows[0];
2416
+ if (!present) throw new Error("the database did not report its Zero Trust tables");
2417
+ return projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
2418
+ z."bypass_application_id", z."bypass_application_name",
2419
+ z."protected_application_id", z."protected_application_name"
2420
+ FROM "project_zero_trust" z
2421
+ LEFT JOIN "projects" p ON p."id" = z."project_id"
2422
+ WHERE z."bypass_application_id" IS NOT NULL
2423
+ OR z."bypass_application_name" IS NOT NULL
2424
+ OR z."protected_application_id" IS NOT NULL
2425
+ OR z."protected_application_name" IS NOT NULL
2426
+ ORDER BY z."project_id"`)).rows : []);
2427
+ }
2428
+ /**
2429
+ * Take the Zero Trust configuration lock exclusively on this session. Every
2430
+ * API path that records or creates a Void-managed Access application holds
2431
+ * the same lock, so none can commit until the returned release runs.
2432
+ */
2433
+ async function lockPlatformProjectZeroTrustConnection(client) {
2434
+ await client.query(`SELECT pg_advisory_lock(${ZERO_TRUST_LOCK})`);
2435
+ let released = false;
2436
+ return async () => {
2437
+ if (released) return;
2438
+ released = true;
2439
+ await client.query(`SELECT pg_advisory_unlock(${ZERO_TRUST_LOCK})`);
2440
+ };
2441
+ }
2442
+ async function hasSettingsTable(client) {
2443
+ return (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "present"`)).rows[0]?.present === true;
2444
+ }
2445
+ /**
2446
+ * Record that uninstall passed its final check. Runs while the configuration
2447
+ * lock is held. An API request admitted before traffic was disabled may be
2448
+ * waiting on that lock; once it gets the lock it reads this record and
2449
+ * refuses. Without the settings table the API has no Zero Trust writers.
2450
+ */
2451
+ async function fencePlatformProjectZeroTrustConnection(client) {
2452
+ if (!await hasSettingsTable(client)) return;
2453
+ await client.query(`INSERT INTO "platform_settings" ("key", "value") VALUES ($1, $2::jsonb)
2454
+ ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value", "updated_at" = now()`, [UNINSTALL_SETTING, JSON.stringify({ startedAt: (/* @__PURE__ */ new Date()).toISOString() })]);
2455
+ }
2456
+ /**
2457
+ * Remove the uninstall record. Uninstall does this when it stops before
2458
+ * disabling traffic; installing or enabling does it before traffic opens.
2459
+ */
2460
+ async function liftPlatformProjectZeroTrustFenceConnection(client) {
2461
+ if (!await hasSettingsTable(client)) return;
2462
+ await client.query("DELETE FROM \"platform_settings\" WHERE \"key\" = $1", [UNINSTALL_SETTING]);
2463
+ }
2464
+ function formatApplications(applications) {
2465
+ const lines = applications.slice(0, LISTED_APPLICATIONS).map((application) => ` ${application.role}: ${application.name ?? "(name not recorded)"}${application.id ? ` (${application.id})` : ""}`);
2466
+ if (applications.length > LISTED_APPLICATIONS) lines.push(` …and ${applications.length - LISTED_APPLICATIONS} more`);
2467
+ return lines.join("\n");
2468
+ }
2469
+ const DASHBOARD_HINT = "To lift sign-in right away, delete these applications in the Cloudflare dashboard under Zero Trust → Access → Applications.";
2470
+ function unfinishedDeletionLines(state) {
2471
+ if (!state.unfinishedDeletions.length) return [];
2472
+ return [`Deletion of ${state.unfinishedDeletions.length === 1 ? "project" : "projects"} ${state.unfinishedDeletions.join(", ")} stopped before it removed their Access applications. Rerun \`void project delete <name>\` for each, or delete it again from the admin dashboard. This removes their applications, including ones already deleted in the Cloudflare dashboard, and Void's records of them.`];
2473
+ }
2474
+ /**
2475
+ * Refuse to uninstall while project Zero Trust still owns Access applications,
2476
+ * or while its state cannot be read. Runs before uninstall changes anything.
2477
+ */
2478
+ async function assertProjectZeroTrustAllowsUninstall(read) {
2479
+ let state;
2480
+ try {
2481
+ if (!read) throw new Error("no platform database connection is available");
2482
+ state = await read();
2483
+ } catch (error) {
2484
+ const reason = (error instanceof Error ? error.message : String(error)).replace(/^platform: /, "");
2485
+ throw new Error([
2486
+ `platform: Refusing to uninstall because the project Zero Trust state could not be read from the platform database: ${reason}`,
2487
+ "Uninstall turns off the platform API, which Void needs to remove the Cloudflare Access applications that project Zero Trust created.",
2488
+ "Make the installation database reachable (export VOID_PLATFORM_DATABASE_URL if this machine has not recorded it), then retry. If project Zero Trust was ever enabled, run `void platform zero-trust disable` first."
2489
+ ].join("\n"));
2490
+ }
2491
+ if (state.active) {
2492
+ const covered = state.destinations.length ? ` on ${state.destinations.join(" and ")}` : " on every hostname under the application domain";
2493
+ throw new Error([
2494
+ "platform: Refusing to uninstall while project Zero Trust is enabled or still changing.",
2495
+ `Uninstall turns off the platform API, so Void could no longer remove its Cloudflare Access applications, and Access would keep asking for sign-in${covered}, including hostnames Void does not serve.`,
2496
+ "Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
2497
+ "If the platform API does not answer, bring it back with `void platform enable` or `void platform repair` first.",
2498
+ ...state.applications.length ? ["Void-managed Access applications:", formatApplications(state.applications)] : [],
2499
+ ...unfinishedDeletionLines(state),
2500
+ DASHBOARD_HINT
2501
+ ].join("\n"));
2502
+ }
2503
+ if (state.applications.length) throw new Error([
2504
+ "platform: Refusing to uninstall while Void-managed Access applications from project Zero Trust remain.",
2505
+ "Zero Trust is disabled, but it could not remove the applications of some projects. Retry the uninstall once they are gone.",
2506
+ "Void-managed Access applications:",
2507
+ formatApplications(state.applications),
2508
+ ...state.applications.some((application) => !application.deleting) ? [`Hourly maintenance retries ${state.unfinishedDeletions.length ? "the other applications" : "them"} while the platform is enabled, and clears Void's records of any you delete in the dashboard.`] : [],
2509
+ ...unfinishedDeletionLines(state),
2510
+ DASHBOARD_HINT
2511
+ ].join("\n"));
2512
+ }
2513
+ //#endregion
2219
2514
  //#region src/cli/platform-installer.ts
2220
2515
  const REMOTE_INSTALLATION_MANIFEST_KEY = PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY;
2221
2516
  const PLATFORM_WORKER_SECRET_UPDATE_NAMES = [
@@ -2530,7 +2825,13 @@ async function preparePlatformDatabaseSchema(manifest, runtime, runtimeDirectory
2530
2825
  }
2531
2826
  async function recoverInterruptedPlatformWorkerCutover(manifest, interruptedCutover, workerActions, configs, previousConfigs, plan, dependencies, ids, zoneId, previousPublicEndpoints, hasEmailGatewayArtifact, runPinnedPlatformWrangler, now, writeRemoteInstallationCheckpoint, onManifestChange) {
2532
2827
  if (interruptedCutover) {
2533
- for (const key of [...interruptedCutover.staged.keys()].reverse()) {
2828
+ for (const key of [
2829
+ "dispatch",
2830
+ "tail",
2831
+ "emailGateway",
2832
+ "proxy",
2833
+ "api"
2834
+ ].filter((key) => interruptedCutover.staged.has(key))) {
2534
2835
  const snapshot = interruptedCutover.staged.get(key);
2535
2836
  const preservedRoutes = dependencies.recoverWorkerCutover ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, snapshot.workerName);
2536
2837
  const recoveryConfig = mergePlatformWorkerTrafficConfig({
@@ -2633,6 +2934,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2633
2934
  const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2634
2935
  const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
2635
2936
  const runPinnedPlatformWrangler = (args, accountId, cwd, capture) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, cloudflareApiBaseUrl);
2937
+ const runParallelPinnedPlatformWrangler = (args, accountId, cwd, capture) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, cloudflareApiBaseUrl, true);
2636
2938
  const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
2637
2939
  const runtimeDirectory = resolvedRuntime.directory;
2638
2940
  const runtime = resolvedRuntime.manifest;
@@ -2724,6 +3026,10 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2724
3026
  writeInstallationManifest(manifest);
2725
3027
  }
2726
3028
  assertRequiredPlatformSecrets(plan, manifest, secrets, !!dependencies.prepareAuthentication);
3029
+ if (hyperdriveAction?.action === "create") {
3030
+ parsePostgresConnectionString(secrets.databaseUrl);
3031
+ assertVoidCreatedHyperdriveTrustsDatabase(secrets.databaseUrl);
3032
+ }
2727
3033
  const pendingSecrets = secretCheckpoint(secrets, workerSecretUpdateIntent);
2728
3034
  writePendingInstallationSecrets(manifest, {
2729
3035
  ...pendingSecrets,
@@ -3075,7 +3381,59 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3075
3381
  };
3076
3382
  writePendingInstallationSecrets(manifest, secretCheckpoint(secrets, workerSecretUpdateIntent));
3077
3383
  }
3078
- for (const key of workerKeys) {
3384
+ const parallelWorkerUploads = !dependencies.deployWorker && !requiresStagedCommit && !workerSecretUpdateIntent && !recoveringUnsafeTraffic && workerActions.length === workerKeys.length && workerActions.every((action) => action.action === "update");
3385
+ let workerManifestQueue = Promise.resolve();
3386
+ let workerManifestFailed = false;
3387
+ let workerManifestFailure;
3388
+ const checkpointWorkerManifest = (update, safetyRecovery = false) => {
3389
+ if (!parallelWorkerUploads) return update();
3390
+ const result = workerManifestQueue.then(async () => {
3391
+ if (workerManifestFailed && !safetyRecovery) throw workerManifestFailure;
3392
+ try {
3393
+ await update();
3394
+ } catch (error) {
3395
+ workerManifestFailed = true;
3396
+ workerManifestFailure = error;
3397
+ throw error;
3398
+ }
3399
+ });
3400
+ workerManifestQueue = result.then(() => void 0, () => void 0);
3401
+ return result;
3402
+ };
3403
+ let availableUploadSlots = 2;
3404
+ let uploadFailure;
3405
+ const uploadWaiters = [];
3406
+ const acquireUploadSlot = async () => {
3407
+ if (uploadFailure !== void 0) throw uploadFailure;
3408
+ if (availableUploadSlots === 0) await new Promise((resolve, reject) => uploadWaiters.push({
3409
+ resolve,
3410
+ reject
3411
+ }));
3412
+ else availableUploadSlots--;
3413
+ return (failure) => {
3414
+ if (failure !== void 0 && uploadFailure === void 0) {
3415
+ uploadFailure = failure;
3416
+ for (const waiter of uploadWaiters.splice(0)) waiter.reject(failure);
3417
+ }
3418
+ const next = uploadFailure === void 0 ? uploadWaiters.shift() : void 0;
3419
+ if (next) next.resolve();
3420
+ else availableUploadSlots++;
3421
+ };
3422
+ };
3423
+ const workerReady = (parallelWorkerUploads ? workerKeys : []).map(() => {
3424
+ let resolve;
3425
+ let reject;
3426
+ return {
3427
+ promise: new Promise((onResolve, onReject) => {
3428
+ resolve = onResolve;
3429
+ reject = onReject;
3430
+ }),
3431
+ resolve,
3432
+ reject
3433
+ };
3434
+ });
3435
+ const uploadBatch = Promise.all(workerReady.map((ready) => ready.promise)).then(() => ({ error: null }), (error) => ({ error }));
3436
+ const deployOneWorker = async (key, waitForPriorWorker, onReadyForActivation) => {
3079
3437
  dependencies.onProgress?.(`Deploying the ${key} service`);
3080
3438
  let workerStepStarted = performance.now();
3081
3439
  const markWorkerStep = (step) => {
@@ -3084,12 +3442,14 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3084
3442
  };
3085
3443
  const action = workerActions.find((entry) => entry.key === (key === "emailGateway" ? "email-gateway-worker" : `${key}-worker`));
3086
3444
  const workerSecrets = platformWorkerSecretPayload(key, workerDeploymentSecrets, plan.account.id, zoneId, action.action !== "create");
3087
- const intended = recordProvisionIntent(manifest, action, now());
3088
- if (intended !== manifest) {
3089
- manifest = intended;
3090
- writeInstallationManifest(manifest);
3091
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3092
- }
3445
+ await checkpointWorkerManifest(async () => {
3446
+ const intended = recordProvisionIntent(manifest, action, now());
3447
+ if (intended !== manifest) {
3448
+ manifest = intended;
3449
+ writeInstallationManifest(manifest);
3450
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3451
+ }
3452
+ });
3093
3453
  await dependencies.revalidateBeforeWorkerDeploy(manifest, action);
3094
3454
  markWorkerStep("revalidate");
3095
3455
  const preservedRoutes = dependencies.deployWorker ? [] : action.action === "create" ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, action.name);
@@ -3098,29 +3458,40 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3098
3458
  const previousWorkerConfig = mergePlatformWorkerTrafficConfig(previousConfigs[key], preservedRoutes);
3099
3459
  const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
3100
3460
  apiBaseUrl: cloudflareApiBaseUrl,
3461
+ ...parallelWorkerUploads ? {
3462
+ acquireUploadSlot,
3463
+ onReadyForActivation,
3464
+ waitForPriorWorker
3465
+ } : {},
3101
3466
  ...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
3102
3467
  onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
3103
- manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3104
- writeInstallationManifest(manifest);
3105
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3468
+ await checkpointWorkerManifest(async () => {
3469
+ manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3470
+ writeInstallationManifest(manifest);
3471
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3472
+ });
3106
3473
  },
3107
3474
  onTrafficPublished: async () => {
3108
3475
  if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
3109
3476
  const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
3110
- const next = recordPlatformWorkerTraffic(manifest, action.name, {
3111
- customDomains,
3112
- routes
3113
- }, now());
3114
- if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
3115
- manifest = next;
3116
- writeInstallationManifest(manifest);
3117
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3118
- }
3477
+ await checkpointWorkerManifest(async () => {
3478
+ const next = recordPlatformWorkerTraffic(manifest, action.name, {
3479
+ customDomains,
3480
+ routes
3481
+ }, now());
3482
+ if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
3483
+ manifest = next;
3484
+ writeInstallationManifest(manifest);
3485
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3486
+ }
3487
+ });
3119
3488
  },
3120
3489
  onUnsafeTraffic: async () => {
3121
- manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
3122
- writeInstallationManifest(manifest);
3123
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3490
+ await checkpointWorkerManifest(async () => {
3491
+ manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
3492
+ writeInstallationManifest(manifest);
3493
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3494
+ }, true);
3124
3495
  },
3125
3496
  managedRoutes: preserveDisabled ? [] : managedRoutes.filter((route) => route.script === action.name).map((route) => route.pattern),
3126
3497
  preservedRoutes,
@@ -3140,19 +3511,37 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3140
3511
  retainNewWorker: async () => {
3141
3512
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3142
3513
  },
3143
- runner: runPinnedPlatformWrangler,
3514
+ runner: parallelWorkerUploads ? runParallelPinnedPlatformWrangler : runPinnedPlatformWrangler,
3144
3515
  workerExisted: action.action !== "create"
3145
3516
  });
3146
3517
  markWorkerStep("deploy");
3147
3518
  if (rollback) workerRollbacks.push(rollback);
3148
- const recorded = recordResource(manifest, action, action.name, "created", now());
3149
- if (rollback || dependencies.deployWorker || JSON.stringify(recorded.resources) !== JSON.stringify(manifest.resources)) {
3150
- manifest = recorded;
3151
- writeInstallationManifest(manifest);
3152
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3153
- }
3519
+ await checkpointWorkerManifest(async () => {
3520
+ const recorded = recordResource(manifest, action, action.name, "created", now());
3521
+ if (rollback || dependencies.deployWorker || JSON.stringify(recorded.resources) !== JSON.stringify(manifest.resources)) {
3522
+ manifest = recorded;
3523
+ writeInstallationManifest(manifest);
3524
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3525
+ }
3526
+ });
3154
3527
  markWorkerStep("record");
3155
- }
3528
+ };
3529
+ if (parallelWorkerUploads) {
3530
+ const tasks = [];
3531
+ for (const [index, key] of workerKeys.entries()) {
3532
+ const priorWorker = tasks.at(-1);
3533
+ const ready = workerReady[index];
3534
+ const task = deployOneWorker(key, priorWorker ? () => priorWorker : void 0, async () => {
3535
+ ready.resolve();
3536
+ const batch = await uploadBatch;
3537
+ if (batch.error !== null) throw batch.error;
3538
+ });
3539
+ task.then(() => ready.resolve(), (error) => ready.reject(error));
3540
+ tasks.push(task);
3541
+ }
3542
+ const firstFailure = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
3543
+ if (firstFailure) throw firstFailure.reason;
3544
+ } else for (const key of workerKeys) await deployOneWorker(key);
3156
3545
  manifest = setInstallationEndpoints(manifest, {
3157
3546
  apiUrl: plan.apiUrl,
3158
3547
  dashboardUrl: null,
@@ -3338,6 +3727,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3338
3727
  retainNewWorker: async () => {
3339
3728
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3340
3729
  },
3730
+ reuseMatchingVersion: false,
3341
3731
  runner: runPinnedPlatformWrangler,
3342
3732
  workerExisted: true
3343
3733
  });
@@ -3361,6 +3751,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3361
3751
  dependencies.onProgress?.("Preparing shared email routing");
3362
3752
  emailOperationId = await (dependencies.bootstrapEmail ?? bootstrapPlatformSharedEmail)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce));
3363
3753
  }
3754
+ await releaseDatabaseLock.liftProjectZeroTrustFence?.();
3364
3755
  if (dependencies.client.deleteKvValue) await dependencies.client.deleteKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY);
3365
3756
  else if (wasDisabled || !previousPublicEndpoints) throw new Error("platform: Routing disablement removal is unavailable.");
3366
3757
  if (requiresStagedCommit) {
@@ -3747,6 +4138,14 @@ function assertRequiredPlatformSecrets(plan, manifest, secrets, authenticationWi
3747
4138
  }
3748
4139
  if (missing.size > 0) throw new Error(`platform: Creating a missing platform Worker requires: ${[...missing].sort().join(", ")}. Restore these values from your secret manager and retry.`);
3749
4140
  }
4141
+ /**
4142
+ * A Hyperdrive that Void creates validates the server against public certificate authorities
4143
+ * only, so it cannot honor a private CA that the local database client would trust.
4144
+ */
4145
+ function assertVoidCreatedHyperdriveTrustsDatabase(databaseUrl) {
4146
+ const sslrootcert = new URL(databaseUrl).searchParams.getAll("sslrootcert").at(-1);
4147
+ if (sslrootcert && sslrootcert !== "system") throw new Error("platform: A Hyperdrive created by Void trusts only public certificate authorities, so it cannot use the certificate authority in DATABASE_URL sslrootcert. If the database certificate comes from a public certificate authority, use sslrootcert=system. Otherwise create a separately managed Hyperdrive that trusts it, then select it with \"Use an existing Hyperdrive\" when you start a new installation, or set VOID_PLATFORM_HYPERDRIVE_ID, VOID_PLATFORM_HYPERDRIVE_ORIGIN_HOST, and VOID_PLATFORM_HYPERDRIVE_ORIGIN_USER.");
4148
+ }
3750
4149
  /** Make pg's current strict TLS behavior explicit without rewriting saved connection details. */
3751
4150
  function platformDatabaseClientUrl(databaseUrl) {
3752
4151
  let url;
@@ -3755,7 +4154,13 @@ function platformDatabaseClientUrl(databaseUrl) {
3755
4154
  } catch {
3756
4155
  return databaseUrl;
3757
4156
  }
3758
- if (url.protocol !== "postgres:" && url.protocol !== "postgresql:" || url.searchParams.getAll("sslmode").at(-1) !== "require" || url.searchParams.getAll("uselibpqcompat").at(-1) === "true") return databaseUrl;
4157
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return databaseUrl;
4158
+ if (url.searchParams.getAll("sslrootcert").at(-1) === "system") {
4159
+ url.searchParams.delete("sslrootcert");
4160
+ url.searchParams.set("sslmode", "verify-full");
4161
+ return url.toString();
4162
+ }
4163
+ if (url.searchParams.getAll("sslmode").at(-1) !== "require" || url.searchParams.getAll("uselibpqcompat").at(-1) === "true") return databaseUrl;
3759
4164
  url.searchParams.set("sslmode", "verify-full");
3760
4165
  return url.toString();
3761
4166
  }
@@ -3812,6 +4217,21 @@ async function readPlatformDatabaseLifecycleManifest(databaseUrl, installationId
3812
4217
  await pool.end();
3813
4218
  }
3814
4219
  }
4220
+ /** Read project Zero Trust state for a plan, which holds no lifecycle lock. */
4221
+ async function readPlatformProjectZeroTrustState(databaseUrl, installationId, ownershipNonce) {
4222
+ if (!databaseUrl) throw new Error("the installation database URL is not recorded on this machine");
4223
+ const { Pool } = await import("pg");
4224
+ const pool = new Pool({
4225
+ connectionString: platformDatabaseClientUrl(databaseUrl),
4226
+ max: 1
4227
+ });
4228
+ try {
4229
+ await readPlatformDatabaseLifecycleManifestConnection(pool, installationId, ownershipNonce);
4230
+ return await readPlatformProjectZeroTrustStateConnection(pool);
4231
+ } finally {
4232
+ await pool.end();
4233
+ }
4234
+ }
3815
4235
  async function inspectPlatformDatabaseStateConnection(client, database) {
3816
4236
  const relation = await client.query(`SELECT to_regclass('"drizzle"."__drizzle_migrations"')::text AS "relation"`);
3817
4237
  if (relation.rows.length === 0 || relation.rows[0]?.relation === null) return {
@@ -3939,6 +4359,10 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
3939
4359
  protection: parsePlatformAccessProtection(settings.protection)
3940
4360
  } : null;
3941
4361
  };
4362
+ release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
4363
+ release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
4364
+ release.fenceProjectZeroTrust = () => fencePlatformProjectZeroTrustConnection(client);
4365
+ release.liftProjectZeroTrustFence = () => liftPlatformProjectZeroTrustFenceConnection(client);
3942
4366
  release.requiredSafetyProtocols = await readRequiredPlatformSafetyProtocols(client);
3943
4367
  release.checkpoint = async (manifest) => {
3944
4368
  if (released) throw new Error("platform: Cannot checkpoint lifecycle state after releasing its lock.");
@@ -4269,24 +4693,26 @@ function snapshotPlatformWorkerUploadInputs(config, directory) {
4269
4693
  function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
4270
4694
  const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
4271
4695
  return async (args, account, cwd, capture) => {
4272
- if (args[0] === "deployments" && (args[1] === "status" || args[1] === "list")) {
4273
- const response = await readCloudflare(`${base}/deployments`);
4274
- const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
4275
- if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
4276
- return {
4277
- status: args[1] === "status" && deployments.length === 0 ? 1 : 0,
4278
- stderr: "",
4279
- stdout: JSON.stringify(args[1] === "status" ? deployments[0] : deployments)
4280
- };
4281
- }
4282
- if (args[0] === "versions" && args[1] === "view" && args[2]) {
4283
- const version = await readCloudflare(`${base}/versions/${encodeURIComponent(args[2])}`);
4284
- return {
4285
- status: 0,
4286
- stderr: "",
4287
- stdout: JSON.stringify(version)
4288
- };
4289
- }
4696
+ try {
4697
+ if (args[0] === "deployments" && (args[1] === "status" || args[1] === "list")) {
4698
+ const response = await readCloudflare(`${base}/deployments`);
4699
+ const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
4700
+ if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
4701
+ return {
4702
+ status: args[1] === "status" && deployments.length === 0 ? 1 : 0,
4703
+ stderr: "",
4704
+ stdout: JSON.stringify(args[1] === "status" ? deployments[0] : deployments)
4705
+ };
4706
+ }
4707
+ if (args[0] === "versions" && args[1] === "view" && args[2]) {
4708
+ const version = await readCloudflare(`${base}/versions/${encodeURIComponent(args[2])}`);
4709
+ return {
4710
+ status: 0,
4711
+ stderr: "",
4712
+ stdout: JSON.stringify(version)
4713
+ };
4714
+ }
4715
+ } catch {}
4290
4716
  return fallback(args, account, cwd, capture);
4291
4717
  };
4292
4718
  }
@@ -4294,6 +4720,7 @@ async function deployPlatformWorker(config, secrets, options) {
4294
4720
  const directory = mkdtempSync(join(tmpdir(), "void-platform-deploy-"));
4295
4721
  const workerName = String(config.name);
4296
4722
  const accountId = String(config.account_id);
4723
+ const readRunner = options.readCloudflare ? platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner) : options.runner;
4297
4724
  let activatedVersionId = null;
4298
4725
  let activatedDeploymentId = null;
4299
4726
  let uploadedVersionId = null;
@@ -4305,13 +4732,22 @@ async function deployPlatformWorker(config, secrets, options) {
4305
4732
  let uploadConfig = withoutPlatformWorkerTrafficAttachments(nextTraffic.wranglerConfig);
4306
4733
  const nextTriggers = readTriggerConfigFromObject(nextTraffic.wranglerConfig);
4307
4734
  const contentHash = secrets === null ? platformWorkerUploadFingerprint(config, options.managedRoutes) : null;
4735
+ const reuseMatchingVersion = options.reuseMatchingVersion !== false;
4308
4736
  let previousTriggers = readTriggerConfigFromObject(previousTraffic.wranglerConfig);
4309
4737
  try {
4310
4738
  const configPath = join(directory, "wrangler.json");
4311
4739
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4312
4740
  const reconcileExistingVersion = async (versionId, triggerConfig, runner, deploymentId) => {
4313
4741
  try {
4314
- assertPlatformTriggerStabilization(await stabilizeCloudflareVersionTriggers({
4742
+ if (deploymentId && options.readCloudflare ? await platformWorkerLiveTriggersMatch({
4743
+ accountId,
4744
+ workerName,
4745
+ desired: nextTriggers,
4746
+ readCloudflare: options.readCloudflare
4747
+ }) : false) {
4748
+ const beforeTraffic = await readPlatformWorkerDeployments(configPath, accountId, workerName, runner);
4749
+ if (beforeTraffic[0]?.id !== deploymentId || !deploymentRunsOnlyVersion(beforeTraffic[0], versionId)) throw new Error(`platform: Worker ${workerName} changed deployment lineage during trigger verification.`);
4750
+ } else assertPlatformTriggerStabilization(await stabilizeCloudflareVersionTriggers({
4315
4751
  configPath,
4316
4752
  knownTriggerConfigs: { [versionId]: nextTriggers },
4317
4753
  manageRoutes: false,
@@ -4344,16 +4780,15 @@ async function deployPlatformWorker(config, secrets, options) {
4344
4780
  throw new PlatformWorkerRecoveryIncompleteError(`platform: Worker ${workerName} traffic reconciliation failed: ${errorMessage(error)}${failures.length > 0 ? ` Safety recovery also failed: ${failures.join("; ")}` : ""}`, { cause: error });
4345
4781
  }
4346
4782
  };
4347
- if (options.workerExisted && contentHash && options.readCloudflare) {
4348
- const directRunner = platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner);
4783
+ if (options.workerExisted && contentHash && reuseMatchingVersion && options.readCloudflare) {
4349
4784
  let candidate;
4350
4785
  let changedDuringRead = false;
4351
4786
  try {
4352
- const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, directRunner);
4787
+ const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4353
4788
  const activeVersion = before[0]?.versions.length === 1 && before[0].versions[0]?.percentage === 100 ? before[0].versions[0].versionId : null;
4354
4789
  if (!activeVersion) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
4355
- const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, directRunner);
4356
- const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, directRunner);
4790
+ const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, readRunner);
4791
+ const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4357
4792
  changedDuringRead = before[0]?.id !== after[0]?.id || !deploymentRunsOnlyVersion(after[0], snapshot.versionId);
4358
4793
  if (!changedDuringRead && snapshot.contentHash === contentHash) candidate = {
4359
4794
  deploymentId: after[0].id,
@@ -4363,16 +4798,24 @@ async function deployPlatformWorker(config, secrets, options) {
4363
4798
  if (error instanceof Error && (error.message.includes("changed concurrently") || error.message.includes("does not belong to this installation"))) throw error;
4364
4799
  }
4365
4800
  if (changedDuringRead) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
4366
- if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) return await reconcileExistingVersion(candidate.snapshot.versionId, candidate.snapshot.triggerConfig, directRunner, candidate.deploymentId);
4801
+ if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
4802
+ await options.onReadyForActivation?.();
4803
+ await options.waitForPriorWorker?.();
4804
+ return await reconcileExistingVersion(candidate.snapshot.versionId, candidate.snapshot.triggerConfig, readRunner, candidate.deploymentId);
4805
+ }
4367
4806
  }
4368
4807
  if (options.workerExisted) {
4369
- const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), options.runner);
4808
+ const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), readRunner);
4370
4809
  previousVersionId = previous.versionId;
4371
- const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4810
+ const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4372
4811
  if (!deploymentRunsOnlyVersion(deployments[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
4373
4812
  previousDeployment = deployments[0];
4374
4813
  previousDeploymentId = previousDeployment.id;
4375
- if (contentHash && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) return await reconcileExistingVersion(previousVersionId, previous.triggerConfig, options.runner);
4814
+ if (contentHash && reuseMatchingVersion && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
4815
+ await options.onReadyForActivation?.();
4816
+ await options.waitForPriorWorker?.();
4817
+ return await reconcileExistingVersion(previousVersionId, previous.triggerConfig, readRunner);
4818
+ }
4376
4819
  uploadConfig = withoutCompletedPlatformWorkerMigrations(uploadConfig, previous.migrationTag);
4377
4820
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4378
4821
  previousTriggers = resolvePlatformPreviousTriggerConfig(withoutPlatformCustomDomainTriggers(previous.triggerConfig), previousTriggers, options.previousPublicEndpoints);
@@ -4404,14 +4847,26 @@ async function deployPlatformWorker(config, secrets, options) {
4404
4847
  writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
4405
4848
  argv.push("--secrets-file", secretsPath);
4406
4849
  }
4407
- const uploaded = options.workerExisted ? await options.runner(argv, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(argv, accountId, directory, true, workerName, options.apiBaseUrl);
4850
+ const releaseUploadSlot = await options.acquireUploadSlot?.();
4851
+ let uploaded;
4852
+ let uploadError;
4853
+ try {
4854
+ uploaded = options.workerExisted ? await options.runner(argv, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(argv, accountId, directory, true, workerName, options.apiBaseUrl);
4855
+ assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
4856
+ } catch (error) {
4857
+ uploadError = error;
4858
+ throw error;
4859
+ } finally {
4860
+ releaseUploadSlot?.(uploadError);
4861
+ }
4408
4862
  uploadedVersionId = parseUploadedPlatformWorkerVersion(`${uploaded.stdout}\n${uploaded.stderr}`);
4409
4863
  if (!options.workerExisted) activatedVersionId = uploadedVersionId;
4410
- assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
4411
4864
  if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
4412
4865
  await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
4866
+ await options.onReadyForActivation?.();
4867
+ await options.waitForPriorWorker?.();
4413
4868
  if (options.workerExisted) {
4414
- const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4869
+ const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4415
4870
  if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
4416
4871
  activatedVersionId = uploadedVersionId;
4417
4872
  assertCleanPlatformWrangler(await options.runner([
@@ -4425,7 +4880,7 @@ async function deployPlatformWorker(config, secrets, options) {
4425
4880
  "--yes"
4426
4881
  ], accountId, directory, true), `activate ${workerName}`);
4427
4882
  }
4428
- const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4883
+ const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4429
4884
  const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
4430
4885
  if (lineage.kind === "concurrent-after") {
4431
4886
  activatedVersionId = null;
@@ -4445,7 +4900,7 @@ async function deployPlatformWorker(config, secrets, options) {
4445
4900
  expectedActiveDeploymentId: lineage.deploymentId,
4446
4901
  expectedActiveVersionId: uploadedVersionId,
4447
4902
  restoration: interveningDeployment,
4448
- runner: options.runner,
4903
+ runner: readRunner,
4449
4904
  workerName
4450
4905
  });
4451
4906
  } catch (recoveryError) {
@@ -4471,7 +4926,7 @@ async function deployPlatformWorker(config, secrets, options) {
4471
4926
  await options.removeCustomDomains();
4472
4927
  return cleanPlatformCommandResult();
4473
4928
  },
4474
- runWrangler: platformWorkerCommandRunner(accountId, directory, options.runner),
4929
+ runWrangler: platformWorkerCommandRunner(accountId, directory, readRunner),
4475
4930
  targetTriggerConfig: nextTriggers,
4476
4931
  targetVersionId: uploadedVersionId,
4477
4932
  workerName
@@ -4495,7 +4950,7 @@ async function deployPlatformWorker(config, secrets, options) {
4495
4950
  removeCustomDomains: options.removeCustomDomains,
4496
4951
  publishCustomDomains: options.publishCustomDomains,
4497
4952
  retainNewWorker: options.retainNewWorker,
4498
- runner: options.runner,
4953
+ runner: readRunner,
4499
4954
  workerExisted: options.workerExisted,
4500
4955
  workerName
4501
4956
  });
@@ -4522,7 +4977,7 @@ async function deployPlatformWorker(config, secrets, options) {
4522
4977
  removeCustomDomains: options.removeCustomDomains,
4523
4978
  publishCustomDomains: options.publishCustomDomains,
4524
4979
  retainNewWorker: options.retainNewWorker,
4525
- runner: options.runner,
4980
+ runner: readRunner,
4526
4981
  workerExisted: options.workerExisted,
4527
4982
  workerName
4528
4983
  }) : null,
@@ -4623,10 +5078,11 @@ async function runPlatformWrangler(args, accountId, cwd, capture) {
4623
5078
  return runPlatformWranglerAtApiBase(args, accountId, cwd, capture);
4624
5079
  }
4625
5080
  const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
4626
- async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl()) {
5081
+ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
4627
5082
  const startedAt = performance.now();
4628
5083
  const result = await createDefaultRunWrangler(process.cwd(), {
4629
5084
  launcher: resolveWranglerArgv(process.cwd()),
5085
+ parallelWorkerUpload,
4630
5086
  timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
4631
5087
  })([...args], {
4632
5088
  accountId,
@@ -4676,8 +5132,11 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4676
5132
  const upstreamBase = new URL(upstreamApiBaseUrl);
4677
5133
  if (upstreamBase.protocol !== "http:" && upstreamBase.protocol !== "https:") throw new Error("platform: Cloudflare API base URL must use HTTP or HTTPS.");
4678
5134
  const basePath = upstreamBase.pathname.replace(/\/+$/, "");
4679
- const expectedPath = `${basePath}/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
5135
+ const accountPath = `${basePath}/accounts/${encodeURIComponent(accountId)}`;
5136
+ const expectedPath = `${accountPath}/workers/scripts/${encodeURIComponent(workerName)}`;
5137
+ const assetUploadPath = `${accountPath}/workers/assets/upload`;
4680
5138
  const state = {
5139
+ assetSessionStarted: false,
4681
5140
  attempted: false,
4682
5141
  conflictStatus: null,
4683
5142
  created: false,
@@ -4697,7 +5156,9 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4697
5156
  const isConditionalCreate = incoming.method === "PUT" && incomingUrl.pathname === expectedPath && supportedCreateQuery;
4698
5157
  const isTopLevelWorkerUpload = incoming.method === "PUT" && new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/accounts/[^/]+/workers/scripts/[^/]+$`).test(incomingUrl.pathname);
4699
5158
  const isReadOnly = incoming.method === "GET" || incoming.method === "HEAD" || incoming.method === "OPTIONS";
4700
- if (isTopLevelWorkerUpload && !isConditionalCreate || !isReadOnly && !isConditionalCreate && !state.created) {
5159
+ const isAssetUploadSession = incoming.method === "POST" && incomingUrl.pathname === `${expectedPath}/assets-upload-session` && incomingUrl.search === "";
5160
+ const isAssetUpload = incoming.method === "POST" && state.assetSessionStarted && (incomingUrl.pathname === assetUploadPath && incomingUrl.search === "?base64=true" || incomingUrl.search === "" && incomingUrl.pathname.startsWith(`${assetUploadPath}/`) && /^[0-9a-f]{32}$/.test(incomingUrl.pathname.slice(assetUploadPath.length + 1)));
5161
+ if (isTopLevelWorkerUpload && !isConditionalCreate || !isReadOnly && !isConditionalCreate && !isAssetUploadSession && !isAssetUpload && !state.created) {
4701
5162
  state.rejectedMutation = `${incoming.method ?? "UNKNOWN"} ${incomingUrl.pathname}${incomingUrl.pathname === expectedPath && !supportedCreateQuery ? " (unsupported Worker create query)" : ""}`;
4702
5163
  incoming.resume();
4703
5164
  outgoing.writeHead(409, { "Content-Type": "application/json" });
@@ -4710,6 +5171,7 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4710
5171
  state.attempted = true;
4711
5172
  headers["if-none-match"] = "*";
4712
5173
  }
5174
+ if (isAssetUploadSession) state.assetSessionStarted = true;
4713
5175
  const upstreamUrl = new URL(`${incomingUrl.pathname}${incomingUrl.search}`, upstreamBase.origin);
4714
5176
  const request$2 = (upstreamUrl.protocol === "https:" ? request$1 : request)(upstreamUrl, {
4715
5177
  headers,
@@ -5214,7 +5676,7 @@ async function verifyPlatformHealth(apiUrl, installationId, proxyUrl, applicatio
5214
5676
  redirect: "manual",
5215
5677
  signal: AbortSignal.timeout(3e4)
5216
5678
  });
5217
- const dispatchUrl = dispatchOrigin ?? `https://test.${applicationBaseDomain}`;
5679
+ const dispatchUrl = dispatchOrigin ?? `https://${PLATFORM_HEALTH_HOSTNAME_LABEL}.${applicationBaseDomain}`;
5218
5680
  const [health, discovery, proxy, dispatch, emailGateway] = await Promise.all([
5219
5681
  request(`${apiUrl}/health`),
5220
5682
  request(`${apiUrl}/.well-known/void-platform`),
@@ -5816,14 +6278,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
5816
6278
  }
5817
6279
  async function runPlatformLifecycleCommand(args) {
5818
6280
  if (args.subcommand === "domain") {
5819
- const { runPlatformDomainCommand } = await import("./platform-domain-CU1JtJkW.mjs");
6281
+ const { runPlatformDomainCommand } = await import("./platform-domain-D6Xcy9ZX.mjs");
5820
6282
  await runPlatformDomainCommand(args);
5821
6283
  return;
5822
6284
  }
5823
6285
  if (args.subcommand === "discover") {
5824
6286
  const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
5825
6287
  const accountId = await selectAccount(await readAccounts(client), args.account);
5826
- const { recoverPlatformInstallations } = await import("./platform-recovery-dvUaptLr.mjs");
6288
+ const { recoverPlatformInstallations } = await import("./platform-recovery-BvtcXON5.mjs");
5827
6289
  const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
5828
6290
  if (recovered.length === 0) {
5829
6291
  log.info(`No recoverable Void platform installations were found in ${accountId}.`);
@@ -5833,7 +6295,7 @@ async function runPlatformLifecycleCommand(args) {
5833
6295
  return;
5834
6296
  }
5835
6297
  if (args.subcommand !== "install") {
5836
- const { runInstalledPlatformCommand } = await import("./platform-management-C8tt6D8h.mjs");
6298
+ const { runInstalledPlatformCommand } = await import("./platform-management-DOtss0BN.mjs");
5837
6299
  await runInstalledPlatformCommand(args);
5838
6300
  return;
5839
6301
  }
@@ -6057,7 +6519,7 @@ async function runPlatformLifecycleCommand(args) {
6057
6519
  progress?.stop("Read the saved installation.");
6058
6520
  progress = void 0;
6059
6521
  if (authoritative) {
6060
- const { selectAuthoritativePlatformManifest } = await import("./platform-management-C8tt6D8h.mjs");
6522
+ const { selectAuthoritativePlatformManifest } = await import("./platform-management-DOtss0BN.mjs");
6061
6523
  const selected = selectAuthoritativePlatformManifest(existing, authoritative);
6062
6524
  authoritativeProvisionIntents = true;
6063
6525
  if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
@@ -6286,7 +6748,7 @@ async function runPlatformLifecycleCommand(args) {
6286
6748
  })).actions.filter((action) => action.kind === "worker"),
6287
6749
  revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
6288
6750
  verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
6289
- const { verifyPlatformInstallationOwnership } = await import("./platform-management-C8tt6D8h.mjs");
6751
+ const { verifyPlatformInstallationOwnership } = await import("./platform-management-DOtss0BN.mjs");
6290
6752
  const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
6291
6753
  const previous = manifest.resources.find((resource) => resource.key === action.key);
6292
6754
  return action.action === "create" && previous && previous.name !== action.name;
@@ -6426,4 +6888,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
6426
6888
  return value;
6427
6889
  }
6428
6890
  //#endregion
6429
- export { hyperdriveOriginFromConnectionString as A, assertPlatformOperatorCredentialCanApplyPlan as C, preparePlatformAccessSetup as D, parsePlatformAccessSetupSpec as E, verifyPlatformRuntimeTokenPermissions as M, PlatformCloudflareClient as O, platformDatabaseNameFromOwnerUrl as S, checkpointPlatformAccessResources as T, createPlatformInstallPlan as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, platformProvisionIntentHash as b, createPlatformHealthProbeToken as c, loadPlatformRuntime as d, platformDatabaseClientUrl as f, trackedPlatformWorkerTraffic as g, recordPlatformWorkerTraffic as h, verifyPlatformAiGatewayPlan as i, hyperdriveOriginMatches as j, hyperdriveOriginConfigHash as k, createPlatformInstallationSecrets as l, resolveBootstrapGitHubId as m, resolvePlatformInstallDatabaseUrl as n, acquirePlatformDatabaseLifecycleLock as o, recordResource as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLockConnection as s, assertNoninteractivePlatformMutationAcknowledged as t, installPlatform as u, formatPlatformInstallPlan as v, resolvePlatformCloudflareCredential as w, revalidatePlatformInstallAction as x, platformAdoptionIntentHash as y };
6891
+ export { PlatformCloudflareClient as A, revalidatePlatformInstallAction as C, checkpointPlatformAccessResources as D, resolvePlatformCloudflareCredential as E, hyperdriveOriginFromConnectionString as M, hyperdriveOriginMatches as N, parsePlatformAccessSetupSpec as O, verifyPlatformRuntimeTokenPermissions as P, platformProvisionIntentHash as S, assertPlatformOperatorCredentialCanApplyPlan as T, recordPlatformWorkerTraffic as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, formatPlatformInstallPlan as b, createPlatformHealthProbeToken as c, loadPlatformRuntime as d, platformDatabaseClientUrl as f, assertProjectZeroTrustAllowsUninstall as g, resolveBootstrapGitHubId as h, verifyPlatformAiGatewayPlan as i, hyperdriveOriginConfigHash as j, preparePlatformAccessSetup as k, createPlatformInstallationSecrets as l, recordResource as m, resolvePlatformInstallDatabaseUrl as n, acquirePlatformDatabaseLifecycleLock as o, readPlatformProjectZeroTrustState as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLockConnection as s, assertNoninteractivePlatformMutationAcknowledged as t, installPlatform as u, trackedPlatformWorkerTraffic as v, platformDatabaseNameFromOwnerUrl as w, platformAdoptionIntentHash as x, createPlatformInstallPlan as y };