void 0.21.8 → 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-CQ2wi4Th.mjs → account-cmd-CjyxcGkM.mjs} +4 -4
  2. package/dist/{auth-link-B_lceSHK.mjs → auth-link-BLO4ptzo.mjs} +4 -4
  3. package/dist/{auth-router-cZq0HuWe.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 -42
  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-BY38i-9E.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-BIo37Y7H.mjs → db-gp2sCXyN.mjs} +14 -14
  27. package/dist/{delete-D6dZ9B6B.mjs → delete-TTedbH6B.mjs} +3 -3
  28. package/dist/{deploy-BM_LoBXC.mjs → deploy-B19L2Bra.mjs} +1 -1
  29. package/dist/{deploy-C0JaQSWS.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-dMnE_Lat.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-CJSnEcuj.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-DZldyb8c.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-B9dbG3Od.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-Bex45lQt.mjs → platform-auth-config-BlN8xTdD.mjs} +3 -3
  67. package/dist/{platform-auth-protection-Dma02l6-.mjs → platform-auth-protection-7d5aV2Jg.mjs} +3 -3
  68. package/dist/{platform-auth-recovery-COOhMCvx.mjs → platform-auth-recovery-Dxij8ZbR.mjs} +3 -3
  69. package/dist/{platform-cmd-BtxZrT6w.mjs → platform-cmd-B3hwKrFK.mjs} +5 -5
  70. package/dist/{platform-cmd-CjNB09OF.mjs → platform-cmd-E0FuL212.mjs} +1 -1
  71. package/dist/{platform-domain-DppaTARe.mjs → platform-domain-D6Xcy9ZX.mjs} +36 -9
  72. package/dist/{platform-lifecycle-CYll-oKE.mjs → platform-lifecycle-DMh_qrry.mjs} +1 -1
  73. package/dist/{platform-lifecycle-BpDlxT3t.mjs → platform-lifecycle-k0E0xoxx.mjs} +769 -59
  74. package/dist/{platform-management-BKYenDlE.mjs → platform-management-Brz_BDiT.mjs} +72 -10
  75. package/dist/{platform-management-BS5r6Ao8.mjs → platform-management-DOtss0BN.mjs} +1 -1
  76. package/dist/{platform-recovery-dJ5Me-I0.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-t6rndSRW.mjs +0 -2
  131. package/dist/migrate-TsHGBnDA.mjs +0 -2
  132. package/dist/prepare-BZXkjdNe.mjs +0 -2
@@ -1,19 +1,21 @@
1
1
  import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
2
- import { a as join } 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";
2
+ import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.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";
12
- import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
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";
13
+ import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { fileURLToPath } from "node:url";
13
15
  import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
14
16
  import { tmpdir } from "node:os";
15
17
  import { execFile } from "node:child_process";
16
- 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";
17
19
  import { stripVTControlCharacters } from "node:util";
18
20
  import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
19
21
  import { readFile } from "node:fs/promises";
@@ -651,7 +653,7 @@ function parsePostgresConnectionString(value) {
651
653
  let mtls;
652
654
  if (sslmode) {
653
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\".");
654
- mtls = { sslmode };
656
+ mtls = { sslmode: "require" };
655
657
  }
656
658
  return {
657
659
  database,
@@ -755,6 +757,11 @@ var PlatformAccessClient = class {
755
757
  throw new Error("platform: Access inventory exceeded the supported pagination limit.");
756
758
  }
757
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
+ */
758
765
  function identityRule(rule) {
759
766
  if (rule.email_domain) {
760
767
  const domain = object(rule.email_domain, "email domain").domain;
@@ -1410,7 +1417,6 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
1410
1417
  input,
1411
1418
  credential: result.credential
1412
1419
  };
1413
- announceCloudflareLogin();
1414
1420
  const login = await runWrangler(["login"], false);
1415
1421
  assertNotCancelled();
1416
1422
  if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
@@ -1499,7 +1505,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
1499
1505
  }
1500
1506
  const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
1501
1507
  if (existingWorkers.length) {
1502
- const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-Dma02l6-.mjs");
1508
+ const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-7d5aV2Jg.mjs");
1503
1509
  await verifyProtectionWorkerOrigins({
1504
1510
  request: input.client.request.bind(input.client),
1505
1511
  listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
@@ -1830,6 +1836,18 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1830
1836
  };
1831
1837
  }
1832
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
+ }
1833
1851
  const deployment = original.deployment;
1834
1852
  const current = (await createPlatformInstallPlan(client, {
1835
1853
  accountId: original.account.id,
@@ -2215,6 +2233,284 @@ function recordPlatformWorkerTraffic(manifest, workerName, inventory, updatedAt)
2215
2233
  return next;
2216
2234
  }
2217
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
2218
2514
  //#region src/cli/platform-installer.ts
2219
2515
  const REMOTE_INSTALLATION_MANIFEST_KEY = PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY;
2220
2516
  const PLATFORM_WORKER_SECRET_UPDATE_NAMES = [
@@ -2529,7 +2825,13 @@ async function preparePlatformDatabaseSchema(manifest, runtime, runtimeDirectory
2529
2825
  }
2530
2826
  async function recoverInterruptedPlatformWorkerCutover(manifest, interruptedCutover, workerActions, configs, previousConfigs, plan, dependencies, ids, zoneId, previousPublicEndpoints, hasEmailGatewayArtifact, runPinnedPlatformWrangler, now, writeRemoteInstallationCheckpoint, onManifestChange) {
2531
2827
  if (interruptedCutover) {
2532
- 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))) {
2533
2835
  const snapshot = interruptedCutover.staged.get(key);
2534
2836
  const preservedRoutes = dependencies.recoverWorkerCutover ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, snapshot.workerName);
2535
2837
  const recoveryConfig = mergePlatformWorkerTrafficConfig({
@@ -2632,6 +2934,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2632
2934
  const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2633
2935
  const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
2634
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);
2635
2938
  const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
2636
2939
  const runtimeDirectory = resolvedRuntime.directory;
2637
2940
  const runtime = resolvedRuntime.manifest;
@@ -2723,6 +3026,10 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2723
3026
  writeInstallationManifest(manifest);
2724
3027
  }
2725
3028
  assertRequiredPlatformSecrets(plan, manifest, secrets, !!dependencies.prepareAuthentication);
3029
+ if (hyperdriveAction?.action === "create") {
3030
+ parsePostgresConnectionString(secrets.databaseUrl);
3031
+ assertVoidCreatedHyperdriveTrustsDatabase(secrets.databaseUrl);
3032
+ }
2726
3033
  const pendingSecrets = secretCheckpoint(secrets, workerSecretUpdateIntent);
2727
3034
  writePendingInstallationSecrets(manifest, {
2728
3035
  ...pendingSecrets,
@@ -3074,38 +3381,117 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3074
3381
  };
3075
3382
  writePendingInstallationSecrets(manifest, secretCheckpoint(secrets, workerSecretUpdateIntent));
3076
3383
  }
3077
- 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) => {
3078
3437
  dependencies.onProgress?.(`Deploying the ${key} service`);
3438
+ let workerStepStarted = performance.now();
3439
+ const markWorkerStep = (step) => {
3440
+ if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] worker ${key} ${step} ${(performance.now() - workerStepStarted).toFixed(0)} ms\n`);
3441
+ workerStepStarted = performance.now();
3442
+ };
3079
3443
  const action = workerActions.find((entry) => entry.key === (key === "emailGateway" ? "email-gateway-worker" : `${key}-worker`));
3080
3444
  const workerSecrets = platformWorkerSecretPayload(key, workerDeploymentSecrets, plan.account.id, zoneId, action.action !== "create");
3081
- manifest = recordProvisionIntent(manifest, action, now());
3082
- writeInstallationManifest(manifest);
3083
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
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
+ });
3084
3453
  await dependencies.revalidateBeforeWorkerDeploy(manifest, action);
3454
+ markWorkerStep("revalidate");
3085
3455
  const preservedRoutes = dependencies.deployWorker ? [] : action.action === "create" ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, action.name);
3456
+ markWorkerStep("traffic inventory");
3086
3457
  const workerConfig = mergePlatformWorkerTrafficConfig(configs[key], preservedRoutes);
3087
3458
  const previousWorkerConfig = mergePlatformWorkerTrafficConfig(previousConfigs[key], preservedRoutes);
3088
3459
  const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
3089
3460
  apiBaseUrl: cloudflareApiBaseUrl,
3461
+ ...parallelWorkerUploads ? {
3462
+ acquireUploadSlot,
3463
+ onReadyForActivation,
3464
+ waitForPriorWorker
3465
+ } : {},
3466
+ ...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
3090
3467
  onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
3091
- manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3092
- writeInstallationManifest(manifest);
3093
- 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
+ });
3094
3473
  },
3095
3474
  onTrafficPublished: async () => {
3096
3475
  if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
3097
3476
  const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
3098
- manifest = recordPlatformWorkerTraffic(manifest, action.name, {
3099
- customDomains,
3100
- routes
3101
- }, now());
3102
- writeInstallationManifest(manifest);
3103
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
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
+ });
3104
3488
  },
3105
3489
  onUnsafeTraffic: async () => {
3106
- manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
3107
- writeInstallationManifest(manifest);
3108
- 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);
3109
3495
  },
3110
3496
  managedRoutes: preserveDisabled ? [] : managedRoutes.filter((route) => route.script === action.name).map((route) => route.pattern),
3111
3497
  preservedRoutes,
@@ -3125,14 +3511,37 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3125
3511
  retainNewWorker: async () => {
3126
3512
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3127
3513
  },
3128
- runner: runPinnedPlatformWrangler,
3514
+ runner: parallelWorkerUploads ? runParallelPinnedPlatformWrangler : runPinnedPlatformWrangler,
3129
3515
  workerExisted: action.action !== "create"
3130
3516
  });
3517
+ markWorkerStep("deploy");
3131
3518
  if (rollback) workerRollbacks.push(rollback);
3132
- manifest = recordResource(manifest, action, action.name, "created", now());
3133
- writeInstallationManifest(manifest);
3134
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3135
- }
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
+ });
3527
+ markWorkerStep("record");
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);
3136
3545
  manifest = setInstallationEndpoints(manifest, {
3137
3546
  apiUrl: plan.apiUrl,
3138
3547
  dashboardUrl: null,
@@ -3276,6 +3685,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3276
3685
  const previousWorkerConfig = mergePlatformWorkerTrafficConfig(configs[key], preservedRoutes);
3277
3686
  const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
3278
3687
  apiBaseUrl: cloudflareApiBaseUrl,
3688
+ ...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
3279
3689
  onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
3280
3690
  manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3281
3691
  writeInstallationManifest(manifest);
@@ -3284,12 +3694,15 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3284
3694
  onTrafficPublished: async () => {
3285
3695
  if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
3286
3696
  const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
3287
- manifest = recordPlatformWorkerTraffic(manifest, action.name, {
3697
+ const next = recordPlatformWorkerTraffic(manifest, action.name, {
3288
3698
  customDomains,
3289
3699
  routes
3290
3700
  }, now());
3291
- writeInstallationManifest(manifest);
3292
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3701
+ if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
3702
+ manifest = next;
3703
+ writeInstallationManifest(manifest);
3704
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3705
+ }
3293
3706
  },
3294
3707
  onUnsafeTraffic: async () => {
3295
3708
  manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
@@ -3314,6 +3727,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3314
3727
  retainNewWorker: async () => {
3315
3728
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3316
3729
  },
3730
+ reuseMatchingVersion: false,
3317
3731
  runner: runPinnedPlatformWrangler,
3318
3732
  workerExisted: true
3319
3733
  });
@@ -3337,6 +3751,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3337
3751
  dependencies.onProgress?.("Preparing shared email routing");
3338
3752
  emailOperationId = await (dependencies.bootstrapEmail ?? bootstrapPlatformSharedEmail)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce));
3339
3753
  }
3754
+ await releaseDatabaseLock.liftProjectZeroTrustFence?.();
3340
3755
  if (dependencies.client.deleteKvValue) await dependencies.client.deleteKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY);
3341
3756
  else if (wasDisabled || !previousPublicEndpoints) throw new Error("platform: Routing disablement removal is unavailable.");
3342
3757
  if (requiresStagedCommit) {
@@ -3407,10 +3822,14 @@ function saveInstalledPlatformConnection(plan, manifest) {
3407
3822
  });
3408
3823
  }
3409
3824
  async function writeRemoteInstallationCheckpointWithoutDatabase(client, plan, manifest, databaseLock) {
3825
+ const startedAt = performance.now();
3410
3826
  await databaseLock?.checkpoint?.(manifest);
3827
+ if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] database checkpoint ${(performance.now() - startedAt).toFixed(0)} ms\n`);
3411
3828
  const routing = manifest.resources.find((resource) => resource.key === "routing-kv" && resource.kind === "kv-namespace" && resource.ownership === "created" && resource.state === "ready");
3412
3829
  if (!routing) return;
3830
+ const kvStartedAt = performance.now();
3413
3831
  await client.putKvValue(plan.account.id, routing.id, REMOTE_INSTALLATION_MANIFEST_KEY, JSON.stringify(manifest));
3832
+ if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] remote checkpoint ${(performance.now() - kvStartedAt).toFixed(0)} ms\n`);
3414
3833
  }
3415
3834
  function recordProvisionIntent(manifest, action, now) {
3416
3835
  const previousResourceId = manifest.resources.find((resource) => resource.key === action.key)?.id ?? null;
@@ -3719,6 +4138,14 @@ function assertRequiredPlatformSecrets(plan, manifest, secrets, authenticationWi
3719
4138
  }
3720
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.`);
3721
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
+ }
3722
4149
  /** Make pg's current strict TLS behavior explicit without rewriting saved connection details. */
3723
4150
  function platformDatabaseClientUrl(databaseUrl) {
3724
4151
  let url;
@@ -3727,7 +4154,13 @@ function platformDatabaseClientUrl(databaseUrl) {
3727
4154
  } catch {
3728
4155
  return databaseUrl;
3729
4156
  }
3730
- 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;
3731
4164
  url.searchParams.set("sslmode", "verify-full");
3732
4165
  return url.toString();
3733
4166
  }
@@ -3784,6 +4217,21 @@ async function readPlatformDatabaseLifecycleManifest(databaseUrl, installationId
3784
4217
  await pool.end();
3785
4218
  }
3786
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
+ }
3787
4235
  async function inspectPlatformDatabaseStateConnection(client, database) {
3788
4236
  const relation = await client.query(`SELECT to_regclass('"drizzle"."__drizzle_migrations"')::text AS "relation"`);
3789
4237
  if (relation.rows.length === 0 || relation.rows[0]?.relation === null) return {
@@ -3911,6 +4359,10 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
3911
4359
  protection: parsePlatformAccessProtection(settings.protection)
3912
4360
  } : null;
3913
4361
  };
4362
+ release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
4363
+ release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
4364
+ release.fenceProjectZeroTrust = () => fencePlatformProjectZeroTrustConnection(client);
4365
+ release.liftProjectZeroTrustFence = () => liftPlatformProjectZeroTrustFenceConnection(client);
3914
4366
  release.requiredSafetyProtocols = await readRequiredPlatformSafetyProtocols(client);
3915
4367
  release.checkpoint = async (manifest) => {
3916
4368
  if (released) throw new Error("platform: Cannot checkpoint lifecycle state after releasing its lock.");
@@ -4122,10 +4574,153 @@ async function claimPlatformDatabaseConnection(client, installationId, ownership
4122
4574
  throw error;
4123
4575
  }
4124
4576
  }
4577
+ let cachedPlatformUploaderHash;
4578
+ function platformWorkerUploaderHash() {
4579
+ if (cachedPlatformUploaderHash !== void 0) return cachedPlatformUploaderHash;
4580
+ try {
4581
+ const launcher = resolveWranglerArgv(process.cwd())[1];
4582
+ if (!launcher) return cachedPlatformUploaderHash = null;
4583
+ const digest = createHash("sha256");
4584
+ digest.update("platform-uploader:v1\0");
4585
+ digest.update(process.version);
4586
+ digest.update(readFileSync(resolve(dirname(launcher), "..", "..", "package.json")));
4587
+ const cfDirectory = dirname(fileURLToPath(import.meta.resolve("cf/package.json")));
4588
+ digest.update(readFileSync(join(cfDirectory, "package.json")));
4589
+ digest.update(readFileSync(join(cfDirectory, "bin", "cf")));
4590
+ const cfCodeHash = platformWorkerDirectoryHash(join(cfDirectory, "dist"));
4591
+ if (!cfCodeHash) throw new Error("Cloudflare CLI code is unavailable.");
4592
+ digest.update(cfCodeHash);
4593
+ let dependencyDirectory = dirname(fileURLToPath(import.meta.resolve("@cloudflare/build-output-utils")));
4594
+ while (!existsSync(join(dependencyDirectory, "package.json"))) {
4595
+ const parent = dirname(dependencyDirectory);
4596
+ if (parent === dependencyDirectory) throw new Error("Cloudflare build output package is unavailable.");
4597
+ dependencyDirectory = parent;
4598
+ }
4599
+ digest.update(readFileSync(join(dependencyDirectory, "package.json")));
4600
+ const buildOutputCodeHash = platformWorkerDirectoryHash(join(dependencyDirectory, "dist"));
4601
+ if (!buildOutputCodeHash) throw new Error("Cloudflare build output code is unavailable.");
4602
+ digest.update(buildOutputCodeHash);
4603
+ const visited = /* @__PURE__ */ new Set();
4604
+ const visit = (path, name) => {
4605
+ if (visited.has(path)) return;
4606
+ visited.add(path);
4607
+ const source = readFileSync(path);
4608
+ digest.update(`${name}\0`);
4609
+ digest.update(source);
4610
+ const imports = [...source.toString("utf8").matchAll(/\b(?:from|import)\s*(?:\(\s*)?['"](\.{1,2}\/[^'"]+)['"]/g)].map((match) => match[1]).sort();
4611
+ for (const specifier of imports) visit(resolve(dirname(path), specifier), specifier);
4612
+ };
4613
+ visit(launcher, "launcher");
4614
+ return cachedPlatformUploaderHash = digest.digest("hex");
4615
+ } catch {
4616
+ return cachedPlatformUploaderHash = null;
4617
+ }
4618
+ }
4619
+ function platformWorkerDirectoryHash(directory) {
4620
+ if (!lstatSync(directory).isDirectory()) return null;
4621
+ const digest = createHash("sha256");
4622
+ const visit = (path, prefix) => {
4623
+ for (const entry of readdirSync(path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
4624
+ const relativePath = `${prefix}${entry.name}`;
4625
+ const absolutePath = join(path, entry.name);
4626
+ if (entry.isDirectory()) {
4627
+ digest.update(`directory:${relativePath}\0`);
4628
+ if (!visit(absolutePath, `${relativePath}/`)) return false;
4629
+ } else if (entry.isFile()) {
4630
+ digest.update(`file:${relativePath}\0`);
4631
+ digest.update(createHash("sha256").update(readFileSync(absolutePath)).digest("hex"));
4632
+ } else return false;
4633
+ }
4634
+ return true;
4635
+ };
4636
+ return visit(directory, "") ? digest.digest("hex") : null;
4637
+ }
4638
+ /** Hash every input that can change a version upload; missing or unusual files disable reuse. */
4639
+ function platformWorkerUploadFingerprint(config, managedRoutes = []) {
4640
+ const uploaderHash = platformWorkerUploaderHash();
4641
+ if (typeof config.main !== "string" || !uploaderHash) return null;
4642
+ try {
4643
+ if (!lstatSync(config.main).isFile()) return null;
4644
+ const mainSha256 = createHash("sha256").update(readFileSync(config.main)).digest("hex");
4645
+ const normalized = {
4646
+ ...config,
4647
+ main: { sha256: mainSha256 }
4648
+ };
4649
+ if (config.assets !== void 0) {
4650
+ const assets = config.assets;
4651
+ if (!assets || typeof assets !== "object" || Array.isArray(assets)) return null;
4652
+ const directory = assets.directory;
4653
+ if (typeof directory !== "string") return null;
4654
+ const assetsHash = platformWorkerDirectoryHash(directory);
4655
+ if (!assetsHash) return null;
4656
+ normalized.assets = {
4657
+ ...assets,
4658
+ directory: assetsHash
4659
+ };
4660
+ }
4661
+ return createHash("sha256").update(JSON.stringify({
4662
+ schema: 2,
4663
+ config: normalized,
4664
+ managedRoutes,
4665
+ uploaderHash
4666
+ })).digest("hex");
4667
+ } catch {
4668
+ return null;
4669
+ }
4670
+ }
4671
+ function snapshotPlatformWorkerUploadInputs(config, directory) {
4672
+ const main = config.main;
4673
+ if (typeof main !== "string") throw new Error("platform: Worker upload has no script artifact.");
4674
+ const snapshotMain = join(directory, "worker-upload.js");
4675
+ copyFileSync(main, snapshotMain);
4676
+ const snapshot = {
4677
+ ...config,
4678
+ main: snapshotMain
4679
+ };
4680
+ if (config.assets !== void 0) {
4681
+ const assets = config.assets;
4682
+ const sourceDirectory = assets.directory;
4683
+ if (typeof sourceDirectory !== "string") throw new Error("platform: Worker upload has no assets directory.");
4684
+ const snapshotDirectory = join(directory, "worker-assets");
4685
+ cpSync(sourceDirectory, snapshotDirectory, { recursive: true });
4686
+ snapshot.assets = {
4687
+ ...assets,
4688
+ directory: snapshotDirectory
4689
+ };
4690
+ }
4691
+ return snapshot;
4692
+ }
4693
+ function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
4694
+ const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
4695
+ return async (args, account, cwd, capture) => {
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 {}
4716
+ return fallback(args, account, cwd, capture);
4717
+ };
4718
+ }
4125
4719
  async function deployPlatformWorker(config, secrets, options) {
4126
4720
  const directory = mkdtempSync(join(tmpdir(), "void-platform-deploy-"));
4127
4721
  const workerName = String(config.name);
4128
4722
  const accountId = String(config.account_id);
4723
+ const readRunner = options.readCloudflare ? platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner) : options.runner;
4129
4724
  let activatedVersionId = null;
4130
4725
  let activatedDeploymentId = null;
4131
4726
  let uploadedVersionId = null;
@@ -4136,22 +4731,106 @@ async function deployPlatformWorker(config, secrets, options) {
4136
4731
  const previousTraffic = isolatePlatformWorkerCustomDomains(options.previousConfig);
4137
4732
  let uploadConfig = withoutPlatformWorkerTrafficAttachments(nextTraffic.wranglerConfig);
4138
4733
  const nextTriggers = readTriggerConfigFromObject(nextTraffic.wranglerConfig);
4734
+ const contentHash = secrets === null ? platformWorkerUploadFingerprint(config, options.managedRoutes) : null;
4735
+ const reuseMatchingVersion = options.reuseMatchingVersion !== false;
4139
4736
  let previousTriggers = readTriggerConfigFromObject(previousTraffic.wranglerConfig);
4140
4737
  try {
4141
4738
  const configPath = join(directory, "wrangler.json");
4142
4739
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4740
+ const reconcileExistingVersion = async (versionId, triggerConfig, runner, deploymentId) => {
4741
+ try {
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({
4751
+ configPath,
4752
+ knownTriggerConfigs: { [versionId]: nextTriggers },
4753
+ manageRoutes: false,
4754
+ previousTriggerConfig: triggerConfig,
4755
+ publishEmptyCustomDomains: async () => {
4756
+ await options.removeCustomDomains();
4757
+ return cleanPlatformCommandResult();
4758
+ },
4759
+ runWrangler: platformWorkerCommandRunner(accountId, directory, runner),
4760
+ targetTriggerConfig: nextTriggers,
4761
+ targetVersionId: versionId,
4762
+ workerName
4763
+ }), workerName, versionId, "publish");
4764
+ if (nextTraffic.customDomains.length > 0) await options.publishCustomDomains(workerName, nextTraffic.customDomains);
4765
+ if (options.managedRoutes.length > 0) await options.publishRoutes(workerName, options.managedRoutes);
4766
+ await options.onTrafficPublished();
4767
+ if (!deploymentId && await readActivePlatformWorkerVersion(configPath, accountId, workerName, runner) !== versionId) throw new Error(`platform: Worker ${workerName} changed during traffic reconciliation.`);
4768
+ if (deploymentId) {
4769
+ const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, runner);
4770
+ if (after[0]?.id !== deploymentId || !deploymentRunsOnlyVersion(after[0], versionId)) throw new Error(`platform: Worker ${workerName} changed deployment lineage during reconciliation.`);
4771
+ }
4772
+ return null;
4773
+ } catch (error) {
4774
+ const failures = await recoverFailedPlatformWorkerDeployment({
4775
+ checkpointUnsafeTraffic: options.onUnsafeTraffic,
4776
+ closeRoutingGate: options.retainNewWorker,
4777
+ rollback: null,
4778
+ unsafeTraffic: true
4779
+ });
4780
+ throw new PlatformWorkerRecoveryIncompleteError(`platform: Worker ${workerName} traffic reconciliation failed: ${errorMessage(error)}${failures.length > 0 ? ` Safety recovery also failed: ${failures.join("; ")}` : ""}`, { cause: error });
4781
+ }
4782
+ };
4783
+ if (options.workerExisted && contentHash && reuseMatchingVersion && options.readCloudflare) {
4784
+ let candidate;
4785
+ let changedDuringRead = false;
4786
+ try {
4787
+ const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4788
+ const activeVersion = before[0]?.versions.length === 1 && before[0].versions[0]?.percentage === 100 ? before[0].versions[0].versionId : null;
4789
+ if (!activeVersion) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
4790
+ const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, readRunner);
4791
+ const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4792
+ changedDuringRead = before[0]?.id !== after[0]?.id || !deploymentRunsOnlyVersion(after[0], snapshot.versionId);
4793
+ if (!changedDuringRead && snapshot.contentHash === contentHash) candidate = {
4794
+ deploymentId: after[0].id,
4795
+ snapshot
4796
+ };
4797
+ } catch (error) {
4798
+ if (error instanceof Error && (error.message.includes("changed concurrently") || error.message.includes("does not belong to this installation"))) throw error;
4799
+ }
4800
+ if (changedDuringRead) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
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
+ }
4806
+ }
4143
4807
  if (options.workerExisted) {
4144
- const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), options.runner);
4808
+ const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), readRunner);
4145
4809
  previousVersionId = previous.versionId;
4146
- const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4810
+ const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4147
4811
  if (!deploymentRunsOnlyVersion(deployments[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
4148
4812
  previousDeployment = deployments[0];
4149
4813
  previousDeploymentId = previousDeployment.id;
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
+ }
4150
4819
  uploadConfig = withoutCompletedPlatformWorkerMigrations(uploadConfig, previous.migrationTag);
4151
4820
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4152
4821
  previousTriggers = resolvePlatformPreviousTriggerConfig(withoutPlatformCustomDomainTriggers(previous.triggerConfig), previousTriggers, options.previousPublicEndpoints);
4153
4822
  previousTriggers = withoutPlatformCustomDomainTriggers(mergePreservedTriggerRoutes(previousTriggers, options.preservedRoutes));
4154
4823
  }
4824
+ const immutableConfig = contentHash ? snapshotPlatformWorkerUploadInputs(config, directory) : null;
4825
+ const uploadedContentHash = immutableConfig ? platformWorkerUploadFingerprint(immutableConfig, options.managedRoutes) : null;
4826
+ if (immutableConfig) {
4827
+ uploadConfig = {
4828
+ ...uploadConfig,
4829
+ main: immutableConfig.main,
4830
+ ...immutableConfig.assets === void 0 ? {} : { assets: immutableConfig.assets }
4831
+ };
4832
+ writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4833
+ }
4155
4834
  const argv = [
4156
4835
  ...options.workerExisted ? ["versions", "upload"] : ["deploy"],
4157
4836
  "--config",
@@ -4161,21 +4840,33 @@ async function deployPlatformWorker(config, secrets, options) {
4161
4840
  "--experimental-auto-create=false",
4162
4841
  ...!options.workerExisted ? ["--autoconfig=false"] : [],
4163
4842
  "--message",
4164
- encodeCloudflareVersionMessage([], nextTriggers)
4843
+ encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
4165
4844
  ];
4166
4845
  if (secrets) {
4167
4846
  const secretsPath = join(directory, "secrets.json");
4168
4847
  writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
4169
4848
  argv.push("--secrets-file", secretsPath);
4170
4849
  }
4171
- 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
+ }
4172
4862
  uploadedVersionId = parseUploadedPlatformWorkerVersion(`${uploaded.stdout}\n${uploaded.stderr}`);
4173
4863
  if (!options.workerExisted) activatedVersionId = uploadedVersionId;
4174
- assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
4175
4864
  if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
4176
4865
  await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
4866
+ await options.onReadyForActivation?.();
4867
+ await options.waitForPriorWorker?.();
4177
4868
  if (options.workerExisted) {
4178
- const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4869
+ const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4179
4870
  if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
4180
4871
  activatedVersionId = uploadedVersionId;
4181
4872
  assertCleanPlatformWrangler(await options.runner([
@@ -4189,7 +4880,7 @@ async function deployPlatformWorker(config, secrets, options) {
4189
4880
  "--yes"
4190
4881
  ], accountId, directory, true), `activate ${workerName}`);
4191
4882
  }
4192
- const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4883
+ const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4193
4884
  const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
4194
4885
  if (lineage.kind === "concurrent-after") {
4195
4886
  activatedVersionId = null;
@@ -4209,7 +4900,7 @@ async function deployPlatformWorker(config, secrets, options) {
4209
4900
  expectedActiveDeploymentId: lineage.deploymentId,
4210
4901
  expectedActiveVersionId: uploadedVersionId,
4211
4902
  restoration: interveningDeployment,
4212
- runner: options.runner,
4903
+ runner: readRunner,
4213
4904
  workerName
4214
4905
  });
4215
4906
  } catch (recoveryError) {
@@ -4235,7 +4926,7 @@ async function deployPlatformWorker(config, secrets, options) {
4235
4926
  await options.removeCustomDomains();
4236
4927
  return cleanPlatformCommandResult();
4237
4928
  },
4238
- runWrangler: platformWorkerCommandRunner(accountId, directory, options.runner),
4929
+ runWrangler: platformWorkerCommandRunner(accountId, directory, readRunner),
4239
4930
  targetTriggerConfig: nextTriggers,
4240
4931
  targetVersionId: uploadedVersionId,
4241
4932
  workerName
@@ -4259,7 +4950,7 @@ async function deployPlatformWorker(config, secrets, options) {
4259
4950
  removeCustomDomains: options.removeCustomDomains,
4260
4951
  publishCustomDomains: options.publishCustomDomains,
4261
4952
  retainNewWorker: options.retainNewWorker,
4262
- runner: options.runner,
4953
+ runner: readRunner,
4263
4954
  workerExisted: options.workerExisted,
4264
4955
  workerName
4265
4956
  });
@@ -4286,7 +4977,7 @@ async function deployPlatformWorker(config, secrets, options) {
4286
4977
  removeCustomDomains: options.removeCustomDomains,
4287
4978
  publishCustomDomains: options.publishCustomDomains,
4288
4979
  retainNewWorker: options.retainNewWorker,
4289
- runner: options.runner,
4980
+ runner: readRunner,
4290
4981
  workerExisted: options.workerExisted,
4291
4982
  workerName
4292
4983
  }) : null,
@@ -4387,9 +5078,11 @@ async function runPlatformWrangler(args, accountId, cwd, capture) {
4387
5078
  return runPlatformWranglerAtApiBase(args, accountId, cwd, capture);
4388
5079
  }
4389
5080
  const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
4390
- async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl()) {
5081
+ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
5082
+ const startedAt = performance.now();
4391
5083
  const result = await createDefaultRunWrangler(process.cwd(), {
4392
5084
  launcher: resolveWranglerArgv(process.cwd()),
5085
+ parallelWorkerUpload,
4393
5086
  timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
4394
5087
  })([...args], {
4395
5088
  accountId,
@@ -4397,6 +5090,10 @@ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBa
4397
5090
  captureJson: capture,
4398
5091
  cwd
4399
5092
  });
5093
+ if (process.env.VOID_PLATFORM_TIMINGS === "1") {
5094
+ const operation = args[0] === "versions" || args[0] === "deployments" ? `${args[0]} ${args[1] ?? ""}` : args[0] ?? "unknown";
5095
+ process.stderr.write(`[platform timing] wrangler ${operation} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
5096
+ }
4400
5097
  const stdout = result.stdout || "";
4401
5098
  const stderr = result.stderr || "";
4402
5099
  if (!capture && result.status !== 0) {
@@ -4435,8 +5132,11 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4435
5132
  const upstreamBase = new URL(upstreamApiBaseUrl);
4436
5133
  if (upstreamBase.protocol !== "http:" && upstreamBase.protocol !== "https:") throw new Error("platform: Cloudflare API base URL must use HTTP or HTTPS.");
4437
5134
  const basePath = upstreamBase.pathname.replace(/\/+$/, "");
4438
- 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`;
4439
5138
  const state = {
5139
+ assetSessionStarted: false,
4440
5140
  attempted: false,
4441
5141
  conflictStatus: null,
4442
5142
  created: false,
@@ -4456,7 +5156,9 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4456
5156
  const isConditionalCreate = incoming.method === "PUT" && incomingUrl.pathname === expectedPath && supportedCreateQuery;
4457
5157
  const isTopLevelWorkerUpload = incoming.method === "PUT" && new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/accounts/[^/]+/workers/scripts/[^/]+$`).test(incomingUrl.pathname);
4458
5158
  const isReadOnly = incoming.method === "GET" || incoming.method === "HEAD" || incoming.method === "OPTIONS";
4459
- 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) {
4460
5162
  state.rejectedMutation = `${incoming.method ?? "UNKNOWN"} ${incomingUrl.pathname}${incomingUrl.pathname === expectedPath && !supportedCreateQuery ? " (unsupported Worker create query)" : ""}`;
4461
5163
  incoming.resume();
4462
5164
  outgoing.writeHead(409, { "Content-Type": "application/json" });
@@ -4469,6 +5171,7 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4469
5171
  state.attempted = true;
4470
5172
  headers["if-none-match"] = "*";
4471
5173
  }
5174
+ if (isAssetUploadSession) state.assetSessionStarted = true;
4472
5175
  const upstreamUrl = new URL(`${incomingUrl.pathname}${incomingUrl.search}`, upstreamBase.origin);
4473
5176
  const request$2 = (upstreamUrl.protocol === "https:" ? request$1 : request)(upstreamUrl, {
4474
5177
  headers,
@@ -4655,6 +5358,11 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
4655
5358
  const readActiveVersion = () => readActivePlatformWorkerVersion(configPath, accountId, workerName, runner);
4656
5359
  const versionId = await readActiveVersion();
4657
5360
  if (!versionId) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
5361
+ return await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, async () => {
5362
+ if (await readActiveVersion() !== versionId) throw new Error(`platform: Worker ${workerName} changed concurrently while its active version was inspected.`);
5363
+ });
5364
+ }
5365
+ async function readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, validateCurrentVersion) {
4658
5366
  const result = await runner([
4659
5367
  "versions",
4660
5368
  "view",
@@ -4677,13 +5385,15 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
4677
5385
  const bindings = resources && typeof resources === "object" && !Array.isArray(resources) ? resources.bindings : null;
4678
5386
  const ownershipBindings = Array.isArray(bindings) ? bindings.filter((binding) => binding && typeof binding === "object" && binding.name === "PLATFORM_OWNERSHIP_MARKER" && binding.type === "plain_text" && typeof binding.text === "string") : [];
4679
5387
  if (ownershipBindings.length !== 1 || ownershipBindings[0].text !== expectedOwnershipMarker) throw new Error(`platform: Active Worker version ${versionId} for ${workerName} does not belong to this installation.`);
4680
- if (await readActiveVersion() !== versionId) throw new Error(`platform: Worker ${workerName} changed concurrently while its active version was inspected.`);
5388
+ await validateCurrentVersion?.();
4681
5389
  const annotations = value.annotations;
4682
5390
  const message = annotations && typeof annotations === "object" && typeof annotations["workers/message"] === "string" ? annotations["workers/message"] ?? null : null;
4683
5391
  const metadata = parseCloudflareVersionMessage(message);
4684
5392
  if (metadata?.schema !== 2) throw new Error(`platform: Active Worker ${workerName} predates safe trigger snapshots. Refusing to upgrade because its routes, cron, and queue consumers could not be restored after a failed cutover.`);
5393
+ const migrationTag = readCloudflareVersionMigrationTag(value);
4685
5394
  return {
4686
- migrationTag: readCloudflareVersionMigrationTag(value),
5395
+ ...metadata.contentHash ? { contentHash: metadata.contentHash } : {},
5396
+ migrationTag,
4687
5397
  triggerConfig: metadata.triggerConfig,
4688
5398
  versionId
4689
5399
  };
@@ -4966,7 +5676,7 @@ async function verifyPlatformHealth(apiUrl, installationId, proxyUrl, applicatio
4966
5676
  redirect: "manual",
4967
5677
  signal: AbortSignal.timeout(3e4)
4968
5678
  });
4969
- const dispatchUrl = dispatchOrigin ?? `https://test.${applicationBaseDomain}`;
5679
+ const dispatchUrl = dispatchOrigin ?? `https://${PLATFORM_HEALTH_HOSTNAME_LABEL}.${applicationBaseDomain}`;
4970
5680
  const [health, discovery, proxy, dispatch, emailGateway] = await Promise.all([
4971
5681
  request(`${apiUrl}/health`),
4972
5682
  request(`${apiUrl}/.well-known/void-platform`),
@@ -5568,14 +6278,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
5568
6278
  }
5569
6279
  async function runPlatformLifecycleCommand(args) {
5570
6280
  if (args.subcommand === "domain") {
5571
- const { runPlatformDomainCommand } = await import("./platform-domain-DppaTARe.mjs");
6281
+ const { runPlatformDomainCommand } = await import("./platform-domain-D6Xcy9ZX.mjs");
5572
6282
  await runPlatformDomainCommand(args);
5573
6283
  return;
5574
6284
  }
5575
6285
  if (args.subcommand === "discover") {
5576
6286
  const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
5577
6287
  const accountId = await selectAccount(await readAccounts(client), args.account);
5578
- const { recoverPlatformInstallations } = await import("./platform-recovery-dJ5Me-I0.mjs");
6288
+ const { recoverPlatformInstallations } = await import("./platform-recovery-BvtcXON5.mjs");
5579
6289
  const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
5580
6290
  if (recovered.length === 0) {
5581
6291
  log.info(`No recoverable Void platform installations were found in ${accountId}.`);
@@ -5585,7 +6295,7 @@ async function runPlatformLifecycleCommand(args) {
5585
6295
  return;
5586
6296
  }
5587
6297
  if (args.subcommand !== "install") {
5588
- const { runInstalledPlatformCommand } = await import("./platform-management-BS5r6Ao8.mjs");
6298
+ const { runInstalledPlatformCommand } = await import("./platform-management-DOtss0BN.mjs");
5589
6299
  await runInstalledPlatformCommand(args);
5590
6300
  return;
5591
6301
  }
@@ -5809,7 +6519,7 @@ async function runPlatformLifecycleCommand(args) {
5809
6519
  progress?.stop("Read the saved installation.");
5810
6520
  progress = void 0;
5811
6521
  if (authoritative) {
5812
- const { selectAuthoritativePlatformManifest } = await import("./platform-management-BS5r6Ao8.mjs");
6522
+ const { selectAuthoritativePlatformManifest } = await import("./platform-management-DOtss0BN.mjs");
5813
6523
  const selected = selectAuthoritativePlatformManifest(existing, authoritative);
5814
6524
  authoritativeProvisionIntents = true;
5815
6525
  if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
@@ -6038,7 +6748,7 @@ async function runPlatformLifecycleCommand(args) {
6038
6748
  })).actions.filter((action) => action.kind === "worker"),
6039
6749
  revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
6040
6750
  verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
6041
- const { verifyPlatformInstallationOwnership } = await import("./platform-management-BS5r6Ao8.mjs");
6751
+ const { verifyPlatformInstallationOwnership } = await import("./platform-management-DOtss0BN.mjs");
6042
6752
  const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
6043
6753
  const previous = manifest.resources.find((resource) => resource.key === action.key);
6044
6754
  return action.action === "create" && previous && previous.name !== action.name;
@@ -6178,4 +6888,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
6178
6888
  return value;
6179
6889
  }
6180
6890
  //#endregion
6181
- 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 };