@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,120 @@
1
+ # Resend outbound adapter
2
+
3
+ `sendResend(config, message, dependencies = {})` exports the portable, single
4
+ submission adapter from `index.mjs`. Use the mail facade for ordinary sends and
5
+ unsubscribe assembly. Direct calls validate configuration and the message through
6
+ the real core helpers before any request. No SDK, Node API or build is required.
7
+
8
+ The adapter submits JSON to `https://api.resend.com/emails` using `POST` and
9
+ `Authorization: Bearer <apiKey>`. Envelope display names, `to`, `cc`, `bcc`, subject,
10
+ text/HTML, `reply_to` and validated custom/thread headers are preserved. Names with
11
+ mailbox delimiters are quoted. Omitted text allows Resend's HTML-to-text generation;
12
+ an explicit empty text string disables that generation. At most 50 `to` recipients
13
+ are accepted. [Send API](https://resend.com/docs/api-reference/emails/send-email)
14
+
15
+ Attachments carry the original filename, Base64-encoded bytes and `content_type`.
16
+ An inline attachment requires `contentId`; this becomes `content_id`. With no
17
+ explicit disposition, a supplied content ID selects inline delivery. Without an
18
+ ID the attachment is ordinary. Explicit `disposition: 'attachment'` together with
19
+ an ID is rejected as `unsupported_resend_attachment_disposition`: Resend has no
20
+ independent send disposition field. No remote attachment URLs are fetched.
21
+ [Wire schema](https://github.com/resend/resend-openapi/blob/main/resend.yaml)
22
+ and [inline images](https://resend.com/docs/dashboard/emails/embed-inline-images)
23
+
24
+ ## Outbound size budget
25
+
26
+ Resend documents a **40 MB total email limit, including attachments after Base64
27
+ encoding**. This adapter treats MB as decimal (40,000,000 bytes) and enforces a
28
+ conservative local budget before making any request.
29
+ [Provider attachment limitations](https://resend.com/docs/dashboard/emails/attachments#attachment-limitations)
30
+
31
+ - The projected email starts with a 1,000,000-byte reserve for provider-added
32
+ envelope, MIME boundaries, headers and signatures. Each text/HTML or attachment
33
+ part adds 2,048 bytes. Metadata (sender, all `to`/`cc`/`bcc`, reply address,
34
+ subject, header names/values, filenames, content types and content IDs) costs
35
+ six times its UTF-8 byte length plus 128 bytes per string, covering encoding and
36
+ folding. An HTML-only request also reserves an equivalent generated text part.
37
+ - Each attachment costs `4 * ceil(rawByteLength / 3)` Base64 bytes, plus two bytes
38
+ per 76-character MIME line. The aggregate includes every attachment, including
39
+ inline and empty attachments; there is no separate per-file allowance.
40
+ - Text and HTML cost the larger of folded Base64 and conservative quoted-printable
41
+ encoding. The latter counts printable ASCII other than `=` as one byte, CR/LF
42
+ as two each, and other UTF-8 bytes as three; all spaces/tabs are counted as
43
+ encoded. It reserves three bytes per 73 encoded bytes for soft line breaks.
44
+ - The projected JSON request must fit 39,000,000 UTF-8 bytes. It counts JSON
45
+ escapes (including lone surrogates), Base64 padding, 1,024 bytes of fixed
46
+ syntax, 64 extra bytes per metadata string and 128 per attachment. After
47
+ serialization, the actual request is independently checked against the same
48
+ 39,000,000-byte ceiling before fetch.
49
+
50
+ The checks are incremental and do not serialize metadata, allocate UTF-8 copies,
51
+ map recipient/attachment arrays or read attachment content to estimate size. All
52
+ attachments pass the aggregate check before the first Base64 encoding. The normal
53
+ facade still validates and snapshots the caller's input as documented; these
54
+ limits bound outbound serialization, not caller-owned input allocation.
55
+
56
+ Exceeding either budget throws the public redacted `MailValidationError` with
57
+ code `resend_message_too_large`, from both direct adapter and facade calls. No
58
+ fetch, outbox write, retry, truncation or alternate send occurs. This is invalid
59
+ input, not an uncertain submission. Bounds are inclusive; additional metadata or
60
+ a second alternative reduces the permitted body/attachment size. These local
61
+ estimates deliberately reject some requests below the provider maximum; the
62
+ provider remains responsible for validating its final generated MIME message.
63
+
64
+ SendGrid-specific `customArgs`, `asmGroupId` and `subscriptionTracking` are
65
+ unsupported and rejected. A remaining `unsubscribe` property is rejected with
66
+ `unsubscribe_requires_facade`; the facade must first assemble List-Unsubscribe
67
+ headers and remove the request property. The adapter also refuses custom email
68
+ headers named `Idempotency-Key` or `Resend-Idempotency-Key`, avoiding a second source
69
+ of correlation/suppression policy. The caller's `deliveryId` is never inserted as
70
+ a provider receipt.
71
+
72
+ ## Idempotency and outcomes
73
+
74
+ `deliveryId` supplies the exact HTTP `Idempotency-Key`. This adapter accepts 1–256
75
+ printable ASCII characters with no leading/trailing whitespace, so Fetch cannot
76
+ silently alter the key. It does not hash or truncate unsupported keys. Applications
77
+ must namespace their keys and retain the immutable payload for a send command.
78
+ Resend retains keys for 24 hours: repeating the same request in that window yields
79
+ the original response without another send. After expiry, reuse can send again;
80
+ this is not indefinite duplicate suppression. A different payload under the same
81
+ key produces HTTP 409 `invalid_idempotent_request`; an unfinished concurrent request
82
+ produces 409 `concurrent_idempotent_requests`.
83
+ [Provider idempotency contract](https://resend.com/docs/dashboard/emails/idempotency-keys)
84
+
85
+ - HTTP 200 with a nonempty, control-free, bounded JSON `id` returns `accepted` and
86
+ that actual ID. Missing/malformed IDs, malformed/oversized JSON, response stream
87
+ failure and unexpected success statuses return `uncertain` with a null ID.
88
+ - 429 returns `rate_limited`; 408, redirects, server errors and transport failures
89
+ return `uncertain`. Other ordinary 4xx statuses return `rejected`.
90
+ - A recognized payload conflict returns `rejected`/`idempotency_conflict` for this
91
+ attempt. It does not establish whether the original request was accepted.
92
+ A concurrent conflict returns `uncertain`/`idempotency_in_progress`. Unknown or
93
+ malformed 409 responses are conservatively `uncertain`.
94
+
95
+ There are no retries or alternate providers/outboxes. Core transport forces manual
96
+ redirects and a 15-second request timeout. Structured response reads additionally
97
+ stop at 64 KiB or 15 seconds and cancel unfinished streams. Diagnostics are fixed
98
+ codes; response/error bodies and credentials are never returned or logged. HTTP
99
+ acceptance is not delivery, inbox receipt, or proof of delivered header/DKIM
100
+ coverage. Applications own durable policy and reconciliation.
101
+
102
+ ## Verification
103
+
104
+ Run from the Base root:
105
+
106
+ ```sh
107
+ node --test modules/mail/tests/resend/*.test.mjs
108
+ node --check modules/mail/src/adapters/resend/index.mjs
109
+ node --check modules/mail/src/adapters/resend/serialize.mjs
110
+ node --check modules/mail/src/adapters/resend/response.mjs
111
+ node --check modules/mail/src/adapters/resend/size.mjs
112
+ ```
113
+
114
+ Fixtures live only in `modules/mail/tests/resend`; they inspect real Web Request
115
+ bytes and Response streams. Size boundaries run in the adapter unit suite;
116
+ `tests/runtime/workerd.test.mjs` checks a binary attachment through the facade
117
+ and adapters, and `tests/assembly/workerd.test.mjs` checks the doctor sender chain.
118
+ These checks establish local protocol behavior, not live-provider delivery.
119
+ No provider credentials or live mail are used by these unit tests. Official
120
+ references were inspected on 6 October 2026.
@@ -0,0 +1,22 @@
1
+ import { fail } from '../../core/errors.mjs';
2
+ import { validateConfig } from '../../core/validation.mjs';
3
+ import { requestProvider } from '../../core/transport.mjs';
4
+ import { serializeResendMessage } from './serialize.mjs';
5
+ import { resendResult } from './response.mjs';
6
+
7
+ /** One submission. Acceptance is not mailbox delivery; callers own reconciliation. */
8
+ export async function sendResend(config, message, dependencies = {}) {
9
+ validateConfig(config, dependencies);
10
+ if (config.provider !== 'resend') fail('invalid_provider');
11
+ const body = serializeResendMessage(message);
12
+ const response = await requestProvider('https://api.resend.com/emails', {
13
+ method: 'POST',
14
+ headers: {
15
+ Authorization: `Bearer ${config.apiKey}`,
16
+ 'Content-Type': 'application/json',
17
+ 'Idempotency-Key': message.deliveryId,
18
+ },
19
+ body,
20
+ }, dependencies);
21
+ return resendResult(response);
22
+ }
@@ -0,0 +1,54 @@
1
+ import { classifyHttpStatus, safeProviderMessageId, sendResult } from '../../core/transport.mjs';
2
+
3
+ // Only bounded structured responses are inspected. Body text never becomes diagnostics.
4
+ async function readObject(response) {
5
+ let reader;
6
+ let timer;
7
+ try {
8
+ reader = response.body?.getReader();
9
+ if (!reader) return null;
10
+ const expired = new Promise(resolve => { timer = setTimeout(() => resolve(null), 15_000); });
11
+ const decoder = new TextDecoder('utf-8', { fatal: true });
12
+ let text = '';
13
+ let size = 0;
14
+ while (true) {
15
+ const chunk = await Promise.race([reader.read(), expired]);
16
+ if (!chunk) return null;
17
+ if (chunk.done) break;
18
+ size += chunk.value.byteLength;
19
+ if (size > 65_536) return null;
20
+ text += decoder.decode(chunk.value, { stream: true });
21
+ }
22
+ const data = JSON.parse(text + decoder.decode());
23
+ return data && typeof data === 'object' && !Array.isArray(data) ? data : null;
24
+ } catch { return null; }
25
+ finally {
26
+ clearTimeout(timer);
27
+ // Cancellation itself can fail or stall on a broken stream; never wait for it.
28
+ if (reader) void reader.cancel().catch(() => {});
29
+ }
30
+ }
31
+
32
+ export async function resendResult(response) {
33
+ if (!response) return sendResult('resend', 'uncertain', null, 'transport_error');
34
+ if (response.status === 409) {
35
+ const data = await readObject(response);
36
+ if (data?.name === 'invalid_idempotent_request') return sendResult('resend', 'rejected', null, 'idempotency_conflict');
37
+ if (data?.name === 'concurrent_idempotent_requests') return sendResult('resend', 'uncertain', null, 'idempotency_in_progress');
38
+ return sendResult('resend', 'uncertain', null, 'idempotency_unknown_conflict');
39
+ }
40
+ const status = classifyHttpStatus(response.status);
41
+ if (status) {
42
+ if (response.body) void response.body.cancel().catch(() => {});
43
+ return sendResult('resend', status, null, status === 'rate_limited' ? 'rate_limited' : status === 'rejected' ? 'provider_rejected' : 'unexpected_response');
44
+ }
45
+ // POST /emails documents HTTP 200 with an id, not bare HTTP success.
46
+ if (response.status !== 200) {
47
+ if (response.body) void response.body.cancel().catch(() => {});
48
+ return sendResult('resend', 'uncertain', null, 'unexpected_response');
49
+ }
50
+ const data = await readObject(response);
51
+ const id = safeProviderMessageId(data?.id);
52
+ if (!id || id !== data.id || 'error' in data || 'name' in data) return sendResult('resend', 'uncertain', null, 'invalid_provider_response');
53
+ return sendResult('resend', 'accepted', id);
54
+ }
@@ -0,0 +1,46 @@
1
+ import { fail } from '../../core/errors.mjs';
2
+ import { encodeBase64, parseAddress, validateMessage } from '../../core/validation.mjs';
3
+ import { assertResendMessageSize, assertResendRequestSize } from './size.mjs';
4
+
5
+ function mailbox(value) {
6
+ const { email, name } = parseAddress(value);
7
+ // Quote names containing mailbox delimiters so one address remains one address.
8
+ return name ? `${/[(),:;@\[\]]/.test(name) ? `"${name}"` : name} <${email}>` : email;
9
+ }
10
+
11
+ export function serializeResendMessage(message) {
12
+ if (message && Object.hasOwn(message, 'unsubscribe')) fail('unsubscribe_requires_facade');
13
+ validateMessage(message, 'resend');
14
+ // Preserve the exact key across Fetch's header normalization. No hashing/truncation.
15
+ if (!/^[\x20-\x7e]{1,256}$/.test(message.deliveryId) || message.deliveryId.trim() !== message.deliveryId) fail('invalid_resend_idempotency_key');
16
+ if (message.to.length > 50) fail('resend_recipient_limit');
17
+ assertResendMessageSize(message);
18
+ for (const name in message.headers) {
19
+ if (!Object.hasOwn(message.headers, name)) continue;
20
+ if (['idempotency-key', 'resend-idempotency-key'].includes(name.toLowerCase())) fail('reserved_resend_header');
21
+ }
22
+ const attachments = message.attachments?.map(attachment => {
23
+ // Resend derives inline disposition from content_id; no independent disposition exists.
24
+ if (attachment.disposition === 'attachment' && attachment.contentId !== undefined) fail('unsupported_resend_attachment_disposition');
25
+ return {
26
+ filename: attachment.filename,
27
+ content: encodeBase64(attachment.content),
28
+ content_type: attachment.contentType,
29
+ ...(attachment.contentId === undefined ? {} : { content_id: attachment.contentId }),
30
+ };
31
+ });
32
+ const body = JSON.stringify({
33
+ from: mailbox(message.from),
34
+ to: message.to.map(mailbox),
35
+ subject: message.subject,
36
+ ...(message.text === undefined ? {} : { text: message.text }),
37
+ ...(message.html === undefined ? {} : { html: message.html }),
38
+ ...(message.cc === undefined ? {} : { cc: message.cc.map(mailbox) }),
39
+ ...(message.bcc === undefined ? {} : { bcc: message.bcc.map(mailbox) }),
40
+ ...(message.replyTo === undefined ? {} : { reply_to: mailbox(message.replyTo) }),
41
+ ...(message.headers === undefined ? {} : { headers: message.headers }),
42
+ ...(attachments === undefined ? {} : { attachments }),
43
+ });
44
+ assertResendRequestSize(body);
45
+ return body;
46
+ }
@@ -0,0 +1,99 @@
1
+ import { fail } from '../../core/errors.mjs';
2
+
3
+ // Resend documents 40 MB including Base64 attachments. Use decimal MB and leave
4
+ // 1 MB for provider-added MIME/envelope/signature data; see the adapter README.
5
+ export const MAX_RESEND_EMAIL_BYTES = 40_000_000;
6
+ export const MAX_RESEND_REQUEST_BYTES = 39_000_000;
7
+ const ENVELOPE_RESERVE = MAX_RESEND_EMAIL_BYTES - MAX_RESEND_REQUEST_BYTES;
8
+ const PART_RESERVE = 2048;
9
+ const tooLarge = () => fail('resend_message_too_large');
10
+ const base64Size = bytes => 4 * Math.ceil(bytes / 3);
11
+ const foldedBase64Size = bytes => {
12
+ const encoded = base64Size(bytes);
13
+ return encoded + 2 * Math.ceil(encoded / 76);
14
+ };
15
+
16
+ // Count without TextEncoder/JSON copies, including JSON escapes and lone UTF-16
17
+ // surrogates. Stop once even the source string exceeds the remaining budget.
18
+ function stringSize(value, limit) {
19
+ if (value.length > limit) tooLarge();
20
+ let utf8 = 0;
21
+ let json = 2; // quotes
22
+ let quotedPrintable = 0;
23
+ for (let i = 0; i < value.length; i++) {
24
+ const code = value.charCodeAt(i);
25
+ let bytes = code < 0x80 ? 1 : code < 0x800 ? 2 : 3;
26
+ let unpaired = code >= 0xd800 && code <= 0xdfff;
27
+ if (code >= 0xd800 && code <= 0xdbff) {
28
+ const next = value.charCodeAt(i + 1);
29
+ if (next >= 0xdc00 && next <= 0xdfff) { bytes = 4; unpaired = false; i++; }
30
+ }
31
+ utf8 += bytes;
32
+ json += unpaired || code < 0x20 && ![8, 9, 10, 12, 13].includes(code) ? 6
33
+ : code === 34 || code === 92 || code < 0x20 ? 2 : bytes;
34
+ // Encode all whitespace conservatively, including trailing spaces/tabs.
35
+ quotedPrintable += code >= 33 && code <= 126 && code !== 61 ? 1
36
+ : code === 10 || code === 13 ? 2 : 3 * bytes;
37
+ if (json > limit) tooLarge();
38
+ }
39
+ return { utf8, json, quotedPrintable };
40
+ }
41
+
42
+ /** Preflight validated inputs before mailbox arrays, Base64 or JSON allocation. */
43
+ export function assertResendMessageSize(message) {
44
+ let request = 1024; // fixed JSON field names, delimiters and optional containers
45
+ let email = ENVELOPE_RESERVE;
46
+ function add(requestBytes, emailBytes) {
47
+ request += requestBytes;
48
+ email += emailBytes;
49
+ if (request > MAX_RESEND_REQUEST_BYTES || email > MAX_RESEND_EMAIL_BYTES) tooLarge();
50
+ }
51
+ function metadata(value) {
52
+ const size = stringSize(value, MAX_RESEND_REQUEST_BYTES - request);
53
+ // Covers MIME header encoding/folding plus address quoting/normalization.
54
+ add(size.json + 64, 6 * size.utf8 + 128);
55
+ }
56
+ metadata(message.from);
57
+ metadata(message.subject);
58
+ if (message.replyTo !== undefined) metadata(message.replyTo);
59
+ for (const field of ['to', 'cc', 'bcc']) {
60
+ for (const value of message[field] ?? []) metadata(value);
61
+ }
62
+ for (const name in message.headers) {
63
+ if (!Object.hasOwn(message.headers, name)) continue;
64
+ metadata(name);
65
+ metadata(message.headers[name]);
66
+ }
67
+ for (const field of ['text', 'html']) {
68
+ if (message[field] === undefined) continue;
69
+ const size = stringSize(message[field], MAX_RESEND_REQUEST_BYTES - request);
70
+ // Reserve the larger of Base64 and conservative quoted-printable transfer
71
+ // encodings, including line folding, without assuming Resend's MIME choice.
72
+ const quotedPrintable = size.quotedPrintable + 3 * Math.ceil(size.quotedPrintable / 73);
73
+ const part = PART_RESERVE + Math.max(foldedBase64Size(size.utf8), quotedPrintable);
74
+ // HTML-only requests permit provider-generated plain text. Budget another
75
+ // part; explicit text (including an empty string) suppresses that generation.
76
+ add(size.json, part * (field === 'html' && message.text === undefined ? 2 : 1));
77
+ }
78
+ for (const attachment of message.attachments ?? []) {
79
+ // Charge the complete aggregate before any attachment bytes are read.
80
+ add(base64Size(attachment.content.byteLength) + 128,
81
+ PART_RESERVE + foldedBase64Size(attachment.content.byteLength));
82
+ metadata(attachment.filename);
83
+ metadata(attachment.contentType);
84
+ if (attachment.contentId !== undefined) metadata(attachment.contentId);
85
+ }
86
+ }
87
+
88
+ /** Final actual UTF-8 request bound, independently of the preflight estimate. */
89
+ export function assertResendRequestSize(body) {
90
+ // A valid JSON string has no unescaped lone surrogates. Counting code points
91
+ // avoids allocating a second body-sized Uint8Array just to measure the request.
92
+ if (body.length > MAX_RESEND_REQUEST_BYTES) tooLarge();
93
+ let bytes = 0;
94
+ for (const character of body) {
95
+ const code = character.codePointAt(0);
96
+ bytes += code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
97
+ if (bytes > MAX_RESEND_REQUEST_BYTES) tooLarge();
98
+ }
99
+ }
@@ -0,0 +1,75 @@
1
+ # SendGrid outgoing adapter
2
+
3
+ `index.mjs` exports the portable ESM functions
4
+ `sendSendGrid(config, message, dependencies = {})` and
5
+ `serializeSendGridMessage(message)`. Both accept the frozen IF-056-MAIL-v1
6
+ message shape. The serializer validates and returns deterministic JSON request
7
+ bytes without effects; the sender validates config and message independently
8
+ and makes one POST to `https://api.sendgrid.com/v3/mail/send` with Bearer API-key
9
+ authentication. Config must name `provider: 'sendgrid'`, an explicit mode, and
10
+ `apiKey`. Remote sending remains remote in test/development modes. There is no
11
+ endpoint override, provider fallback, outbox selection or automatic retry.
12
+
13
+ The facade must call the actual `applyUnsubscribeHeaders` implementation before
14
+ this adapter. That assembler consumes `unsubscribe` and returns a new message
15
+ with the real headers. Direct callers must call the assembler themselves, or
16
+ supply their already-validated headers without an `unsubscribe` request. The
17
+ adapter rejects every unconsumed `unsubscribe` request, including one accompanied
18
+ by matching-looking headers, with `unsubscribe_requires_facade`. It validates
19
+ header syntax/reserved names and preserves headers; it does not certify the
20
+ semantics or delivered/DKIM coverage of caller-supplied unsubscribe headers.
21
+
22
+ The wire preserves To/Cc/Bcc display names, plain/HTML alternatives, reply address,
23
+ thread and custom headers, binary attachments, disposition and content IDs.
24
+ Attachments use base64 generated from `Uint8Array`; callers never supply base64.
25
+ `rer_delivery_id` always carries the caller's delivery ID. Custom arguments retain
26
+ the original reserved-name and 10,000-byte UTF-8 JSON limit. Positive safe-integer
27
+ ASM group IDs and optional boolean subscription tracking are supported. Omitted
28
+ subscription tracking remains omitted. Open, click and Google Analytics tracking
29
+ are explicitly disabled. Subscription tracking may append provider content;
30
+ its selection is not proof of delivered unsubscribe headers.
31
+
32
+ Shared core validation rejects unknown fields, header/address injection and
33
+ unsupported shapes. SendGrid additionally rejects empty subjects, explicitly
34
+ empty content parts/attachments, duplicate recipients, non-ASCII sender mailbox
35
+ addresses, and more than 1,000 total To/Cc/Bcc recipients. Omit unused content
36
+ parts. Local resource ceilings are 1,000 attachments, 1,000 header entries (custom arguments retain their 10,000-byte limit), 1,000,000 header key/value code units, and request JSON strictly below
37
+ 30,000,000 UTF-8 bytes. Body and base64-size checks run before encoding attachments;
38
+ final serialized bytes are checked too. The wire ceiling does not establish that
39
+ the provider-generated MIME is below its separate 30 MB message limit. Oversize
40
+ or unsupported requests fail before fetch with fixed `MailValidationError` codes;
41
+ no values appear in diagnostics.
42
+
43
+ The shared transport enforces a 15-second deadline, abort signal and manual
44
+ redirect handling. Only HTTP 202 reports `accepted`; its real, bounded
45
+ `X-Message-Id` is retained or explicitly null. That is submission acceptance,
46
+ not mailbox receipt. HTTP 429 is `rate_limited`; other 4xx except 408 are
47
+ `rejected`. Network loss, deadline expiry, redirects, 408, 5xx and unexpected
48
+ non-202 responses are `uncertain`. Results never expose provider bodies/errors.
49
+ Correlation is not duplicate suppression: reconcile an uncertain submission
50
+ before deciding whether to submit again.
51
+
52
+ ## Verification
53
+
54
+ From Base root:
55
+
56
+ ```sh
57
+ node --test modules/mail/tests/sendgrid/*.test.mjs modules/mail/tests/core/*.test.mjs
58
+ ```
59
+
60
+ Tests include complete wire conformance, all response classes, no retry/forwarding,
61
+ redaction, nullable receipts, direct-call validation, binary round trips, exact
62
+ custom-argument limit, a real 15-second deadline, and isolated ESM execution with
63
+ Web APIs only (no Buffer/process/require). They use injected test-only responses;
64
+ no live provider API is called. `tests/sendgrid/conformance.mjs` exports
65
+ `runSendGridConformance()` for reuse inside workerd, and
66
+ `tests/sendgrid/worker.mjs` is its Worker entry. The isolated harness can run it with
67
+ `createWorker('tests/sendgrid/worker.mjs', ['tests/sendgrid/conformance.mjs',
68
+ 'src/adapters/sendgrid/index.mjs'])`, then dispatch a fetch and dispose the worker.
69
+ `tests/assembly/workerd.test.mjs` exercises the complete facade inside actual
70
+ workerd, including unsubscribe assembly and the doctor sender chain. Hosted send/receive proof remains pending.
71
+
72
+ Provider references checked 6 October 2026:
73
+ [Mail Send](https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send),
74
+ [limits](https://www.twilio.com/docs/sendgrid/api-reference/mail-send), and
75
+ [X-Message-ID](https://www.twilio.com/docs/sendgrid/glossary/x-message-id).
@@ -0,0 +1,35 @@
1
+ import { validateConfig } from '../../core/validation.mjs';
2
+ import { fail, MailValidationError } from '../../core/errors.mjs';
3
+ import { requestProvider, classifyHttpStatus, sendResult, safeProviderMessageId } from '../../core/transport.mjs';
4
+ import { serializeSendGridMessage } from './serialize.mjs';
5
+
6
+ export { serializeSendGridMessage } from './serialize.mjs';
7
+
8
+ /** One submission; acceptance is not mailbox delivery or duplicate suppression. */
9
+ export async function sendSendGrid(config, message, dependencies = {}) {
10
+ let body;
11
+ let apiKey;
12
+ try {
13
+ validateConfig(config, dependencies);
14
+ if (config.provider !== 'sendgrid') fail('sendgrid_provider_required');
15
+ body = serializeSendGridMessage(message);
16
+ apiKey = config.apiKey;
17
+ } catch (error) {
18
+ if (error instanceof MailValidationError) throw error;
19
+ fail('invalid_mail_input');
20
+ }
21
+ const response = await requestProvider('https://api.sendgrid.com/v3/mail/send', {
22
+ method: 'POST',
23
+ headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
24
+ body,
25
+ }, dependencies);
26
+ if (response === null) return sendResult('sendgrid', 'uncertain', null, 'transport_uncertain');
27
+ // Provider bodies may contain secrets or personal information. Never read them.
28
+ if (response.status === 202) {
29
+ return sendResult('sendgrid', 'accepted', safeProviderMessageId(response.headers.get('x-message-id')));
30
+ }
31
+ const status = classifyHttpStatus(response.status) ?? 'uncertain';
32
+ return sendResult('sendgrid', status, null, {
33
+ rejected: 'provider_rejected', rate_limited: 'provider_rate_limited', uncertain: 'provider_uncertain',
34
+ }[status]);
35
+ }
@@ -0,0 +1,104 @@
1
+ import { validateMessage, parseAddress, encodeBase64 } from '../../core/validation.mjs';
2
+ import { fail, MailValidationError } from '../../core/errors.mjs';
3
+
4
+ // Local allocation/wire ceilings, not a promise that provider-generated MIME fits.
5
+ const maxWireBytes = 30_000_000;
6
+ const encoder = new TextEncoder();
7
+
8
+ function preflight(message) {
9
+ if (!message || typeof message !== 'object') fail('invalid_message');
10
+ let recipients = 0;
11
+ for (const field of ['to', 'cc', 'bcc']) {
12
+ if (Array.isArray(message[field])) recipients += message[field].length;
13
+ }
14
+ if (recipients > 1000) fail('sendgrid_too_many_recipients');
15
+ let bytes = 0;
16
+ for (const field of ['text', 'html']) {
17
+ const value = message[field];
18
+ if (typeof value === 'string') {
19
+ if (value.length >= maxWireBytes) fail('sendgrid_message_too_large');
20
+ bytes += encoder.encode(value).byteLength;
21
+ }
22
+ }
23
+ if (Array.isArray(message.attachments)) {
24
+ if (message.attachments.length > 1000) fail('sendgrid_too_many_attachments');
25
+ for (const attachment of message.attachments) {
26
+ if (attachment?.content instanceof Uint8Array) bytes += 4 * Math.ceil(attachment.content.byteLength / 3);
27
+ if (bytes >= maxWireBytes) fail('sendgrid_message_too_large');
28
+ }
29
+ }
30
+ if (bytes >= maxWireBytes) fail('sendgrid_message_too_large');
31
+ // Bound records before the shared validator serializes custom arguments.
32
+ for (const field of ['headers', 'customArgs']) {
33
+ const record = message[field];
34
+ if (record && typeof record === 'object') {
35
+ const keys = Reflect.ownKeys(record);
36
+ if (keys.length > (field === 'customArgs' ? 10_000 : 1000)) fail('sendgrid_metadata_too_large');
37
+ let size = 0;
38
+ for (const key of keys) {
39
+ const value = record[key];
40
+ size += typeof key === 'string' ? key.length : 0;
41
+ size += typeof value === 'string' ? value.length : 0;
42
+ if (size > (field === 'customArgs' ? 10_000 : 1_000_000)) fail('sendgrid_metadata_too_large');
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ /** Validated deterministic request bytes, without any provider or outbox effect. */
49
+ export function serializeSendGridMessage(message) {
50
+ try {
51
+ preflight(message);
52
+ validateMessage(message, 'sendgrid');
53
+ // Only the real unsubscribe assembler consumes this feature request.
54
+ if (message.unsubscribe !== undefined) fail('unsubscribe_requires_facade');
55
+ if (!message.subject.length) fail('sendgrid_subject_required');
56
+ const from = parseAddress(message.from);
57
+ if (/[^\x00-\x7f]/.test(from.email)) fail('sendgrid_ascii_sender_required');
58
+ const personalization = { to: message.to.map(parseAddress) };
59
+ if (message.cc?.length) personalization.cc = message.cc.map(parseAddress);
60
+ if (message.bcc?.length) personalization.bcc = message.bcc.map(parseAddress);
61
+ const recipients = new Set();
62
+ for (const list of Object.values(personalization)) {
63
+ for (const { email } of list) {
64
+ const key = email.toLowerCase();
65
+ if (recipients.has(key)) fail('sendgrid_duplicate_recipient');
66
+ recipients.add(key);
67
+ }
68
+ }
69
+ const content = [];
70
+ for (const [field, type] of [['text', 'text/plain'], ['html', 'text/html']]) {
71
+ if (message[field] !== undefined) {
72
+ if (!message[field].length) fail('sendgrid_empty_content');
73
+ content.push({ type, value: message[field] });
74
+ }
75
+ }
76
+ const attachments = message.attachments?.map(attachment => {
77
+ if (!attachment.content.byteLength) fail('sendgrid_empty_attachment');
78
+ return {
79
+ content: encodeBase64(attachment.content), type: attachment.contentType,
80
+ filename: attachment.filename, disposition: attachment.disposition ?? 'attachment',
81
+ ...(attachment.contentId === undefined ? {} : { content_id: attachment.contentId }),
82
+ };
83
+ });
84
+ const body = JSON.stringify({
85
+ personalizations: [personalization], from, subject: message.subject, content,
86
+ ...(message.replyTo === undefined ? {} : { reply_to: parseAddress(message.replyTo) }),
87
+ // The facade always supplies a headers object; an empty one is the same request as none.
88
+ ...(message.headers === undefined || !Reflect.ownKeys(message.headers).length ? {} : { headers: message.headers }),
89
+ ...(attachments?.length ? { attachments } : {}),
90
+ custom_args: { ...message.customArgs, rer_delivery_id: message.deliveryId },
91
+ ...(message.asmGroupId === undefined ? {} : { asm: { group_id: message.asmGroupId } }),
92
+ tracking_settings: {
93
+ click_tracking: { enable: false, enable_text: false }, open_tracking: { enable: false },
94
+ ganalytics: { enable: false },
95
+ ...(message.subscriptionTracking === undefined ? {} : { subscription_tracking: { enable: message.subscriptionTracking } }),
96
+ },
97
+ });
98
+ if (encoder.encode(body).byteLength >= maxWireBytes) fail('sendgrid_message_too_large');
99
+ return body;
100
+ } catch (error) {
101
+ if (error instanceof MailValidationError) throw error;
102
+ fail('invalid_mail_input');
103
+ }
104
+ }
@@ -0,0 +1,9 @@
1
+ /** Input values never appear in validation diagnostics. */
2
+ export class MailValidationError extends Error {
3
+ constructor(code = 'invalid_mail_input') {
4
+ super('Mail input is invalid; check the error code and module contract.');
5
+ this.name = 'MailValidationError';
6
+ this.code = typeof code === 'string' && /^[a-z][a-z0-9_]{0,63}$/.test(code) ? code : 'invalid_mail_input';
7
+ }
8
+ }
9
+ export function fail(code) { throw new MailValidationError(code); }
@@ -0,0 +1,33 @@
1
+ import { validateSendResult } from './validation.mjs';
2
+
3
+ /** One bounded request. Fetch exceptions never escape with credential/body text. */
4
+ export async function requestProvider(url, init, dependencies = {}) {
5
+ const timeout = dependencies.timeoutMilliseconds ?? 15_000;
6
+ let timer;
7
+ try {
8
+ const deadline = new Promise(resolve => { timer = setTimeout(() => resolve(null), timeout); });
9
+ return await Promise.race([
10
+ Promise.resolve().then(() => (dependencies.fetch ?? globalThis.fetch)(url, {
11
+ ...init, redirect: 'manual', signal: AbortSignal.timeout(timeout),
12
+ })).then(response => response instanceof Response ? response : null, () => null),
13
+ deadline,
14
+ ]);
15
+ } catch { return null; }
16
+ finally { clearTimeout(timer); }
17
+ }
18
+
19
+ export function classifyHttpStatus(status) {
20
+ if (status === 429) return 'rate_limited';
21
+ if (status >= 400 && status < 500 && status !== 408) return 'rejected';
22
+ if (status >= 200 && status < 300) return null;
23
+ return 'uncertain';
24
+ }
25
+
26
+ export function safeProviderMessageId(value) {
27
+ return typeof value === 'string' && value.trim() && value.length <= 1024 && !/[\x00-\x1f\x7f]/.test(value) ? value.trim() : null;
28
+ }
29
+ export function sendResult(provider, status, providerMessageId = null, code, recipientResults) {
30
+ return validateSendResult({ provider, status, providerMessageId,
31
+ ...(code === undefined ? {} : { code }), ...(recipientResults === undefined ? {} : { recipientResults }),
32
+ }, provider);
33
+ }