@runeditrun/rer 0.0.0-stage → 0.1.1

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 (527) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/bin/rer.mjs +3 -0
  4. package/cli/README.md +243 -0
  5. package/cli/adapter.mjs +10 -0
  6. package/cli/ai-local.mjs +65 -0
  7. package/cli/archive-path.mjs +52 -0
  8. package/cli/archive-vite.mjs +55 -0
  9. package/cli/archive.mjs +8 -0
  10. package/cli/auth-copy.mjs +80 -0
  11. package/cli/bounded.mjs +70 -0
  12. package/cli/cache-doctor.mjs +63 -0
  13. package/cli/container-transfer.mjs +35 -0
  14. package/cli/container.mjs +62 -0
  15. package/cli/contract.mjs +25 -0
  16. package/cli/dependencies.mjs +45 -0
  17. package/cli/dependency-approval.mjs +33 -0
  18. package/cli/dependency-doctor.mjs +34 -0
  19. package/cli/dependency-input.mjs +43 -0
  20. package/cli/deploy-config.mjs +118 -0
  21. package/cli/deploy.mjs +37 -0
  22. package/cli/dev-identity.mjs +80 -0
  23. package/cli/errors.mjs +14 -0
  24. package/cli/export.mjs +42 -0
  25. package/cli/font-cache.mjs +29 -0
  26. package/cli/hosted-output.mjs +110 -0
  27. package/cli/independent.mjs +29 -0
  28. package/cli/inspect.mjs +174 -0
  29. package/cli/jsonc.mjs +111 -0
  30. package/cli/lifecycle.mjs +148 -0
  31. package/cli/local.mjs +100 -0
  32. package/cli/main.mjs +57 -0
  33. package/cli/managed-server.mjs +143 -0
  34. package/cli/node-runtime-cache.mjs +68 -0
  35. package/cli/node-runtime-discovery.mjs +56 -0
  36. package/cli/node-runtime-download.mjs +59 -0
  37. package/cli/node-runtime.mjs +50 -0
  38. package/cli/output.mjs +22 -0
  39. package/cli/parse.mjs +77 -0
  40. package/cli/port.mjs +52 -0
  41. package/cli/portability-worker.mjs +56 -0
  42. package/cli/portability.mjs +66 -0
  43. package/cli/preview-cache.mjs +99 -0
  44. package/cli/project.mjs +48 -0
  45. package/cli/release.mjs +24 -0
  46. package/cli/runtime-front-door.mjs +46 -0
  47. package/cli/scheduled.mjs +81 -0
  48. package/cli/server-activity.mjs +12 -0
  49. package/cli/server-proxy.mjs +93 -0
  50. package/cli/server-registry.mjs +182 -0
  51. package/cli/server-resources.mjs +22 -0
  52. package/cli/settings.mjs +15 -0
  53. package/cli/setup-answers.mjs +29 -0
  54. package/cli/setup-input.mjs +113 -0
  55. package/cli/shared.mjs +7 -0
  56. package/cli/transfers.mjs +33 -0
  57. package/cli/version.mjs +36 -0
  58. package/composition/git-source.mjs +111 -0
  59. package/edition-authoring/files.mjs +81 -0
  60. package/edition-authoring/manifest.mjs +65 -0
  61. package/global/README.md +158 -0
  62. package/global/checkout.mjs +72 -0
  63. package/global/cli/command.mjs +13 -0
  64. package/global/cli/install.mjs +75 -0
  65. package/global/cli/layout.mjs +45 -0
  66. package/global/cli/status.mjs +54 -0
  67. package/global/cli/uninstall.mjs +36 -0
  68. package/global/delegate.mjs +13 -0
  69. package/global/launch/capacity.mjs +72 -0
  70. package/global/launch/dev.mjs +117 -0
  71. package/global/launch/facts.mjs +94 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +63 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +106 -0
  77. package/global/launch/steps.mjs +90 -0
  78. package/global/launch/stop.mjs +89 -0
  79. package/global/launch/worker.mjs +62 -0
  80. package/global/list.mjs +59 -0
  81. package/global/main.mjs +53 -0
  82. package/global/package-info.mjs +38 -0
  83. package/global/registry.mjs +24 -0
  84. package/global/rer.mjs +29 -0
  85. package/global/update/archive.mjs +89 -0
  86. package/global/update/command.mjs +90 -0
  87. package/global/update/latest.mjs +6 -0
  88. package/global/update/notice.mjs +58 -0
  89. package/global/update/registry.mjs +47 -0
  90. package/global/version.mjs +29 -0
  91. package/install.sh +144 -0
  92. package/modules/ai/LICENSE +21 -0
  93. package/modules/ai/README.md +90 -0
  94. package/modules/ai/client.mjs +71 -0
  95. package/modules/ai/config.mjs +39 -0
  96. package/modules/ai/dev/index.d.mts +6 -0
  97. package/modules/ai/dev/index.mjs +64 -0
  98. package/modules/ai/dev/outbox.mjs +33 -0
  99. package/modules/ai/errors.mjs +28 -0
  100. package/modules/ai/fake/index.d.mts +3 -0
  101. package/modules/ai/fake/index.mjs +82 -0
  102. package/modules/ai/index.d.mts +10 -0
  103. package/modules/ai/index.mjs +3 -0
  104. package/modules/ai/package.json +6 -0
  105. package/modules/container/README.md +156 -0
  106. package/modules/container/adapters/d1.mjs +34 -0
  107. package/modules/container/adapters/egress.mjs +40 -0
  108. package/modules/container/adapters/jobs.mjs +47 -0
  109. package/modules/container/adapters/objects.mjs +84 -0
  110. package/modules/container/adapters/retained.mjs +14 -0
  111. package/modules/container/adapters/sqlite-error.mjs +7 -0
  112. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  113. package/modules/container/assets.mjs +14 -0
  114. package/modules/container/bindings.mjs +1 -0
  115. package/modules/container/boundary.mjs +38 -0
  116. package/modules/container/build.mjs +65 -0
  117. package/modules/container/deploy-input.mjs +17 -0
  118. package/modules/container/entrypoint.sh +5 -0
  119. package/modules/container/host.mjs +43 -0
  120. package/modules/container/ingress.mjs +23 -0
  121. package/modules/container/lock.mjs +11 -0
  122. package/modules/container/migrations.mjs +16 -0
  123. package/modules/container/node-engine.mjs +39 -0
  124. package/modules/container/portability-schema.mjs +14 -0
  125. package/modules/container/remote/deploy.py +76 -0
  126. package/modules/container/remote/files.py +275 -0
  127. package/modules/container/remote/resources.py +157 -0
  128. package/modules/container/runner.mjs +10 -0
  129. package/modules/container/ssh-plan.mjs +44 -0
  130. package/modules/container/ssh.mjs +63 -0
  131. package/modules/container/transfer.mjs +27 -0
  132. package/modules/container/worker-engine.mjs +32 -0
  133. package/modules/dependencies/README.md +211 -0
  134. package/modules/dependencies/approval.mjs +51 -0
  135. package/modules/dependencies/auth.mjs +40 -0
  136. package/modules/dependencies/authority.mjs +14 -0
  137. package/modules/dependencies/crypto.mjs +13 -0
  138. package/modules/dependencies/index.d.mts +23 -0
  139. package/modules/dependencies/index.mjs +113 -0
  140. package/modules/dependencies/logout.mjs +42 -0
  141. package/modules/dependencies/native.mjs +9 -0
  142. package/modules/dependencies/protocol.mjs +23 -0
  143. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  144. package/modules/dependencies/schema/dependencies.sql +15 -0
  145. package/modules/dependencies/store.mjs +19 -0
  146. package/modules/deploy-kit/README.md +308 -0
  147. package/modules/deploy-kit/artifact.mjs +47 -0
  148. package/modules/deploy-kit/build-modules.mjs +21 -0
  149. package/modules/deploy-kit/check.mjs +13 -0
  150. package/modules/deploy-kit/cli.mjs +50 -0
  151. package/modules/deploy-kit/container-config.mjs +8 -0
  152. package/modules/deploy-kit/default-stage.mjs +14 -0
  153. package/modules/deploy-kit/deploy.mjs +80 -0
  154. package/modules/deploy-kit/health-request.mjs +86 -0
  155. package/modules/deploy-kit/health.mjs +99 -0
  156. package/modules/deploy-kit/index.mjs +12 -0
  157. package/modules/deploy-kit/local-only.mjs +10 -0
  158. package/modules/deploy-kit/migrations.mjs +48 -0
  159. package/modules/deploy-kit/owner-cli.mjs +63 -0
  160. package/modules/deploy-kit/owner.mjs +297 -0
  161. package/modules/deploy-kit/prepare.mjs +62 -0
  162. package/modules/deploy-kit/provider.mjs +99 -0
  163. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  164. package/modules/deploy-kit/receipt.mjs +16 -0
  165. package/modules/deploy-kit/render.mjs +47 -0
  166. package/modules/deploy-kit/resources.mjs +106 -0
  167. package/modules/deploy-kit/service-grants.mjs +25 -0
  168. package/modules/deploy-kit/sql-safety.mjs +94 -0
  169. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  170. package/modules/deploy-kit/teardown.mjs +100 -0
  171. package/modules/deploy-kit/templates.mjs +31 -0
  172. package/modules/deploy-kit/validation.mjs +58 -0
  173. package/modules/deploy-kit/worker.mjs +105 -0
  174. package/modules/erasure/CONTRACT.md +90 -0
  175. package/modules/erasure/README.md +221 -0
  176. package/modules/erasure/audit.mjs +23 -0
  177. package/modules/erasure/blobs.mjs +45 -0
  178. package/modules/erasure/cascade.mjs +91 -0
  179. package/modules/erasure/completion.mjs +62 -0
  180. package/modules/erasure/declaration.mjs +51 -0
  181. package/modules/erasure/fence.mjs +19 -0
  182. package/modules/erasure/index.d.mts +62 -0
  183. package/modules/erasure/index.mjs +85 -0
  184. package/modules/erasure/order.mjs +56 -0
  185. package/modules/erasure/plan.mjs +121 -0
  186. package/modules/erasure/rate.mjs +57 -0
  187. package/modules/erasure/receipt.mjs +32 -0
  188. package/modules/erasure/root.mjs +21 -0
  189. package/modules/erasure/schema/erasure-rates.sql +5 -0
  190. package/modules/erasure/schema/erasure.sql +27 -0
  191. package/modules/erasure/scope.mjs +29 -0
  192. package/modules/erasure/sql.mjs +23 -0
  193. package/modules/erasure/verification.mjs +69 -0
  194. package/modules/integrations/core/CONTRACT.md +50 -0
  195. package/modules/integrations/core/README.md +27 -0
  196. package/modules/integrations/core/crypto.ts +19 -0
  197. package/modules/integrations/core/database.ts +21 -0
  198. package/modules/integrations/core/delivery.ts +97 -0
  199. package/modules/integrations/core/destination.ts +13 -0
  200. package/modules/integrations/core/index.ts +7 -0
  201. package/modules/integrations/core/node-transport.ts +72 -0
  202. package/modules/integrations/core/public-address.ts +57 -0
  203. package/modules/integrations/core/repository.ts +74 -0
  204. package/modules/integrations/core/schema.sql +36 -0
  205. package/modules/integrations/core/sqlite.ts +20 -0
  206. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  207. package/modules/integrations/core/tokens.ts +71 -0
  208. package/modules/integrations/core/types.ts +52 -0
  209. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  210. package/modules/integrations/core/webhook.ts +73 -0
  211. package/modules/job-queue/README.md +173 -0
  212. package/modules/job-queue/cli.mjs +64 -0
  213. package/modules/job-queue/command.mjs +64 -0
  214. package/modules/job-queue/config.mjs +48 -0
  215. package/modules/job-queue/identity.mjs +48 -0
  216. package/modules/job-queue/index.mjs +135 -0
  217. package/modules/job-queue/membership.mjs +30 -0
  218. package/modules/job-queue/module.json +10 -0
  219. package/modules/job-queue/policy.mjs +29 -0
  220. package/modules/job-queue/processes.mjs +80 -0
  221. package/modules/job-queue/resources.mjs +37 -0
  222. package/modules/job-queue/storage.mjs +64 -0
  223. package/modules/job-queue/supervisor.mjs +30 -0
  224. package/modules/job-queue/waiting.mjs +16 -0
  225. package/modules/mail/LICENSE +21 -0
  226. package/modules/mail/README.md +336 -0
  227. package/modules/mail/doctor.mjs +81 -0
  228. package/modules/mail/index.mjs +8 -0
  229. package/modules/mail/live-proof/README.md +113 -0
  230. package/modules/mail/live-proof/run.mjs +55 -0
  231. package/modules/mail/package.json +19 -0
  232. package/modules/mail/pnpm-lock.yaml +537 -0
  233. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  234. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  235. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  236. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  237. package/modules/mail/src/adapters/messaging/README.md +92 -0
  238. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  239. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  240. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  241. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  242. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  243. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  244. package/modules/mail/src/adapters/resend/README.md +120 -0
  245. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  246. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  247. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  248. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  249. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  250. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  251. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  252. package/modules/mail/src/core/errors.mjs +9 -0
  253. package/modules/mail/src/core/transport.mjs +33 -0
  254. package/modules/mail/src/core/validation.mjs +172 -0
  255. package/modules/mail/src/development/index.d.mts +37 -0
  256. package/modules/mail/src/development/index.mjs +35 -0
  257. package/modules/mail/src/development/outbox.mjs +78 -0
  258. package/modules/mail/src/development/schema.sql +9 -0
  259. package/modules/mail/src/development/viewer-html.mjs +42 -0
  260. package/modules/mail/src/development/viewer.mjs +65 -0
  261. package/modules/mail/src/doctor/README.md +107 -0
  262. package/modules/mail/src/doctor/common.mjs +64 -0
  263. package/modules/mail/src/doctor/dns.mjs +102 -0
  264. package/modules/mail/src/doctor/index.mjs +28 -0
  265. package/modules/mail/src/doctor/probe.mjs +66 -0
  266. package/modules/mail/src/doctor/receive.mjs +81 -0
  267. package/modules/mail/src/doctor/records.mjs +45 -0
  268. package/modules/mail/src/events/README.md +268 -0
  269. package/modules/mail/src/events/d1-schema.sql +25 -0
  270. package/modules/mail/src/events/d1-storage.mjs +76 -0
  271. package/modules/mail/src/events/index.mjs +3 -0
  272. package/modules/mail/src/events/intake.mjs +201 -0
  273. package/modules/mail/src/events/storage.mjs +52 -0
  274. package/modules/mail/src/events/verify.mjs +92 -0
  275. package/modules/mail/src/inbound/README.md +137 -0
  276. package/modules/mail/src/inbound/bytes.mjs +91 -0
  277. package/modules/mail/src/inbound/headers.mjs +143 -0
  278. package/modules/mail/src/inbound/index.mjs +29 -0
  279. package/modules/mail/src/inbound/mime.mjs +36 -0
  280. package/modules/mail/src/inbound/multipart.mjs +27 -0
  281. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  282. package/modules/mail/src/inbound/resend.mjs +54 -0
  283. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  284. package/modules/mail/src/sender.mjs +33 -0
  285. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  286. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  287. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  288. package/modules/operator-auth/README.md +599 -0
  289. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  290. package/modules/operator-auth/accounts/admin.mjs +159 -0
  291. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  292. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  293. package/modules/operator-auth/accounts/index.d.mts +9 -0
  294. package/modules/operator-auth/accounts/index.mjs +9 -0
  295. package/modules/operator-auth/accounts/native.d.mts +38 -0
  296. package/modules/operator-auth/accounts/native.mjs +171 -0
  297. package/modules/operator-auth/accounts/oa.mjs +90 -0
  298. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  299. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  300. package/modules/operator-auth/accounts/roles.mjs +26 -0
  301. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  302. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  303. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  304. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  305. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  306. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  307. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  308. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  309. package/modules/operator-auth/accounts/store.mjs +246 -0
  310. package/modules/operator-auth/branding.mjs +66 -0
  311. package/modules/operator-auth/config.mjs +35 -0
  312. package/modules/operator-auth/conformance/README.md +81 -0
  313. package/modules/operator-auth/conformance/index.d.mts +43 -0
  314. package/modules/operator-auth/conformance/index.mjs +300 -0
  315. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  316. package/modules/operator-auth/crypto.mjs +17 -0
  317. package/modules/operator-auth/dev/badge.mjs +62 -0
  318. package/modules/operator-auth/dev/index.d.mts +14 -0
  319. package/modules/operator-auth/dev/index.mjs +137 -0
  320. package/modules/operator-auth/dev/personas.mjs +17 -0
  321. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  322. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  323. package/modules/operator-auth/dev-admission.d.mts +4 -0
  324. package/modules/operator-auth/dev-admission.mjs +10 -0
  325. package/modules/operator-auth/dev-gate.mjs +86 -0
  326. package/modules/operator-auth/docs/README.md +78 -0
  327. package/modules/operator-auth/docs/native-accounts.md +313 -0
  328. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  329. package/modules/operator-auth/docs/vite-spa.md +144 -0
  330. package/modules/operator-auth/errors.mjs +9 -0
  331. package/modules/operator-auth/http.mjs +42 -0
  332. package/modules/operator-auth/identity/admission.mjs +105 -0
  333. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  334. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  335. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  336. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  337. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  338. package/modules/operator-auth/identity/index.d.mts +7 -0
  339. package/modules/operator-auth/identity/index.mjs +5 -0
  340. package/modules/operator-auth/identity/oidc/README.md +171 -0
  341. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  342. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  343. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  344. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  345. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  346. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  347. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  348. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  349. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  350. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  351. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  352. package/modules/operator-auth/identity/principal.mjs +28 -0
  353. package/modules/operator-auth/index.d.mts +223 -0
  354. package/modules/operator-auth/index.mjs +11 -0
  355. package/modules/operator-auth/input.mjs +55 -0
  356. package/modules/operator-auth/local-cli.mjs +16 -0
  357. package/modules/operator-auth/page.mjs +34 -0
  358. package/modules/operator-auth/policy.mjs +20 -0
  359. package/modules/operator-auth/routes.mjs +91 -0
  360. package/modules/operator-auth/service.mjs +152 -0
  361. package/modules/operator-auth/signin/index.mjs +67 -0
  362. package/modules/owner-claim/README.md +137 -0
  363. package/modules/owner-claim/idp.mjs +40 -0
  364. package/modules/owner-claim/index.d.mts +27 -0
  365. package/modules/owner-claim/index.mjs +39 -0
  366. package/modules/owner-claim/page.mjs +11 -0
  367. package/modules/owner-claim/schema/idp.sql +4 -0
  368. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  369. package/modules/owner-claim/transaction.mjs +87 -0
  370. package/modules/owner-claim/verify.mjs +80 -0
  371. package/modules/portability/README.md +444 -0
  372. package/modules/portability/bridge.mjs +102 -0
  373. package/modules/portability/catalogue.mjs +118 -0
  374. package/modules/portability/cli-options.mjs +38 -0
  375. package/modules/portability/cli.mjs +56 -0
  376. package/modules/portability/copies.mjs +68 -0
  377. package/modules/portability/dev-data.mjs +32 -0
  378. package/modules/portability/diff.mjs +33 -0
  379. package/modules/portability/export.mjs +129 -0
  380. package/modules/portability/files.mjs +65 -0
  381. package/modules/portability/format.mjs +122 -0
  382. package/modules/portability/generated.mjs +60 -0
  383. package/modules/portability/hash.mjs +79 -0
  384. package/modules/portability/http.mjs +42 -0
  385. package/modules/portability/import.mjs +185 -0
  386. package/modules/portability/index.d.mts +58 -0
  387. package/modules/portability/index.mjs +22 -0
  388. package/modules/portability/journal.mjs +49 -0
  389. package/modules/portability/legacy-files.mjs +43 -0
  390. package/modules/portability/legacy-safety.mjs +27 -0
  391. package/modules/portability/lines.mjs +36 -0
  392. package/modules/portability/migration-history.mjs +18 -0
  393. package/modules/portability/migration-statements.mjs +23 -0
  394. package/modules/portability/payloads.mjs +52 -0
  395. package/modules/portability/provider-data.mjs +25 -0
  396. package/modules/portability/restore-safety.mjs +30 -0
  397. package/modules/portability/schema/portability.sql +25 -0
  398. package/modules/portability/storage.mjs +49 -0
  399. package/modules/release/README.md +212 -0
  400. package/modules/release/catalogue.mjs +79 -0
  401. package/modules/release/export.mjs +51 -0
  402. package/modules/release/produce.mjs +136 -0
  403. package/modules/release/public-child-transport-state.cjs +29 -0
  404. package/modules/release/public-child.mjs +71 -0
  405. package/modules/release/public-git.mjs +21 -0
  406. package/modules/release/publication-scan.mjs +22 -0
  407. package/modules/release/publish-handoff.mjs +37 -0
  408. package/modules/release/source.mjs +71 -0
  409. package/modules/release/test-summary.mjs +50 -0
  410. package/onboarding/README.md +163 -0
  411. package/onboarding/activity.mjs +31 -0
  412. package/onboarding/adapter-auth.mjs +88 -0
  413. package/onboarding/adapter-hosting.mjs +35 -0
  414. package/onboarding/adapter-labels.mjs +13 -0
  415. package/onboarding/adapter-settings.mjs +129 -0
  416. package/onboarding/adapter.mjs +238 -0
  417. package/onboarding/agent-handoff.mjs +9 -0
  418. package/onboarding/app-processes.mjs +21 -0
  419. package/onboarding/archive-output.d.mts +3 -0
  420. package/onboarding/archive-output.mjs +54 -0
  421. package/onboarding/command-custody.mjs +24 -0
  422. package/onboarding/command-recovery.mjs +8 -0
  423. package/onboarding/commands.mjs +166 -0
  424. package/onboarding/dependency-approval.mjs +36 -0
  425. package/onboarding/dev-identity.mjs +23 -0
  426. package/onboarding/email.mjs +24 -0
  427. package/onboarding/failures.mjs +42 -0
  428. package/onboarding/input-limits.mjs +7 -0
  429. package/onboarding/installer-inputs.mjs +24 -0
  430. package/onboarding/launcher.mjs +34 -0
  431. package/onboarding/local/build-files.mjs +64 -0
  432. package/onboarding/local/build.mjs +49 -0
  433. package/onboarding/local/environment.mjs +30 -0
  434. package/onboarding/local/files.mjs +35 -0
  435. package/onboarding/local/operation.mjs +166 -0
  436. package/onboarding/local/owner.mjs +34 -0
  437. package/onboarding/local/process-table.mjs +114 -0
  438. package/onboarding/local/recovery.mjs +79 -0
  439. package/onboarding/local/state.mjs +31 -0
  440. package/onboarding/metadata.mjs +7 -0
  441. package/onboarding/operator-install.mjs +45 -0
  442. package/onboarding/operator-installer.mjs +39 -0
  443. package/onboarding/operator-prerequisites.mjs +27 -0
  444. package/onboarding/package.json +6 -0
  445. package/onboarding/prefill.mjs +16 -0
  446. package/onboarding/private-files.mjs +97 -0
  447. package/onboarding/private.mjs +35 -0
  448. package/onboarding/process-cleanup.mjs +134 -0
  449. package/onboarding/process-diagnostics.mjs +91 -0
  450. package/onboarding/process-scope.mjs +118 -0
  451. package/onboarding/process-table.mjs +118 -0
  452. package/onboarding/process-tree.mjs +36 -0
  453. package/onboarding/production-modules.mjs +22 -0
  454. package/onboarding/production-options.mjs +40 -0
  455. package/onboarding/production-plan.mjs +8 -0
  456. package/onboarding/production-runtime.mjs +30 -0
  457. package/onboarding/project-vite.mjs +14 -0
  458. package/onboarding/providers.mjs +289 -0
  459. package/onboarding/queue.mjs +53 -0
  460. package/onboarding/queued-entry.mjs +34 -0
  461. package/onboarding/readiness.mjs +59 -0
  462. package/onboarding/redaction-jwt.mjs +32 -0
  463. package/onboarding/redaction.mjs +298 -0
  464. package/onboarding/schema/SETUP.schema.json +514 -0
  465. package/onboarding/schema/dependencies.mjs +35 -0
  466. package/onboarding/schema/json-schema.mjs +202 -0
  467. package/onboarding/schema/setup-validation.mjs +325 -0
  468. package/onboarding/secrets.mjs +21 -0
  469. package/onboarding/server.mjs +188 -0
  470. package/onboarding/setup-manifest.mjs +116 -0
  471. package/onboarding/state.mjs +46 -0
  472. package/onboarding/theme-files.mjs +37 -0
  473. package/onboarding/theme.mjs +118 -0
  474. package/onboarding/ui/activity.js +98 -0
  475. package/onboarding/ui/address-client.mjs +33 -0
  476. package/onboarding/ui/app.js +377 -0
  477. package/onboarding/ui/index.html +1 -0
  478. package/onboarding/ui/model-core.mjs +127 -0
  479. package/onboarding/ui/model.mjs +5 -0
  480. package/onboarding/ui/style.css +63 -0
  481. package/onboarding/ui/theme.css +37 -0
  482. package/onboarding/ui/workspace-art.js +30 -0
  483. package/onboarding/wrangler-auth.mjs +43 -0
  484. package/operations/README.md +246 -0
  485. package/operations/operations.mjs +168 -0
  486. package/operations/project-operations.mjs +157 -0
  487. package/package.json +11 -4
  488. package/scripts/publish-check/README.md +72 -0
  489. package/scripts/publish-check/checksum_maps.py +52 -0
  490. package/scripts/publish-check/cli.py +34 -0
  491. package/scripts/publish-check/command_arguments.py +51 -0
  492. package/scripts/publish-check/command_shell_quotes.py +41 -0
  493. package/scripts/publish-check/command_source_quotes.py +120 -0
  494. package/scripts/publish-check/content_identities.py +250 -0
  495. package/scripts/publish-check/copy_identities.py +41 -0
  496. package/scripts/publish-check/der.py +61 -0
  497. package/scripts/publish-check/destination.py +117 -0
  498. package/scripts/publish-check/env_values.py +74 -0
  499. package/scripts/publish-check/font_licence.py +23 -0
  500. package/scripts/publish-check/git_history.py +115 -0
  501. package/scripts/publish-check/github_refs.py +217 -0
  502. package/scripts/publish-check/gitleaks.py +72 -0
  503. package/scripts/publish-check/gitleaks.toml +40 -0
  504. package/scripts/publish-check/history_scan.py +176 -0
  505. package/scripts/publish-check/hit_location.py +18 -0
  506. package/scripts/publish-check/hygiene_config.py +42 -0
  507. package/scripts/publish-check/hygiene_text.py +11 -0
  508. package/scripts/publish-check/identity-recipe.mjs +54 -0
  509. package/scripts/publish-check/json_identity.py +79 -0
  510. package/scripts/publish-check/license-exceptions.json +11 -0
  511. package/scripts/publish-check/licenses.py +49 -0
  512. package/scripts/publish-check/policy.py +18 -0
  513. package/scripts/publish-check/project_identity.py +89 -0
  514. package/scripts/publish-check/provenance.py +91 -0
  515. package/scripts/publish-check/provenance_pins.py +33 -0
  516. package/scripts/publish-check/public_base.py +63 -0
  517. package/scripts/publish-check/public_hygiene.py +80 -0
  518. package/scripts/publish-check/publication-config.json +12 -0
  519. package/scripts/publish-check/publication_config.py +34 -0
  520. package/scripts/publish-check/report.py +52 -0
  521. package/scripts/publish-check/repository_commands.py +75 -0
  522. package/scripts/publish-check/repository_intent.py +52 -0
  523. package/scripts/publish-check/reviewed.py +41 -0
  524. package/scripts/publish-check/source_hashes.mjs +6 -0
  525. package/scripts/publish-check/tree_scan.py +88 -0
  526. package/scripts/publish-check/trust_bundle.py +87 -0
  527. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,62 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdir, writeFile, readdir, rm } from 'node:fs/promises';
3
+ import { resolve, join, relative } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { assert, hash, localPath } from './validation.mjs';
6
+ import { collectFiles, regularBytes, validateArtifact, MAX_ARTIFACT } from './artifact.mjs';
7
+ import { collectBuildModules } from './build-modules.mjs';
8
+ import { refuseLocalModels } from './local-only.mjs';
9
+
10
+ export function assertCredentialFree(env) {
11
+ for (const [key,value] of Object.entries(env)) if (value && /(?:TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|AUTHORIZATION|COOKIE|CREDENTIAL)/i.test(key)) throw new Error('prepare-credential-environment-forbidden');
12
+ assert(!env.NODE_OPTIONS && !env.NODE_PATH,'prepare-node-injection-forbidden');
13
+ }
14
+ function run(argv, cwd, env) {
15
+ try { return execFileSync(argv[0],argv.slice(1),{cwd,env,encoding:'utf8',stdio:['ignore','pipe','pipe'],timeout:15*60*1000,maxBuffer:1024*1024}); }
16
+ catch { throw new Error('prepare-command-failed'); }
17
+ }
18
+ export async function packageBuild({root,source,runIdentity,productDirectory = 'default-seed'}) {
19
+ assert(productDirectory === '' || localPath(productDirectory), 'invalid-product-directory');
20
+ const product = join(root,productDirectory);
21
+ await refuseLocalModels(root);
22
+ if (product !== root) await refuseLocalModels(product);
23
+ const build = join(product,'dist/server');
24
+ const config = JSON.parse(await regularBytes(join(build,'wrangler.json'),1024*1024));
25
+ const main = localPath(relative(build,resolve(build,config.main)));
26
+ const modules = await collectBuildModules(build,config,main);
27
+ const assets = await collectFiles(join(product,'dist/client'));
28
+ const migrations = [];
29
+ for (const name of (await readdir(join(product,'migrations/core'))).sort()) {
30
+ assert(/^[0-9]+_[a-zA-Z0-9_-]+\.sql$/.test(name),'unexpected-migration-file');
31
+ const sql = (await regularBytes(join(product,'migrations/core',name),512000)).toString('utf8'); migrations.push({binding:'DB',name,sql,sha256:hash(sql)});
32
+ }
33
+ const artifact = {version:1,source,run:runIdentity,main,modules,assets,migrations}; validateArtifact(artifact);
34
+ return artifact;
35
+ }
36
+ export async function prepare(env = process.env) {
37
+ assertCredentialFree(env);
38
+ assert(process.platform === 'linux' && env.RER_PREPARE_ISOLATION === 'ephemeral-credential-free-job','prepare-requires-isolated-linux-job');
39
+ assert(/^[a-f0-9]{40}$/.test(env.RER_DEPLOY_SOURCE_SHA ?? '') && /^\d+$/.test(env.RER_DEPLOY_RUN_ID ?? '') && /^\d+$/.test(env.RER_DEPLOY_RUN_ATTEMPT ?? ''),'missing-prepare-identity');
40
+ const root = process.cwd();
41
+ assert(env.RER_DEPLOY_ARTIFACT && resolve(env.RER_DEPLOY_ARTIFACT) !== root && !resolve(env.RER_DEPLOY_ARTIFACT).startsWith(root+'/'),'artifact-must-be-outside-checkout');
42
+ const home = resolve(env.RER_PREPARE_HOME ?? ''); assert(home !== root && !home.startsWith(root+'/') && env.RER_PREPARE_HOME,'missing-isolated-home');
43
+ await mkdir(home,{recursive:false,mode:0o700});
44
+ try {
45
+ const cleanEnv = {PATH:env.PATH,HOME:home,CI:'true',WRANGLER_SEND_METRICS:'false',CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV:'false'};
46
+ const git = (...args) => run(['git',...args],root,cleanEnv).trim();
47
+ const source = {sha:git('rev-parse','HEAD'),tree:git('rev-parse','HEAD^{tree}')};
48
+ assert(source.sha === env.RER_DEPLOY_SOURCE_SHA && !git('status','--porcelain','--untracked-files=no'),'prepare-source-mismatch');
49
+ run(['pnpm','install','--frozen-lockfile'],root,cleanEnv);
50
+ run(['pnpm','--dir','default-seed','build'],root,cleanEnv);
51
+ assert(git('rev-parse','HEAD') === source.sha && !git('status','--porcelain','--untracked-files=no'),'source-changed-during-build');
52
+ const artifact = await packageBuild({root,source,runIdentity:{id:env.RER_DEPLOY_RUN_ID,attempt:env.RER_DEPLOY_RUN_ATTEMPT}});
53
+ const bytes = JSON.stringify(artifact); assert(Buffer.byteLength(bytes) <= MAX_ARTIFACT,'artifact-too-large');
54
+ await writeFile(env.RER_DEPLOY_ARTIFACT,bytes,{flag:'wx',mode:0o600});
55
+ // Public digest only. The trusted artifact service binds it to the run/source; stdout alone is not custody.
56
+ return {sha256:hash(bytes),source,run:artifact.run};
57
+ } finally { await rm(home,{recursive:true,force:true}); }
58
+ }
59
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
60
+ try { assert(process.argv.length === 2,'prepare-no-arguments'); console.log(JSON.stringify(await prepare())); }
61
+ catch { console.error('deploy-kit-prepare-failed'); process.exitCode=1; }
62
+ }
@@ -0,0 +1,99 @@
1
+ import { assert, hexId } from './validation.mjs';
2
+
3
+ export async function boundedJSON(response, limit = 4 * 1024 * 1024) {
4
+ assert(response.body, 'empty-response');
5
+ const reader = response.body.getReader(); const chunks = []; let length = 0;
6
+ try { while (true) { const { done, value } = await reader.read(); if (done) break; length += value.length; assert(length <= limit, 'response-too-large'); chunks.push(value); } }
7
+ finally { await reader.cancel().catch(() => {}); }
8
+ try { return JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { throw new Error('malformed-response'); }
9
+ }
10
+ /** Only this fixed origin and the explicitly selected account ever receive the token. */
11
+ export function cloudflare({ accountId, token, fetch: request = globalThis.fetch, timeoutMs = 30000, maxRequests = 500 }) {
12
+ assert(hexId(accountId), 'invalid-cloudflare-account-id');
13
+ assert(typeof token === 'string' && token.trim() && !/[\r\n]/.test(token), 'invalid-cloudflare-api-token');
14
+ let requests = 0;
15
+ async function envelope(path, { method = 'GET', body, assetToken } = {}) {
16
+ assert(typeof path === 'string' && /^\/[a-z0-9/_?=&.,%-]+$/i.test(path) && !path.includes('..') && !path.split('?')[0].includes('%'), 'invalid-provider-path');
17
+ assert(++requests <= maxRequests, 'provider-request-budget');
18
+ assert(!assetToken || path === '/workers/assets/upload?base64=true', 'invalid-asset-auth-target');
19
+ const signal = AbortSignal.timeout(timeoutMs);
20
+ let response;
21
+ try { response = await request(`https://api.cloudflare.com/client/v4/accounts/${accountId}${path}`, {
22
+ method, redirect: 'error', signal, headers: { Authorization: `Bearer ${assetToken ?? token}`, ...(body && !(body instanceof FormData) ? { 'Content-Type':'application/json' } : {}) },
23
+ ...(body === undefined ? {} : { body: body instanceof FormData ? body : JSON.stringify(body) }),
24
+ }); } catch { throw new Error('provider-transport-failed'); }
25
+ if (!response.ok) { const error = new Error(`provider-http-${response.status}`); error.status = response.status; await response.body?.cancel(); throw error; }
26
+ const payload = await boundedJSON(response);
27
+ assert(payload?.success === true && Object.hasOwn(payload,'result') && (!payload.errors || Array.isArray(payload.errors) && !payload.errors.length), 'provider-unsuccessful-or-malformed');
28
+ return payload;
29
+ }
30
+ async function list(path, { field, pagination = 'page' } = {}) {
31
+ if (pagination === 'single') {
32
+ assert(path === '/workers/scripts' || /^\/queues\/[a-f0-9]{32}\/consumers$/.test(path),'invalid-single-list');
33
+ const data = await envelope(path);
34
+ assert(Array.isArray(data.result) && data.result.length <= 10000,'provider-invalid-list');
35
+ const info = data.result_info;
36
+ if (info != null) {
37
+ assert(typeof info === 'object' && !Array.isArray(info),'provider-incomplete-pagination');
38
+ for (const key of ['count','total_count']) if (Object.hasOwn(info,key)) assert(info[key] === data.result.length,'provider-partial-list');
39
+ assert([undefined,null,''].includes(info.cursor) &&
40
+ (info.total_pages === undefined || info.total_pages === 1 || info.total_pages === 0 && data.result.length === 0) &&
41
+ (info.page === undefined || info.page === 1),'provider-incomplete-pagination');
42
+ }
43
+ return data.result;
44
+ }
45
+ assert(['page','cursor','r2-bounded'].includes(pagination),'invalid-list-pagination');
46
+ const r2Bounded = pagination === 'r2-bounded';
47
+ assert(!r2Bounded || path === '/r2/buckets' && field === 'buckets', 'invalid-r2-bounded-list');
48
+ const result = []; const seen = new Set(); let cursor; let total;
49
+ for (let page = 1; page <= 100; page++) {
50
+ const suffix = r2Bounded || pagination === 'cursor' ? (path === '/r2/buckets' ? `?per_page=1000${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}` : (cursor ? `?cursor=${encodeURIComponent(cursor)}` : '')) : `?page=${page}&per_page=100`;
51
+ const data = await envelope(path + suffix); const rows = field ? data.result?.[field] : data.result;
52
+ assert(Array.isArray(rows), 'provider-invalid-list');
53
+ result.push(...rows); assert(result.length <= 10000, 'provider-list-too-large');
54
+ if (r2Bounded) {
55
+ const info = data.result_info;
56
+ assert(rows.length <= 1000, 'provider-partial-list');
57
+ // Only this endpoint's explicit bounded strategy uses the requested
58
+ // limit when metadata is absent. Never invent metadata for other modes.
59
+ if (info === undefined || info === null) {
60
+ assert(rows.length < 1000, 'provider-incomplete-pagination');
61
+ assert(total === undefined || result.length === total, 'provider-partial-list');
62
+ return result;
63
+ }
64
+ assert(typeof info === 'object' && !Array.isArray(info) && Number.isSafeInteger(info.per_page) && info.per_page > 0 && info.per_page <= 1000, 'provider-incomplete-pagination');
65
+ assert(rows.length <= info.per_page, 'provider-partial-list');
66
+ if (Object.hasOwn(info, 'count')) assert(info.count === rows.length, 'provider-partial-list');
67
+ if (Object.hasOwn(info, 'total_count')) {
68
+ assert(Number.isSafeInteger(info.total_count) && info.total_count >= 0, 'provider-incomplete-pagination');
69
+ total ??= info.total_count; assert(total === info.total_count, 'provider-pagination-changed');
70
+ }
71
+ assert(total === undefined || result.length <= total, 'provider-partial-list');
72
+ assert(info.cursor === undefined || typeof info.cursor === 'string', 'provider-invalid-cursor');
73
+ if (!info.cursor) {
74
+ assert(rows.length < info.per_page && (total === undefined || result.length === total), 'provider-partial-list');
75
+ return result;
76
+ }
77
+ assert(total === undefined || result.length < total, 'provider-partial-list');
78
+ cursor = info.cursor; assert(/^[a-zA-Z0-9_+\/=-]+$/.test(cursor) && !seen.has(cursor), 'provider-invalid-cursor'); seen.add(cursor);
79
+ } else if (pagination === 'cursor') {
80
+ const info = data.result_info;
81
+ // R2's terminal short page may explicitly report null pagination. The
82
+ // requested documented limit supplies the bound; a full page is ambiguous.
83
+ if (path === '/r2/buckets' && info === null) { assert(rows.length < 1000, 'provider-incomplete-pagination'); return result; }
84
+ assert(info && Number.isSafeInteger(info.per_page) && info.per_page > 0, 'provider-incomplete-pagination');
85
+ if (!info.cursor) { assert(rows.length < info.per_page, 'provider-partial-list'); return result; }
86
+ cursor = info.cursor; assert(typeof cursor === 'string' && /^[a-zA-Z0-9_+\/=-]+$/.test(cursor) && !seen.has(cursor), 'provider-invalid-cursor'); seen.add(cursor);
87
+ } else {
88
+ const info = data.result_info;
89
+ assert(info && info.page === page && Number.isSafeInteger(info.total_count) && info.total_count >= 0, 'provider-incomplete-pagination');
90
+ total ??= info.total_count; assert(total === info.total_count, 'provider-pagination-changed');
91
+ assert(result.length <= total, 'provider-partial-list');
92
+ if (result.length === total) return result;
93
+ assert(rows.length > 0 && (info.total_pages === undefined || page < info.total_pages), 'provider-partial-list');
94
+ }
95
+ }
96
+ throw new Error('provider-pagination-limit');
97
+ }
98
+ return { accountId, envelope, list, async request(path, options) { return (await envelope(path, options)).result; } };
99
+ }
@@ -0,0 +1,10 @@
1
+ import { resourceName } from './validation.mjs';
2
+
3
+ /** Decode the documented and observed Worker consumer identity wire variants. */
4
+ export function workerConsumerScript(consumer) {
5
+ if (!consumer || typeof consumer !== 'object' || Array.isArray(consumer) || consumer.type !== 'worker') return null;
6
+ const fields = ['script_name','script'].filter(key => Object.hasOwn(consumer,key));
7
+ if (!fields.length || fields.some(key => !resourceName(consumer[key]) || consumer[key] !== consumer[key].trim())) return null;
8
+ const script = consumer[fields[0]];
9
+ return fields.every(key => consumer[key] === script) ? script : null;
10
+ }
@@ -0,0 +1,16 @@
1
+ import { writeFile, rename, rm } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { regularBytes } from './artifact.mjs';
4
+ import { assert, stable } from './validation.mjs';
5
+
6
+ /** Preserve the identity written before health; replace only health evidence. */
7
+ export async function writeHealthReceipt(path, receipt) {
8
+ const previous = JSON.parse(await regularBytes(path,1024*1024));
9
+ const identity = ({health,...value}) => value;
10
+ assert(stable(identity(previous)) === stable(identity(receipt)),'health-receipt-identity-mismatch');
11
+ const temporary = `${path}.health-${randomUUID()}`;
12
+ try {
13
+ await writeFile(temporary,JSON.stringify(receipt,null,2)+'\n',{flag:'wx',mode:0o600});
14
+ await rename(temporary,path);
15
+ } finally { await rm(temporary,{force:true}); }
16
+ }
@@ -0,0 +1,47 @@
1
+ import { assert, stable } from './validation.mjs';
2
+ /** Every request reaches the Worker first, so its routing, origin admission and private-path
3
+ * refusals decide what is served; the Worker serves built files from env.ASSETS itself.
4
+ * `rer verify` serves the local build with this same setting (cli/hosted-output.mjs). */
5
+ export const RUN_WORKER_FIRST = true;
6
+ export function renderConfig(plan, resources, accountId, sourceSha, fingerprint, main, hasAssets = false) {
7
+ return { name:plan.worker, account_id:accountId, main, compatibility_date:plan.compatibilityDate, compatibility_flags:plan.compatibilityFlags ?? [], workers_dev:plan.workersDev !== false,
8
+ ...(hasAssets ? {assets:{directory:'__static',binding:'ASSETS',run_worker_first:RUN_WORKER_FIRST}} : {}),
9
+ vars:{...plan.vars,RER_DEPLOY_SOURCE:sourceSha,RER_DEPLOY_FINGERPRINT:fingerprint}, version_metadata:{binding:'RER_VERSION'},
10
+ d1_databases:resources.d1.map(x => ({binding:x.binding,database_name:x.name,database_id:x.id})), r2_buckets:resources.r2.map(x => ({binding:x.binding,bucket_name:x.name})),
11
+ queues:{producers:resources.queues.map(x => ({binding:x.binding,queue:x.name})), consumers:plan.queues.filter(x => x.consumer).map(x => ({queue:x.name,...(x.deadLetterQueue ? {dead_letter_queue:x.deadLetterQueue} : {}),max_batch_size:x.consumer.batch_size,max_retries:x.consumer.max_retries,...(x.consumer.max_wait_time_ms === undefined ? {} : {max_batch_timeout:x.consumer.max_wait_time_ms/1000}),...(x.consumer.max_concurrency === undefined ? {} : {max_concurrency:x.consumer.max_concurrency}),...(x.consumer.retry_delay === undefined ? {} : {retry_delay:x.consumer.retry_delay})}))},
12
+ durable_objects:{bindings:plan.durableObjects.map(x => ({name:x.binding,class_name:x.className}))}, triggers:{crons:plan.crons},
13
+ // Only plans that declare KV add the key, so earlier fingerprints are unchanged.
14
+ ...(resources.kv ? {kv_namespaces:resources.kv.map(x => ({binding:x.binding,id:x.id}))} : {}),
15
+ ...(plan.services ? {services:plan.services.map(serviceEntry)} : {}) };
16
+ }
17
+ const serviceEntry = x => ({binding:x.binding,service:x.service,...(x.entrypoint ? {entrypoint:x.entrypoint} : {}),...(x.props ? {props:x.props} : {})});
18
+ function serviceBinding(x) {
19
+ const {binding,...rest} = serviceEntry(x);
20
+ return {type:'service',name:binding,...rest};
21
+ }
22
+ export function bindingsFor(config, resources, plan) {
23
+ return [ ...(config.assets ? [{type:'assets',name:'ASSETS'}] : []), ...Object.entries(config.vars).map(([name,text]) => ({type:'plain_text',name,text})), {type:'version_metadata',name:'RER_VERSION'},
24
+ ...resources.d1.map(x => ({type:'d1',name:x.binding,id:x.id})), ...resources.r2.map(x => ({type:'r2_bucket',name:x.binding,bucket_name:x.name})),
25
+ ...(resources.kv ?? []).map(x => ({type:'kv_namespace',name:x.binding,namespace_id:x.id})),
26
+ ...resources.queues.map(x => ({type:'queue',name:x.binding,queue_name:x.name})), ...plan.durableObjects.map(x => ({type:'durable_object_namespace',name:x.binding,class_name:x.className})),
27
+ ...(plan.services ?? []).map(serviceBinding) ];
28
+ }
29
+ export function verifyBindings(actual, expected) {
30
+ assert(Array.isArray(actual),'invalid-provider-bindings');
31
+ const names = new Set();
32
+ const services = new Set(expected.filter(x => x.type === 'service').map(x => x.name));
33
+ for (const binding of actual) {
34
+ assert(binding && typeof binding.name === 'string' && binding.name.length && !names.has(binding.name), 'binding-readback-mismatch');
35
+ names.add(binding.name);
36
+ // Provider-added secrets and ordinary bindings can be legitimate, but every service
37
+ // binding grants authority and must be explicitly declared by the caller.
38
+ assert(binding.type !== 'service' || services.has(binding.name), 'binding-readback-mismatch');
39
+ }
40
+ for (const desired of expected) {
41
+ const rows = actual.filter(x => x.name === desired.name); assert(rows.length === 1,'binding-readback-mismatch');
42
+ if (desired.type === 'service') {
43
+ assert(Object.keys(rows[0]).every(key => Object.hasOwn(desired, key) || key === 'environment' && rows[0][key] === 'production'), 'binding-readback-mismatch');
44
+ }
45
+ for (const [key,value] of Object.entries(desired)) assert(stable(rows[0][key]) === stable(value),'binding-readback-mismatch');
46
+ }
47
+ }
@@ -0,0 +1,106 @@
1
+ import { assert, uuid, hexId, kvOf } from './validation.mjs';
2
+ import { workerConsumerScript } from './queue-consumer.mjs';
3
+ function unique(rows, key, name) { const found = rows.filter(x => x?.[key] === name); assert(found.length <= 1,'ambiguous-resource-name'); return found[0]; }
4
+ /** Account-level resources found or created by name. Durable Object namespaces
5
+ * belong to their Worker and are created by its upload migration instead. */
6
+ export const resourceKinds = Object.freeze({
7
+ d1: {items:plan => plan.d1, key:'name',
8
+ list:api => api.list('/d1/database'),
9
+ create:(api,item) => api.request('/d1/database',{method:'POST',body:{name:item.name}})},
10
+ r2: {items:plan => plan.r2, key:'name',
11
+ list:api => api.list('/r2/buckets',{field:'buckets',pagination:'r2-bounded'}),
12
+ create:(api,item) => api.request('/r2/buckets',{method:'POST',body:{name:item.name,...(item.locationHint ? {locationHint:item.locationHint}: {})}})},
13
+ kv: {items:kvOf, key:'title',
14
+ list:api => api.list('/storage/kv/namespaces'),
15
+ create:(api,item) => api.request('/storage/kv/namespaces',{method:'POST',body:{title:item.name}})},
16
+ queues: {items:plan => plan.queues, key:'queue_name',
17
+ list:api => api.list('/queues'),
18
+ create:(api,item) => api.request('/queues',{method:'POST',body:{queue_name:item.name}})},
19
+ });
20
+ export function findResource(rows, kind, name) { return unique(rows,resourceKinds[kind].key,name); }
21
+ export async function reconcileResources(api, plan, { create = true } = {}) {
22
+ const resources = { d1:[], r2:[], queues:[] };
23
+ if (plan.d1.length) {
24
+ const rows = await resourceKinds.d1.list(api);
25
+ for (const item of plan.d1) {
26
+ let row = unique(rows,'name',item.name);
27
+ if (!row) { assert(create,'missing-d1'); row = await resourceKinds.d1.create(api,item); }
28
+ assert(row.name === item.name && uuid(row.uuid), 'invalid-d1-identity');
29
+ const read = await api.request(`/d1/database/${row.uuid}`);
30
+ assert(read.uuid === row.uuid && read.name === item.name && (!item.readReplication || read.read_replication?.mode === item.readReplication), 'incompatible-d1');
31
+ resources.d1.push({binding:item.binding,name:item.name,id:row.uuid});
32
+ }
33
+ }
34
+ if (plan.r2.length) {
35
+ const rows = await resourceKinds.r2.list(api);
36
+ for (const item of plan.r2) {
37
+ let row = unique(rows,'name',item.name);
38
+ if (!row) { assert(create,'missing-r2'); row = await resourceKinds.r2.create(api,item); }
39
+ const read = await api.request(`/r2/buckets/${item.name}`);
40
+ assert(row.name === item.name && read.name === item.name && (!item.jurisdiction || read.jurisdiction === item.jurisdiction) && (!item.storageClass || read.storage_class === item.storageClass), 'incompatible-r2');
41
+ resources.r2.push({binding:item.binding,name:item.name});
42
+ }
43
+ }
44
+ // Only plans that declare KV carry the key, so earlier receipts compare unchanged.
45
+ if (kvOf(plan).length) {
46
+ resources.kv = [];
47
+ const rows = await resourceKinds.kv.list(api);
48
+ for (const item of kvOf(plan)) {
49
+ let row = unique(rows,'title',item.name);
50
+ if (!row) { assert(create,'missing-kv'); row = await resourceKinds.kv.create(api,item); }
51
+ assert(row.title === item.name && hexId(row.id), 'invalid-kv-identity');
52
+ const read = await api.request(`/storage/kv/namespaces/${row.id}`);
53
+ assert(read.id === row.id && read.title === item.name, 'incompatible-kv');
54
+ resources.kv.push({binding:item.binding,name:item.name,id:row.id});
55
+ }
56
+ }
57
+ if (plan.queues.length) {
58
+ const rows = await resourceKinds.queues.list(api);
59
+ for (const item of plan.queues) {
60
+ let row = unique(rows,'queue_name',item.name);
61
+ if (!row) { assert(create,'missing-queue'); row = await resourceKinds.queues.create(api,item); }
62
+ assert(row.queue_name === item.name && hexId(row.queue_id), 'invalid-queue-identity');
63
+ const read = await api.request(`/queues/${row.queue_id}`);
64
+ assert(read.queue_name === item.name && read.queue_id === row.queue_id && read.settings?.delivery_paused !== true, 'incompatible-or-paused-queue');
65
+ resources.queues.push({binding:item.binding,name:item.name,id:row.queue_id});
66
+ }
67
+ }
68
+ return resources;
69
+ }
70
+ export async function reconcileTriggers(api, plan, resources, { mutate = true, reapplyCrons = false } = {}) {
71
+ // PUT replaces the entire schedule set. Inspect it before any trigger write,
72
+ // and never remove a schedule that the requested plan does not own.
73
+ const crons = await checkCronOwnership(api, plan);
74
+ const updates = [];
75
+ for (const item of plan.queues.filter(x => x.consumer)) {
76
+ const id = resources.queues.find(x => x.binding === item.binding).id;
77
+ const consumers = await api.request(`/queues/${id}/consumers`);
78
+ assert(Array.isArray(consumers), 'invalid-queue-consumers');
79
+ const wanted = {type:'worker',script_name:plan.worker,settings:item.consumer,...(item.deadLetterQueue ? {dead_letter_queue:item.deadLetterQueue} : {})};
80
+ const matches = consumers.filter(x => workerConsumerScript(x) === plan.worker);
81
+ assert(consumers.length === matches.length && matches.length <= 1, 'queue-consumer-conflict');
82
+ const row = matches[0];
83
+ if (!row || (row.dead_letter_queue || '') !== (item.deadLetterQueue || '') || Object.entries(item.consumer).some(([k,v]) => row.settings?.[k] !== v)) {
84
+ assert(mutate,'queue-consumer-drift');
85
+ if (row) assert(hexId(row.consumer_id),'invalid-consumer-id');
86
+ updates.push({path:`/queues/${id}/consumers${row ? `/${row.consumer_id}`:''}`,options:{method:row ? 'PUT':'POST',body:wanted}});
87
+ }
88
+ }
89
+ // Validate every consumer's ownership before applying any trigger update.
90
+ for (const {path,options} of updates) await api.request(path,options);
91
+ const desired = JSON.stringify([...plan.crons].sort());
92
+ if (JSON.stringify(crons) !== desired || reapplyCrons && plan.crons.length > 0) {
93
+ assert(mutate,'cron-drift');
94
+ await api.request(`/workers/scripts/${plan.worker}/schedules`,{method:'PUT',body:plan.crons.map(cron => ({cron}))});
95
+ assert(JSON.stringify(await checkCronOwnership(api,plan)) === desired,'cron-drift');
96
+ }
97
+ }
98
+
99
+ export async function checkCronOwnership(api, plan) {
100
+ const result = await api.request(`/workers/scripts/${plan.worker}/schedules`);
101
+ assert(result && !Array.isArray(result) && Array.isArray(result.schedules) && result.schedules.every(x => typeof x?.cron === 'string' && x.cron.trim()), 'invalid-cron-list');
102
+ const crons = result.schedules.map(x => x.cron).sort();
103
+ assert(new Set(crons).size === crons.length, 'invalid-cron-list');
104
+ assert(crons.every(cron => plan.crons.includes(cron)), 'cron-drift');
105
+ return crons;
106
+ }
@@ -0,0 +1,25 @@
1
+ import { assert, stable } from './validation.mjs';
2
+ import { activeWorker } from './worker.mjs';
3
+
4
+ /** A named service entrypoint's authority is stored by the service owner. Cloudflare
5
+ * settings and immutable deployment identity are both checked; no omitted props waiver. */
6
+ export async function verifyServiceGrants(api, plan) {
7
+ for (const binding of plan.services ?? []) {
8
+ if (!binding.grant) continue;
9
+ const current = await activeWorker(api, binding.service), expected = binding.grant;
10
+ assert(current && ['versionId','deploymentId','etag'].every(key => current[key] === expected.provider[key]), 'service-grant-deployment-drift');
11
+ const values = {};
12
+ for (const name of ['EGRESS_GRANTS','RER_DEPLOY_SOURCE','RER_DEPLOY_FINGERPRINT']) {
13
+ const rows = current.settings.bindings.filter(b => b.name === name);
14
+ assert(rows.length === 1 && rows[0].type === 'plain_text' && typeof rows[0].text === 'string', 'service-grant-readback-mismatch');
15
+ values[name] = rows[0].text;
16
+ }
17
+ assert(values.RER_DEPLOY_SOURCE === expected.sourceSha && values.RER_DEPLOY_FINGERPRINT === expected.fingerprint, 'service-grant-deployment-drift');
18
+ let grants; try { grants = JSON.parse(values.EGRESS_GRANTS); } catch { throw new Error('service-grant-readback-mismatch'); }
19
+ assert(Array.isArray(grants) && grants.length && grants.every(row => row && Object.keys(row).sort().join() === 'allowedHosts,entrypoint'
20
+ && typeof row.entrypoint === 'string' && (row.allowedHosts === null || Array.isArray(row.allowedHosts)))
21
+ && new Set(grants.map(row => row.entrypoint)).size === grants.length, 'service-grant-readback-mismatch');
22
+ const rows = grants.filter(row => row.entrypoint === binding.entrypoint);
23
+ assert(rows.length === 1 && stable(rows[0].allowedHosts) === stable(expected.allowedHosts), 'service-grant-readback-mismatch');
24
+ }
25
+ }
@@ -0,0 +1,94 @@
1
+ import { assert } from './validation.mjs';
2
+ import { sqlTokens } from './sql-tokens.mjs';
3
+
4
+ const reserved = new Set(['_rer_deploy_migrations', 'd1_migrations']);
5
+ const controls = new Set(['BEGIN', 'COMMIT', 'END', 'ROLLBACK', 'SAVEPOINT', 'RELEASE', 'ATTACH', 'DETACH', 'VACUUM']);
6
+ const needsOperand = new Set(['CASE', 'WHEN', 'THEN', 'ELSE', 'AND', 'OR', 'IS', 'NOT', 'IN', 'LIKE', 'GLOB', 'MATCH', 'REGEXP', 'BETWEEN', 'ESCAPE', 'COLLATE', 'AS', 'DISTINCT', 'FROM']);
7
+ const keyword = token => token?.kind === 'word' ? token.value.toUpperCase() : '';
8
+ const symbol = (token, value) => token?.kind === 'symbol' && token.value === value;
9
+
10
+ function createsTrigger(statement) {
11
+ let index = 0;
12
+ if (keyword(statement[index++]) !== 'CREATE') return false;
13
+ if (['TEMP', 'TEMPORARY'].includes(keyword(statement[index]))) index++;
14
+ return keyword(statement[index]) === 'TRIGGER';
15
+ }
16
+
17
+ function endsExpression(tokens, index) {
18
+ const token = tokens[index];
19
+ // After a function call or FILTER clause, OVER still needs its window.
20
+ // Elsewhere SQLite permits OVER as an ordinary column or window name.
21
+ if (keyword(token) === 'OVER' && symbol(tokens[index - 1], ')')) return false;
22
+ return token && (['string', 'identifier', 'number'].includes(token.kind) ||
23
+ (token.kind === 'word' && !needsOperand.has(keyword(token))) || symbol(token, ')'));
24
+ }
25
+
26
+ function triggerBegin(tokens) {
27
+ let index = keyword(tokens[1]) === 'TRIGGER' ? 2 : 3;
28
+ if (keyword(tokens[index]) === 'IF') {
29
+ assert(keyword(tokens[index + 1]) === 'NOT' && keyword(tokens[index + 2]) === 'EXISTS', 'unsafe-migration-control');
30
+ index += 3;
31
+ }
32
+ const afterName = start => symbol(tokens[start + 1], '.') ? start + 3 : start + 1;
33
+ index = afterName(index);
34
+ while (index < tokens.length && keyword(tokens[index]) !== 'ON') index++;
35
+ index = afterName(index + 1);
36
+ for (; index < tokens.length; index++) {
37
+ if (keyword(tokens[index]) === 'BEGIN' && endsExpression(tokens, index - 1) && !symbol(tokens[index + 1], '.')) return index;
38
+ }
39
+ return -1;
40
+ }
41
+
42
+ function assertClosedExpressions(tokens) {
43
+ const scopes = [];
44
+ for (let index = 0; index < tokens.length; index++) {
45
+ const token = tokens[index];
46
+ if (symbol(token, '(')) scopes.push('(');
47
+ else if (keyword(token) === 'CASE') scopes.push('CASE');
48
+ else if (symbol(token, ')')) assert(scopes.pop() === '(', 'unsafe-migration-control');
49
+ // END may instead be an operand (THEN end) or a qualified identifier
50
+ // (NEW.end). Only an END following a complete expression closes CASE.
51
+ else if (keyword(token) === 'END' && scopes.at(-1) === 'CASE' &&
52
+ endsExpression(tokens, index - 1) && !symbol(tokens[index + 1], '.')) scopes.pop();
53
+ }
54
+ assert(scopes.length === 0, 'unsafe-migration-control');
55
+ }
56
+
57
+ export function assertMigrationSqlSafe(sql) {
58
+ const tokens = sqlTokens(sql);
59
+ // SQLite also treats single-quoted text as identifiers and virtual-table
60
+ // options as table names. Reserve exact controller names in every token.
61
+ assert(tokens.every(token => !reserved.has(token.value.toLowerCase())), 'unsafe-migration-control');
62
+ // ROLLBACK also controls transactions in ON CONFLICT / OR clauses and
63
+ // RAISE(ROLLBACK, ...), without beginning a standalone SQL statement.
64
+ assert(tokens.every(token => keyword(token) !== 'ROLLBACK'), 'unsafe-migration-control');
65
+ let inTrigger = false;
66
+ let statement = [];
67
+ const finishStatement = () => {
68
+ if (!statement.length) return;
69
+ const first = keyword(statement[0]);
70
+ if (inTrigger && first === 'END' && statement.length === 1) {
71
+ inTrigger = false;
72
+ } else {
73
+ assert(!controls.has(first), 'unsafe-migration-control');
74
+ if (createsTrigger(statement)) {
75
+ const begin = triggerBegin(statement);
76
+ assert(!inTrigger && begin >= 0, 'unsafe-migration-control');
77
+ // Header CASE expressions must close before BEGIN; body expressions
78
+ // must close before each semicolon, never at the trigger's final END.
79
+ assertClosedExpressions(statement.slice(0, begin));
80
+ const body = statement.slice(begin + 1);
81
+ assert(body.length > 0 && !controls.has(keyword(body[0])), 'unsafe-migration-control');
82
+ assertClosedExpressions(body);
83
+ inTrigger = true;
84
+ } else assertClosedExpressions(statement);
85
+ }
86
+ statement = [];
87
+ };
88
+ for (const token of tokens) {
89
+ if (symbol(token, ';')) finishStatement();
90
+ else statement.push(token);
91
+ }
92
+ finishStatement();
93
+ assert(!inTrigger, 'unsafe-migration-control');
94
+ }
@@ -0,0 +1,49 @@
1
+ import { assert } from './validation.mjs';
2
+
3
+ // SQLite quotes escape themselves by doubling, except brackets, which end at ].
4
+ // Keep quoted tokens intact: their punctuation and keywords are never syntax.
5
+ export function sqlTokens(sql) {
6
+ assert(!sql.includes('\0'), 'unsafe-migration-control');
7
+ const tokens = [];
8
+ let offset = 0;
9
+ while (offset < sql.length) {
10
+ const start = offset;
11
+ const char = sql[offset++];
12
+ if (/[\t\n\v\f\r \uFEFF]/.test(char)) continue;
13
+ if (char === '-' && sql[offset] === '-') {
14
+ const end = sql.indexOf('\n', offset + 1);
15
+ offset = end < 0 ? sql.length : end + 1;
16
+ continue;
17
+ }
18
+ if (char === '/' && sql[offset] === '*') {
19
+ const end = sql.indexOf('*/', offset + 1);
20
+ // An open comment could swallow the controller's appended completion SQL.
21
+ assert(end >= 0, 'unsafe-migration-control');
22
+ offset = end + 2;
23
+ continue;
24
+ }
25
+ if (`'\"\`[`.includes(char)) {
26
+ const closing = char === '[' ? ']' : char;
27
+ let value = '', closed = false;
28
+ while (offset < sql.length) {
29
+ const next = sql[offset++];
30
+ if (next !== closing) { value += next; continue; }
31
+ if (char !== '[' && sql[offset] === closing) { value += closing; offset++; continue; }
32
+ closed = true;
33
+ break;
34
+ }
35
+ assert(closed, 'unsafe-migration-control');
36
+ tokens.push({ kind: char === "'" ? 'string' : 'identifier', value });
37
+ } else if (/[0-9]/.test(char) || (char === '.' && /[0-9]/.test(sql[offset] ?? ''))) {
38
+ const number = /^(?:0[xX][0-9a-fA-F_]+|(?:[0-9][0-9_]*(?:\.[0-9_]*)?|\.[0-9][0-9_]*)(?:[eE][+-]?[0-9][0-9_]*)?)/.exec(sql.slice(start))[0];
39
+ offset = start + number.length;
40
+ tokens.push({ kind: 'number', value: number });
41
+ } else if (/[A-Za-z_\u0080-\uFFFF]/.test(char)) {
42
+ while (offset < sql.length && /[A-Za-z0-9_$\u0080-\uFFFF]/.test(sql[offset])) offset++;
43
+ tokens.push({ kind: 'word', value: sql.slice(start, offset) });
44
+ } else {
45
+ tokens.push({ kind: 'symbol', value: char });
46
+ }
47
+ }
48
+ return tokens;
49
+ }
@@ -0,0 +1,100 @@
1
+ import { assert, hexId, uuid } from './validation.mjs';
2
+ import { workerConsumerScript } from './queue-consumer.mjs';
3
+
4
+ function checked(rows, key, valid) {
5
+ assert(Array.isArray(rows) && rows.every(row => row && valid(row[key])),'teardown-invalid-list-identity');
6
+ assert(new Set(rows.map(row => row[key])).size === rows.length,'teardown-duplicate-list-identity');
7
+ return rows;
8
+ }
9
+ const listedName = value => typeof value === 'string' && value.length > 0;
10
+ const name = value => typeof value === 'string' && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value);
11
+
12
+ /** Read every account list. Post-delete callers restart this entire snapshot. */
13
+ async function inventory(api) {
14
+ const reads = await Promise.allSettled([
15
+ api.list('/workers/scripts',{pagination:'single'}), api.list('/d1/database'),
16
+ api.list('/r2/buckets',{field:'buckets',pagination:'r2-bounded'}), api.list('/queues'),
17
+ api.list('/workers/durable_objects/namespaces'),
18
+ ]);
19
+ const failure = reads.find(result => result.status === 'rejected');
20
+ if (failure) throw failure.reason;
21
+ const [workers,d1,r2,queues,durableObjects] = reads.map(result => result.value);
22
+ checked(workers,'id',listedName); checked(d1,'uuid',uuid); checked(r2,'name',listedName); checked(queues,'queue_id',hexId); checked(durableObjects,'id',hexId);
23
+ assert(d1.every(row=>listedName(row.name)) && queues.every(row=>listedName(row.queue_name)) && durableObjects.every(row=>listedName(row.script)),'teardown-invalid-list-name');
24
+ const consumers = [];
25
+ // The provider's global request budget bounds consumer reads as well.
26
+ for (const queue of queues) {
27
+ const rows = checked(await api.list(`/queues/${queue.queue_id}/consumers`,{pagination:'single'}),'consumer_id',hexId);
28
+ assert(rows.every(row=>row.type !== 'worker' || workerConsumerScript(row)),'teardown-invalid-worker-consumer');
29
+ consumers.push(...rows.map(row=>({...row,queueId:queue.queue_id,queueName:queue.queue_name})));
30
+ }
31
+ return {workers,d1,r2,queues,consumers,durableObjects};
32
+ }
33
+
34
+ /** Read-only verification. It never deletes, repairs or authorizes deletion. */
35
+ export async function verifyTeardown({api,plan,receipt,prefix=plan?.worker,maxAttempts=4,clock=()=>performance.now(),sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms)),onAttempt=()=>{}}) {
36
+ assert(receipt?.version === 1 && receipt.accountId === api.accountId && receipt.worker === plan?.worker,'teardown-receipt-target-mismatch');
37
+ assert(name(prefix) && (prefix.startsWith('rer-seedproof-') || /^rer-base-stage-[0-9]{1,20}$/.test(prefix)) && plan.worker === prefix,'teardown-disposable-prefix-required');
38
+ assert(Number.isSafeInteger(maxAttempts) && maxAttempts >= 1 && maxAttempts <= 8,'teardown-invalid-attempt-bound');
39
+ assert(Array.isArray(receipt.resources?.d1) && Array.isArray(receipt.resources.r2) && Array.isArray(receipt.resources.queues) && Array.isArray(receipt.durableObjects),'teardown-invalid-receipt');
40
+ assert(receipt.resources.d1.every(row=>uuid(row.id) && listedName(row.name)) && receipt.resources.r2.every(row=>listedName(row.name)) && receipt.resources.queues.every(row=>hexId(row.id) && listedName(row.name)) && receipt.durableObjects.every(row=>hexId(row.id)),'teardown-invalid-receipt-identity');
41
+ const attempts = []; const start = clock();
42
+ const prefixed = value => typeof value === 'string' && value.startsWith(prefix);
43
+ for (let number=1; number<=maxAttempts; number++) {
44
+ const attempt = {number,offsetMs:Math.round(clock()-start),errorClass:null}; attempts.push(attempt);
45
+ try {
46
+ const listing = await inventory(api);
47
+ const remaining = {
48
+ workers:listing.workers.filter(row=>row.id === receipt.worker || prefixed(row.id)),
49
+ d1:listing.d1.filter(row=>receipt.resources.d1.some(owned=>owned.id === row.uuid) || prefixed(row.name)),
50
+ r2:listing.r2.filter(row=>receipt.resources.r2.some(owned=>owned.name === row.name) || prefixed(row.name)),
51
+ queues:listing.queues.filter(row=>receipt.resources.queues.some(owned=>owned.id === row.queue_id) || prefixed(row.queue_name)),
52
+ consumers:listing.consumers.filter(row=>receipt.resources.queues.some(owned=>owned.id === row.queueId) || prefixed(row.queueName) || workerConsumerScript(row) === receipt.worker || prefixed(workerConsumerScript(row))),
53
+ durableObjects:listing.durableObjects.filter(row=>receipt.durableObjects.some(owned=>owned.id === row.id) || prefixed(row.script) || prefixed(row.name) || prefixed(row.class)),
54
+ };
55
+ attempt.counts = Object.fromEntries(Object.entries(listing).map(([type,rows])=>[type,rows.length]));
56
+ const survivors = Object.entries(remaining).flatMap(([type,rows])=>rows.map(row=>({type,name:row.name ?? row.queue_name ?? row.script ?? row.id,id:row.uuid ?? row.queue_id ?? row.consumer_id ?? row.id})));
57
+ if (survivors.length) {
58
+ const error = new Error('teardown-resources-remain'); error.remaining = survivors; error.listing = remaining; throw error;
59
+ }
60
+ await onAttempt(attempt);
61
+ // The scoped view is the empty listing that was seen; foreign rows stay out of the evidence.
62
+ return {ok:true,scope:'authenticated-scoped-teardown-verification',prefix,accountId:api.accountId,attempts,listing:remaining};
63
+ } catch (error) {
64
+ attempt.errorClass = error.message;
65
+ await onAttempt(attempt);
66
+ // Partial snapshots can accompany concurrent removal as well. They may
67
+ // be retried here, but never accepted, inferred empty, or relaxed.
68
+ if (number === maxAttempts || !['provider-pagination-changed','provider-partial-list'].includes(error.message)) {
69
+ error.attempts = attempts;
70
+ throw error;
71
+ }
72
+ await sleep(Math.min(250 * 2 ** (number-1),2000));
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Delete a Worker the caller has already proven it owns. The provider keeps dispatching a
79
+ * deleted Worker's Cron (as failing invocations) until its next scheduler refresh, so clear the
80
+ * schedule set first, wait until the provider reports none, and only then delete. A set that does
81
+ * not settle within `maxWaitMs` leaves the Worker in place.
82
+ */
83
+ export async function deleteWorker({api,worker,maxWaitMs=60000,clock=()=>performance.now(),sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms))}) {
84
+ assert(name(worker),'teardown-invalid-worker-name');
85
+ const schedules = `/workers/scripts/${worker}/schedules`;
86
+ await api.request(schedules,{method:'PUT',body:[]});
87
+ const start = clock();
88
+ for (let delay=250;; delay=Math.min(delay*2,4000)) {
89
+ const result = await api.request(schedules);
90
+ assert(result && !Array.isArray(result) && Array.isArray(result.schedules),'invalid-cron-list');
91
+ if (!result.schedules.length) break;
92
+ if (clock()-start+delay > maxWaitMs) {
93
+ const error = new Error(`The provider still lists ${result.schedules.length} Cron schedule(s) on ${worker} ${Math.round(maxWaitMs/1000)} seconds after they were cleared, so the Worker was not deleted. Run the teardown again once the provider reports none.`);
94
+ error.code = 'teardown-schedules-not-cleared';
95
+ throw error;
96
+ }
97
+ await sleep(delay);
98
+ }
99
+ await api.request(`/workers/scripts/${worker}?force=true`,{method:'DELETE'});
100
+ }